I'm building a TCP socket interface class in async/await style using SwiftNIO, the interface is in one-write-one-read pattern.
After some work I realized that after I write and flush some data to the socket, I can only get response data from a ChannelHandler
, which in my case is ChannelInboundHandler
. Its callback function channelRead
got the respond data, but I have no idea how to pass that data to the interface function.
I read the docs and know that there are EventLoopFuture
and EventLoopPromise
which may probably can help me achieve my goal, but I just can't figure out how.
I've searched the internet and found this, I used code mentioned but didn't work.
My code:
Interface class
class PTSocket: ObservableObject {
private var host: String
private var port: Int
private var handler: PTHandler?
private var eventLoopGroup: EventLoopGroup
private var bootstrap: ClientBootstrap
private var channel: Channel?
init(host: String, port: Int) {
let handler = PTHandler()
self.host = host
self.port = port
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
bootstrap = ClientBootstrap(group: eventLoopGroup)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.pipeline.addHandler(handler)
}
self.handler = handler
}
public func connect() async throws {
channel = try { () -> Channel in
return try bootstrap.connect(host: host, port: port).wait()
}()
// Handshake
try await send(command: .handshake)
}
public func send(command: PTMagic) async throws -> [UInt8] {
let buffer = channel!.allocator.buffer(bytes: command.magic)
channel!.writeAndFlush(buffer, promise: nil)
let promise = channel?.eventLoop.next().makePromise(of: [UInt8].self)
let result = try await promise!.futureResult.get()
print("Waiting for data:\(result)")
return []
}
}
Handler class
class PTHandler: ChannelInboundHandler {
public typealias InboundIn = ByteBuffer
public typealias OutboundOut = ByteBuffer
private var responseBytes: [UInt8] = []
// For reading chunks
private var totalBytesCount: Int = 0
private var remainingBytesCount: Int = 0
private var existingBytes: [UInt8] = []
// init(callback: @escaping ([UInt8]) -> Void) {
// readCallback = callback
// }
public func channelActive(context: ChannelHandlerContext) {
print("Socket connected.")
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
var buffer = unwrapInboundIn(data)
if let received = buffer.readBytes(length: buffer.readableBytes) {
print("[Server] Reply: '\(received)'")
responseBytes.append(contentsOf: received)
}
context.fireChannelRead(data)
}
func channelReadComplete(context: ChannelHandlerContext) {
print("Read complete: \(responseBytes)")
context.fireChannelReadComplete()
responseBytes.removeAll()
}
public func errorCaught(context: ChannelHandlerContext, error: Error) {
print("error===: ", error)
context.close(promise: nil)
}
}
If I run this code, the Waiting for data
print is never reached, and it blocks my next call to send
.