最近蘋果開源了Swift版的Netty
SwiftNIO是一個跨平臺非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協議伺服器和客戶端。簡單來說就是可以用來實現各種高效能服務端和客戶端,如http、tcp。
因為最近專案剛好要用到udp,所以趁機把平常用Netty實現的該用Swift。
首先簡單看一下幾個用到的類
MultiThreadedEventLoopGroup
顧名思義,這東西是一個執行緒池。每個EventLoopGroup
裡面有多個EventLoop
,而每個EventLoop
都與一個執行緒繫結。
DatagramBootstrap
Bootstrap
是開發Netty程式的基礎,SwiftNIO也是一樣。通過Bootstrap
的bind方法來建立連線,我們也可以通過該方法返回的Channel
來判斷是否建立成功。
ChannelHandler
ChannelHandler
是用來處理資料,如客戶端向服務端傳送資料,服務端的資料處理就是在ChannelHandler
中完成。ChannelHandler
本身是一個protocol,我們用到的有ChannelInboundHandler
和ChannelOutboundHandler
這兩個,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
,並把它新增到
ChannlPipeline
。Handler
需要設定兩個東西 InboundIn
和 OutboundOut
。
InboundIn
: 是入站資料的型別,就是接收客戶端發過來的資料型別。
OutboundOut
: 是出站資料的型別,就是返回給客戶端的資料型別,同時也是傳遞給下一個ChannelOutboundHandler
的型別。
我們這裡用的是 AddressedEnvelope<ByteBuffer>
它裡面是一個 SocketAddress
加上 ByteBuffer
。
當客戶端發來資料的時候會呼叫Handler
的channelRead(ctx: , data: )
這裡進來的是NIOAny
型別,需要呼叫 Handler
的 unwrapInboundIn()
方法把 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了。