SwiftNIO初探-簡單UDP通訊

skyPivot發表於2018-06-03

最近蘋果開源了Swift版的Netty

SwiftNIO是一個跨平臺非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協議伺服器和客戶端。簡單來說就是可以用來實現各種高效能服務端和客戶端,如http、tcp。

因為最近專案剛好要用到udp,所以趁機把平常用Netty實現的該用Swift。

首先簡單看一下幾個用到的類

MultiThreadedEventLoopGroup

顧名思義,這東西是一個執行緒池。每個EventLoopGroup裡面有多個EventLoop,而每個EventLoop都與一個執行緒繫結。

DatagramBootstrap

Bootstrap是開發Netty程式的基礎,SwiftNIO也是一樣。通過Bootstrap的bind方法來建立連線,我們也可以通過該方法返回的Channel來判斷是否建立成功。

ChannelHandler

ChannelHandler 是用來處理資料,如客戶端向服務端傳送資料,服務端的資料處理就是在ChannelHandler中完成。ChannelHandler 本身是一個protocol,我們用到的有ChannelInboundHandlerChannelOutboundHandler這兩個,ChannlPipeline會從頭到尾順序呼叫ChannelInboundHandler處理資料,從尾到頭呼叫ChannelOutboundHandler資料。

接下來看服務端程式碼:

class UDPServer {
    private let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
    
    func listen(on port: Int) {
        let bootstrap = DatagramBootstrap(group: loopGroup)
            .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
            .channelInitializer { (channel) -> EventLoopFuture<Void> in
                channel.pipeline.add(handler: UDPServerHandler())
        }
        
        do {
            let channel = try bootstrap.bind(host: "127.0.0.1", port: port).wait()
            print("listen on \(channel.localAddress!)")
            try channel.closeFuture.wait()
        } catch {
            print(error)
        }
    }
    
    final class UDPServerHandler: ChannelInboundHandler {
        typealias InboundIn = AddressedEnvelope<ByteBuffer>
        typealias OutboundOut = AddressedEnvelope<ByteBuffer>
        
        func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
            let getData = unwrapInboundIn(data)
            print(getData.data)
        }
    }
}

let server = UDPServer()
server.listen(on: 5656)
複製程式碼

執行程式碼就會可以看到控制檯輸出: listen on [IPv4]127.0.0.1:5656

我們通過內部類的形式實現了一個 ChannelInboundHandler ,並把它新增到 ChannlPipelineHandler 需要設定兩個東西 InboundInOutboundOut

InboundIn: 是入站資料的型別,就是接收客戶端發過來的資料型別。 OutboundOut: 是出站資料的型別,就是返回給客戶端的資料型別,同時也是傳遞給下一個ChannelOutboundHandler的型別。

我們這裡用的是 AddressedEnvelope<ByteBuffer> 它裡面是一個 SocketAddress 加上 ByteBuffer

當客戶端發來資料的時候會呼叫HandlerchannelRead(ctx: , data: ) 這裡進來的是NIOAny型別,需要呼叫 HandlerunwrapInboundIn() 方法把 NIOAny 轉成 InboundIn 型別。

就這樣一個簡單的UDP服務端就完成了,可以通過各種UDP工具進行測試,或者用SwiftNIO再寫一個客戶端:

class UDPClient {
    private let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
    private var channel: Channel!
    
    init(port: Int) {
        let bootstrap = DatagramBootstrap(group: loopGroup)
            .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
            .channelInitializer { (channel) -> EventLoopFuture<Void> in
                channel.pipeline.add(handler: UDPClientHandler())
        }
        
        do {
            channel = try bootstrap.bind(host: "0.0.0.0", port: port).wait()
            sent(with: "test".data(using: .utf8)!)
        } catch {
            print(error)
        }
    }
    
    func sent(with data: Data) {
        var byteBuffer = ByteBufferAllocator().buffer(capacity: data.count)
        byteBuffer.write(bytes: data)
        let address = try! SocketAddress(ipAddress: "127.0.0.1", port: 5656)
        channel.writeAndFlush(NIOAny(AddressedEnvelope<ByteBuffer>(remoteAddress: address, data: byteBuffer)), promise: nil)
    }
    
    final class UDPClientHandler: ChannelInboundHandler {
        typealias InboundIn = AddressedEnvelope<ByteBuffer>
        
        func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
            print(data)
        }
    }
}

let client = UDPClient(port: 22222)
複製程式碼

到這裡,用SwiftNIO構建簡單的UDP通訊已經OK了。

相關文章