꿈돌이랜드

[번역] SwiftNIO Readme Conceptual Overview 본문

Programming/iOS

[번역] SwiftNIO Readme Conceptual Overview

loinsir 2024. 1. 16. 15:52
반응형

https://github.com/apple/swift-nio

iOS 앱 내에 로컬 프록시 서버를 구축하기 위해 방법을 찾아보던 도중, 서버 개발을 위한 SwiftNIO라는 애플의 프레임워크를 알 게 되었습니다.

사용 방법을 위해 README를 읽고 번역해보았습니다. 오역이 있을 수 있습니다.

Swift로 서버를 구축해보고자 하시는 분들께 도움이 되었으면 합니다.


SwiftNIO는 유지 관리 가능한 고성능 프로토콜 서버 및 클라이언트의 신속한 개발을 위한 크로스 플랫폼 비동기, 이벤트 드리븐 네트워크 어플리케이션 프레임워크.

Netty와 비슷하지만, Swift용으로 작성됨.

개요

  • SwiftNIO는 기본적으로 Swift에서 고성능 네트워킹 어플리케이션을 구축하기 위한 도구.
  • 특히 thread-per-connect 동시성 모델이 비효율적이거나 지원 불가능한 사례를 대상으로 한다.
  • 이를 위해 SwfitNIO는 non-blocking-I/O를 광범위하게 사용
  • SwiftNIO는 웹 프레임워크와 같은 고수준의 솔루션을 제공하는 것이 목표가 아니다.
  • 대신 SwiftNIO는 이러한 상위 수준 애플리케이션을 위한 하위 수준의 빌딩 블록을 제공하는 게 목표

기본 아키텍처

SwiftNIO의 기본 빌딩 블록은 다음 8가지 타입의 객체가 있다.

  • EventLoopGroup: 프로토콜
  • EventLoop: 프로토콜
  • Channel: 프로토콜
  • ChannelHandler: 프로토콜
  • Bootstrap: 몇가지 연관된 구조체
  • ByteBuffer: 구조체
  • EventLoopFuture: 제네릭 클래스
  • EventLoopPromise: 제네릭 구조체

EventLoop와 EventLoopGroups

  • SwiftNIO의 기본적인 I/O 원시는 EventLoop
  • event loop는 이벤트(data received와 같이 보통 I/O 와 관련된 이벤트)가 발생하기를 기다린 다음, 발생 시 일종의 콜백을 실행하는 객체
  • 거의 모든 SwiftNIO 애플리케이션에는 상대적으로 적은 수의 event loop가 존재.
  • 일반적으로 애플리케이션이 사용하려는 CPU 코어 당 한두 개만 사용
  • 일반적으로 event loop는 애플리케이션의 전체 수명 동안 실행되며 이벤트를 전달하는 끝없는 루프에서 spinning

  • event loop들은 event loop groups로 수집된다.
  • 이러한 그룹은 이벤트 루프를 중심으로, 작업을 분배하는 메커니즘을 제공
    • 예를 들어, 인바운드 연결을 수신할 때, listen 소켓은 하나의 이벤트 루프에 등록
    • 하지만 우리는, 해당 listten 소켓에서 허용되는 모든 연결이 동일한 event loop에 등록되는 것을 원하지 않음
    • 왜냐하면 그렇게 되면 다른 event loop가 비워져 있음에도 하나의 event loop에 잠재적으로 과부하가 걸릴 수 있기 때문에
    • 이러한 이유로 event loop groups는 여러 event loop에 load를 분산시키는 기능을 제공

  • 현재 SwiftNIO는 하나의 EventLoopGroup(MultiThreadedEventLoopGroup)과 두개의 EventLoop 구현(SelectableEventLoop, EmbeddedEventLoop)이 존재.
  • 프로덕션 애플리케이션의 경우 POSIX 라이브러리를 사용해서 여러 스레드를 생성하고 각 스레드에 하나씩 배치하는 MultiThreadedEventLoopGroup이 존재
  • SelectableEventLoop는 file descriptors 로부터 I/O 이벤트를 관리하거나, work를 전송하는 작업을 수행하는 selector를 사용하는 event loop이다.
  • 이러한 EventLoop와 EventLoopGroups는 NIOPosix 모듈을 통해 제공.
  • 추가적으로 주로 테스팅 목적의 dummy event loop인 EmbeddedEventLoop도 존재 (NIOEmbedded 모듈을 통해 제공)

  • EventLoop들에는 여러가지 중요한 속성이 존재
  • 가장 중요한 건 SwiftNIO 애플리케이션에서 모든 작업이 수행되는 방식.
  • EventLoop 객체들은 거의 다른 SwiftNIO 애플리케이션의 객체들을 소유하고, 이 실행 모델을 이해하는 것은 매우 중요함

Channels, Channel Handlers, Channel Pipelines, and Channel Contexts

  • EventLoop들이 SwiftNIO의 동작에 매우 중요하지만, 대부분 사용자는 EventLoopPromise들을 생성하고 작업을 예약하도록 요청하는 것 외에는 실질적으로 상호작용하지는 않는다.
  • SwiftNIO 어플리케이션 대부분의 유저는 Channels와 ChannelHandlers와 상호작용하는데 대부분의 시간을 할애할 것.
  • 유저와 상호작용 하는 거의 모든 SwiftNIO 프로그램의 file descriptor는 단일 Channel과 관련된다.
  • Channel은 이러한 file descriptor를 소유하고, 그것의 생명주기를 관리하는 책임이 있다.
  • 또한 그 file descriptor의 Inbound와 outbound 이벤트를 처리할 책임도 있다:
    • even loop에 file descriptor에 해당하는 이벤트가 있을 때 마다 해당 file descriptor를 소유한 Channel에 알릴 것이다.

  • Channel 그것들 자신은 유용하지 않다. 드물게, socket에서 받거나 보낸 데이터로 아무것도 원하지 않는 애플리케이션이 존재한다.
  • 그래서 Channel의 또다른 중요한 부분은 ChannelPipeline이다.
  • ChannelPipeline은 ChannelHandler라고 불리는, Channel의 이벤트를 처리하는 객체의 시퀀스다.
  • ChannelHandler들은 진행하면서, 순서대로 하나에서 다른 하나로 이러한 이벤트들을 전환하고 변화시키면서 처리한다.
  • 이것들을 데이터 처리 파이프라인으로 생각할 수 있다. 그래서 이름이 ChannelPipeline이다.

  • 모든 ChannelHandler들은 Inbound와 Outbound 핸들러 이거나, 둘다 이다.
  • Inbound 핸들러들은 inbound 이벤트를 처리한다.
    • inbound 이벤트는 socket으로부터 데이터를 읽거나
    • socket close나 를 원격 peer들에 의해 발생한 이벤트의 다른 종류들 읽거나 하는 이벤트들을 말한다.
  • Outbound 핸들러들은 outbound 이벤트를 처리한다.
    • writes나, 접속 시도, local socket close같은 이벤트를 말한다.

  • 각 핸들러는 event들을 순서대로 처리한다.
    • 예를 들어, write 이벤트 들이 pipeline의 뒤에서부터 앞으로 전달되는 반면, read 이벤트들은 한번에 한 handler씩, pipeline의 앞부터 뒤로 전달된다.
    • 각 handler는 언제든지 적절한 방향으로 다음 handler로 전달될 inbound 혹은 outbound 이벤트를 생성할 수 있다.
    • 이를 통해 handler는 read를 분할하고, write를 통합하고, 연결 시도를 지연하고, 일반적으로 이벤트의 임의 전환을 수행할 수 있다.

  • 일반적으로 ChannelHandler는 재사용 가능성이 높은 구성 요소로 설계되었다.
  • 이 말은, 한 특정 데이터 변환의 수행하면서, 가능한 한 작게 디자인 되도록 의도했다는 것이다.
  • 이는 handler를 새롭고 유연한 방식으로 함께 구성할 수 있어 코드 재사용 및 캡슐화에 도움이 된다.

  • ChannelHandler는 ChannelHandlerContext를 사용하여, 자신이 ChannelPipeline에 있는 위치를 추적 할 수 있다.
    • 이 객체들은 pipeline에서 이전과 다음 channel handler의 참조를 포함해서, 항상 ChannelHandler가 pipeline에서 유지되는 동안 event를 방출하는 것을 보장한다.

  • SwiftNIO에는 HTTP 파싱과 같은 기능적으로 유용한 ChannelHandler가 많이 적재되어 있다.
  • 추가적으로, 고성능 어플리케이션은 Context Switching을 동반하는 문제를 방지하는 데 도움이 되므로, 최대한 많은 로직을 Channelhandler에서 제공하는 것을 원할 것 입니다.

  • SwiftNIO는 몇몇 Channel 구현을 적재하고 있습니다.
  • 특히 inbound 연결을 accept하는 ServerSocketChannel, TCP 연결을 위한 Channel인 SocketChannel, UDP 소켓을 위한 DatagramChannel을 적재하고 있습니다.
    • 이러한 모든 것들은 EmbddedChannel을 제공하는 NIOPosix 모듈을 통해 제공됩니다.

A Note on Blocking

  • ChannelPipeline에 관하여 중요한 것 중 하나는 그들은 thread-safe 하다는 것.
  • 이것은 SwiftNIO 어플리케이션을 작성하는 데 매우 중요하고, 이로 인해 synchronize의 없이 ChannelHandler 들을 더 간단하게 작성할 수 있게 한다.
  • 그러나 이는 모든 ChannelPipeline의 코드가 EventLoop로 같은 스레드에 보내지는 걸로 이뤄진다.
  • 이 말은, 일반적으로, ChannelHandler는 background 스레드로 보내지 않는 blocking code를 호출하면 안된다.
  • 만약, ChannelHandler가 어떠한 이유로 block 된다면, 모든 부모 EventLoop에 붙은 Channel들은 blocking 호출이 완료될 때 까지 진행할 수 없게 된다.
  • 이는 일반적으로 SwiftNIO 어플리케이션을 작성하면서 고려할 사항.
  • 만약 코드를 blocking 스타일로 작성하는게 유용하다면, pipeline에서 작업을 마치면 다른 스레드로 작업을 전송시키는 게 좋다.

Bootstrap

  • EventLoop들에 Channel을 직접 설정하고, 등록하는게 가능하지만, 일반적으로 이 작업을 처리하기 위해서는 고수준의 추상화를 사용하는 게 더 유용하다.
  • 이러한 이유로, SwiftNIO는 channel들의 생성을 선형으로 연결하는 목적의 몇가지 Bootstrap 객체를 적재하고 있다.
  • 일부 BootStrap 객체는 또한 TCP 연결 시도를 위한 Happy Eyeballs 지원과 같은 다른 기능도 제공한다.
  • 현재 SwiftNIO는 NIOPosix 모듈 안에 3가지 Bootstrap 객체를 적재하고 있다:
    • channel listening을 위한 ServerBootstrap
    • TCP 채널 클라이언트를 위한 ClientBootstrap
    • UDP 채널을 위한 DatagramBootstrap

ByteBuffer

  • SwiftNIO 어플리케이션 내의 작업의 다수는 Byte 단위의 버퍼를 뒤섞는 것.
  • 적어도, Byte단위의 버퍼 형태로 네트워크와 data를 주고 받는다.
  • 그렇기에, SwiftNIO 어플리케이션이 수행하는 작업 종류의 최적화된 고성능 데이터 구조를 가지는 게 중요하다.

  • 이러한 이유로, SwiftNIO는 SwiftNIO 어플리케이션 대부분의 핵심 building 블록을 형성하는 빠른 copy-on-right bytebuffer인 ByteBuffer를 제공한다.
  • 이 타입은 NIOCore 모듈로 제공된다
  • ByteBuffer는 몇가지 유용한 기능을 제공하고, 추가적으로 unsafe 모드에서 사용할 수 있는 몇가지 hook들을 제공한다.
  • 이렇게 하면 메모리 정확성 문제까지, 응용 프로그램을 여는 비용으로 성능이 향상되는지 확인하는 경계를 허문다.
  • 일반적으로, 항상 safe mode에서 ByteBuffer를 사용하는 것이 매우 권장된다.

Promises and Futures

  • 동시성과 동기 코드를 작성하는 것의 주된 차이점은, 즉시 모든 action들이 완료되지 않을 것이라는 것.
  • 예를 들어, channel에 데이터를 작성할 때, Event loop가 즉시 해당 write를 네트워크로 flush하지 못할 수 있다.
  • 이러한 이유로, SwiftNIO는 EventLoopPromise<T>와 EventLoopFuture<T>를 제공하여 비동기적으로 완료 되는 작업을 관리한다.
  • 이러한 타입은 NIOCore 모듈에서 제공한다.

  • EventLoopFuture<T>는 미래에 특정 시점에 채워질 함수의 리턴값을 위한 컨테이너이다.
  • 각 EventLoopFuture는 상응하는 결과가 넣어질 객체인 EventLoopPromise<T>을 가진다.
  • promise가 성공하면, future가 채워진다.

  • future를 poll하여 완료 시점을 탐지해야 한다면, 상당히 비효율적일 것이므로, EventLoopFuture<T>는 관리되는 callback들을 가질 수 있도록 디자인 되었다.
  • 기본적으로, 결과가 가능해질 때, 실행될 미래의 callback을 중단할 수 있다.
  • EventLoopFuture<T>는 이러한 callback이 처음에 promise를 생성한 event loop에서 항상 실행되도록 스케줄링을 신중하게 조정해서 event loop future 콜백을 중심으로 너무 많은 동기화를 필요로 하지 않도록 한다.

  • 고려해야 할 또다른 주제는 닫히는 작업으로 전달된 promise가 Channel에서의 closeFuture과 어떻게 다르게 동작하는지다.
  • 예를 들어, 닫히는 작업으로 전달된 promise는 Channel이 닫힌 이후, ChannelPipeline이 완전히 지워지기 전이지만, 전달된 promise는 성공할 것이다.
  • 이를 통해, 필요한 경우 channelpipeline이 완전히 지워지기 전에 조치를 취할 수 있다.
  • 추가적인 조치 없이 channel이 닫히고, channelpipeline까지 지워질 때까지 기다리려면 closeFuture가 succeed할 때까지 기다리는 것이 더 나은 방법일 것이다.


Uploaded by N2T

반응형