Problem with writing and flushing data in channelRead

Hi,

as I have just started with swift and swift-nio I am having trouble with a simple thing. Writing data in channelRead of a handler. The data is properly written but it is not flushed (sent) back to the server. When I close the app the missing buffer is sent.

Let me show you some of the code (please don't mind the poor quality, it's just a POC :slight_smile:)

Setting up the client:

import Foundation
import NIO
import SWIRCMessageParser

final public class IRCClient {
    private var channel: Channel? = nil
    private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    private let parser = IRCMessageParser()
    private let dispatcher = IRCMessageDispatcher()
    private let host: String
    private let port: Int
    
    public init(host: String, port: Int) {
        self.host = host;
        self.port = port;
        
        dispatcher.addReceiver(PingReceiver(self))
    }
    
    public func connect() throws {
        do {
            self.channel = try ClientBootstrap(group: group)
                .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
                .channelInitializer { channel in
                    channel.pipeline.addHandler(ByteToMessageHandler(LineDecoder())).flatMap { v in
                        channel.pipeline.addHandler(IRCMessageHandler(dispatcher: self.dispatcher, parser: self.parser))
                    }
                }
                .connect(host: self.host, port: self.port)
                .wait()
        } catch let error {
            throw error
        }
    }
    
    public func close() throws {
        try group.syncShutdownGracefully()
    }
    
    public func send(_ message: String) throws {
        if self.channel == nil {
            throw NSError(domain: "Called \(#function) before connect", code: 1)
        }

        _ = channel!.writeAndFlush(message)
    }
}

Simple dispatcher:

import Foundation
import SWIRCMessageParser

final class IRCMessageDispatcher {
    private var receivers: [IRCMessageReceiver]

    public init(_ receivers: [IRCMessageReceiver] = []) {
        self.receivers = receivers
    }
    
    public func addReceiver(_ receiver: IRCMessageReceiver) {
        self.receivers.append(receiver)
    }
    
    public func dispatch(_ message: IRCMessage) {
        self.receivers.forEach { (receiver: IRCMessageReceiver) in
            receiver.receive(message)
        }
    }
}

Channel Handler:

import NIO
import SWIRCMessageParser

final class IRCMessageHandler: ChannelDuplexHandler {
    typealias InboundIn = ByteBuffer
    typealias InboundOut = ByteBuffer
    typealias OutboundIn = String
    typealias OutboundOut = ByteBuffer
    
    private let dispatcher: IRCMessageDispatcher
    private let parser: IRCMessageParser
    
    init(dispatcher: IRCMessageDispatcher, parser: IRCMessageParser) {
        self.dispatcher = dispatcher
        self.parser = parser
    }
    
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let message = parser.parse(rawMessage: String(buffer: self.unwrapInboundIn(data)))
        if message == nil {
            print("Invalid message received: \(data.description)")
            context.fireChannelRead(data)

            return
        }

        print(message!)
        self.dispatcher.dispatch(message!)

        context.fireChannelRead(data)
    }


    func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
        var buffer = context.channel.allocator.buffer(capacity: 200)
        buffer.writeString(self.unwrapOutboundIn(data))

        context.write(NIOAny(buffer), promise: promise)
    }
}

And the PingReceiver:

import Foundation
import SWIRCMessageParser

open class PingReceiver: IRCMessageReceiver {
    private let client: IRCClient
    
    public init(_ client: IRCClient) {
        self.client = client;
    }
    
    public func receive(_ message: IRCMessage) {
        if message.command == "PING" {
            do {
                try self.client.send("PONG :\(message.params.first!)")
            } catch let error {
                print("PONG Error: \(error.localizedDescription)")
            }
        }
    }
}

I have already tried simplifying it to just context.writeAndFlush if message is a ping in channelRead and that still did not send the message instantly.

How do you know the data is not sent to the server?

I run the server locally:

[23 Apr 2020 09:19:45] [DEBUG] Client connected (127.0.0.1:59530).
[23 Apr 2020 09:19:45] [DEBUG] Received message: Command: NICK, Params: ["nick"]
[23 Apr 2020 09:19:45] [DEBUG] Received message: Command: USER, Params: ["nick", "2", "*", "nick"]
[23 Apr 2020 09:19:55] [DEBUG] Client did not respond to ping (127.0.0.1:59530).
// HERE I QUIT THE APP - remaining buffer is sent
[23 Apr 2020 09:20:02] [DEBUG] Received message: Command: PONG, Params: ["server"]
[23 Apr 2020 09:20:02] [DEBUG] Client disconnected (127.0.0.1:59530).

So IRC is a line-separated protocol. Right now your client commands don't contain newlines. You send "PONG :\(message.params.first!)". Have you tried changing that to ""PONG :\(message.params.first!)\r\n"

Wow. I feel quite ashamed now. I don't know how have I missed that here, since I do that in the other place:

            try client.send("NICK nick\r\n")
            try client.send("USER nick 2 * :nick\r\n")

Damn... sorry for wasting your time, it of course works now! :smiley:

You didn't waste my time, and there's no need to feel ashamed! Things like this are easy to miss.

4 Likes