[Swift Nio - DatagramBoostrap] Trouble while sending data with large buffer size like image data

I'm newbie with swift and swift nio too. I want to use ios device like udp socket server for capture image from video and send this image to client connect to server. I use netcat on mac os for testing my code. With short text, netcat client received successfully. But with image data, nothing to response. Where is my faults?

import Foundation
import NIOCore
import NIOPosix

private final class ClientHandler: ChannelInboundHandler {
    typealias InboundIn = AddressedEnvelope<ByteBuffer>
    typealias OutboundOut = AddressedEnvelope<ByteBuffer>
    
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let incomingEnvelope = unwrapInboundIn(data)
        
        let videoCapture = VideoCapture.shared()
        if videoCapture.videoUrl == nil {
            if let url = Bundle.main.url(
                        forResource: "video",
                        withExtension: "mp4"
            ) {
                videoCapture.setGenerator(url: url)
            }
        }
        guard let imgData = videoCapture.readFrame() else { return }
        let encodedData = imgData.base64EncodedString()
        
        var buffer = context.channel.allocator.buffer(capacity: encodedData.count)
        buffer.writeString(encodedData)

        let outgoingEnvelope = AddressedEnvelope(remoteAddress: incomingEnvelope.remoteAddress, data: buffer)
        context.write(wrapOutboundOut(outgoingEnvelope), promise: nil)

        print("dubug: send image \(videoCapture.framePos) - \(encodedData.count) bytes")
    }
    

    public func channelReadComplete(context: ChannelHandlerContext) {
        context.flush()
    }

    public func errorCaught(context: ChannelHandlerContext, error: Error) {
        print("error: ", error)
        context.close(promise: nil)
    }
}

enum UDPServerError: Error {
    case invalidHost
    case invalidPort
}

class UDPServer {
    private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    private var host: String?
    var port: Int?
    
    init(host: String, port: Int) {
        self.host = host
        self.port = port
    }
    
    func start() throws {
        guard let host = host else {
            throw UDPServerError.invalidHost
        }
        guard let port = port else {
            throw UDPServerError.invalidPort
        }
        do {
            let bootstrap = DatagramBootstrap(group: group)
                .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
                .channelInitializer { channel in
                    channel.pipeline.addHandler(ClientHandler())
                }
            

            let channel = try bootstrap.bind(host: host, port: port).wait()

            print("Server started and listening on \(channel.localAddress!)")
        } catch let error {
            throw error
        }
    }
    
    func stop() {
        do {
            try group.syncShutdownGracefully()
        } catch let error {
            print("Error shutting down \(error.localizedDescription)")
            exit(0)
        }
        print("Client connection closed")
    }
}

My first guess would be that the data you try to send is too large. UDP does not automatically chunk your data into smaller IP packets like TCP does. SwiftNIO can't automatically do that for you either because the other side needs to be aware that your data is chunked and there are multiple strategies and protocols for that.

To verify that this is indeed an issue with the size of the message you try to send, try to just send up to 500 bytes in one message by replacing your single write with multiple chunked writes:

while !buffer.readableBytesView.isEmpty {
    let outgoingEnvelope = AddressedEnvelope(
        remoteAddress: incomingEnvelope.remoteAddress,
        data: buffer.readSlice(length: min(500, buffer.readableBytes))!
    )
    context.write(wrapOutboundOut(outgoingEnvelope), promise: nil)
}
context.flush()
1 Like

Thanks for your reply. I tried your solution and it worked great. Thanks you so much!

1 Like

UDP does not automatically chunk your data into smaller IP packets
like TCP does.

Just to clarify here, UDP can send large datagrams (up to 64 KiB-ish) but that involves IP-level fragmentation. IP-level fragmentation is almost always bad so, if you need to send large datagrams, it’s better to do your own fragmentation.

In a real app you’ll want to use path MTU discovery. A size of 500 bytes is fine for testing but most real world links support 1500-ish bytes, and so you’re leaving a lot of performance on the floor there.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes