TransWikia.com

Simple TCP listener on swift

Stack Overflow Asked by Mr. Park on November 7, 2021

I send messages to applications running on remote computers.
I send messages this ways.

c#:

    void web_data_work()
    {
        try
        {
            TcpClient client = new TcpClient(address, port);
            Byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}", message));
            NetworkStream stream = client.GetStream();
            try
            {
                if (message != "")
                {
                    stream.Write(data, 0, data.Length);
                    Byte[] readingData = new Byte[256];
                    String responseData = String.Empty;
                    StringBuilder completeMessage = new StringBuilder();
                    int numberOfBytesRead = 0;
                    do
                    {
                        numberOfBytesRead = stream.Read(readingData, 0, readingData.Length);
                        completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(readingData, 0, numberOfBytesRead));
                    }
                    while (stream.DataAvailable);
                    responseData = completeMessage.ToString();
                    this.Invoke((MethodInvoker)delegate ()
                    {
                        output_list.Items.Add(string.Format("Sended – {0}", responseData));
                        message = "";
                    });
                }
            }
            finally
            {
                stream.Close();
                client.Close();
            }
        }
        catch
        {
            this.Invoke((MethodInvoker)delegate ()
            {
                output_list.Items.Add("Not sended");
                message = "";
            });
        }
    }

swift:

func web_data_work()
{
    let address = address_box.stringValue
    let port = port_box.intValue
    
    let task = URLSession.shared.streamTask(withHostName: address, port: Int(port))
    let data = message.data(using: .utf8)!

    task.write(data as Data, timeout: 0)
    {
        error in
        //om = "Not sended"
        //self.output_message()
    }

    task.resume()
}

In c# i can read messages using TcpListener, how do i do it in swift?
Only two parameters are used:
"address" – ip address of the computer to which messages are sent and which is listened to by the TcpListener of that computer.
"port" – port of the sending and receiving between computers.

P.S. Preferably without additional libraries.

One Answer

You can achieve this with just two classes: SocketPort & FileHandle.

Before you copy & paste the example below, please, read my comments at the end.

import Cocoa

class TcpEchoClient {
    var readToEndOfFileCompletionHandler: (() -> Void)?
    let fileHandle: FileHandle
    var observer: NSObjectProtocol?
    
    init(fileHandle: FileHandle) {
        self.fileHandle = fileHandle
        
        // Register observer for the data & EOF
        self.observer = NotificationCenter.default.addObserver(forName: .NSFileHandleReadToEndOfFileCompletion,
                                                               object: fileHandle,
                                                               queue: OperationQueue.main) { [weak self] note in
            self?.handleReadToEndOfFileCompletion(notification: note)
        }
        
        // Instruct the handle to read till the EOF & notify
        fileHandle.readToEndOfFileInBackgroundAndNotify()
    }
    
    func handleReadToEndOfFileCompletion(notification note: Notification) {
        defer {
            // No matter what happens, call the completion handle by the end
            readToEndOfFileCompletionHandler?()
        }
        
        // Is there an error?
        if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
            print("Client (fileHandle.fileDescriptor) error: File handle error (errorCode.intValue)")
            return
        }
        
        // No error, we should have data available
        guard let data = note.userInfo?[NSFileHandleNotificationDataItem] as? Data else {
            print("Client (fileHandle.fileDescriptor) error: Unable to get data")
            return
        }
        
        // Convert them to UTF-8 string
        guard let text = String(data: data, encoding: .utf8) else {
            print("Client (fileHandle.fileDescriptor) error: Unable to convert data to UTF-8 string")
            return
        }
        
        // Print the text
        print("Client (fileHandle.fileDescriptor) received: (text)")
    }
    
    deinit {
        // Remove observer for the data & EOF
        if let observer = observer {
            NotificationCenter.default.removeObserver(observer)
        }
        // Close the handle
        try? fileHandle.close()
    }
}

class TcpServer {
    let port: SocketPort
    let fileHandle: FileHandle
    var clients: Dictionary<Int32, TcpEchoClient>
    var observer: NSObjectProtocol?
    
    init?(tcpPort: UInt16) {
        guard let socketPort = SocketPort(tcpPort: tcpPort) else {
            return nil
        }

        // Keep the socket port around otherwise you'll get error 38
        port = socketPort
        // No clients for now
        clients = [:]
        // Create handle from the socket
        fileHandle = FileHandle(fileDescriptor: port.socket)
        // Register observer for the connection accepted
        observer = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
                                                          object: fileHandle,
                                                          queue: OperationQueue.main) { [weak self] note in
            if let handle = note.object as? FileHandle {
                // Ask immediately for another accepted connection notification
                handle.acceptConnectionInBackgroundAndNotify()
            }
            
            self?.handleConnectionAccepted(notification: note)
        }
        
        // Instruct the handle to accept connection & notify
        fileHandle.acceptConnectionInBackgroundAndNotify()
    }
    
    func handleConnectionAccepted(notification note: Notification) {
        // Is there an error?
        if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
            print("Server error: File handle error (errorCode.intValue)")
            return
        }
        
        // No, we should have received the client file handle
        guard let clientFileHandle = note.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else {
            print("Server error: Unable to get accepted connection file handle")
            return
        }

        let fileDescriptor = clientFileHandle.fileDescriptor
        
        // Create new client from the file handle
        let client = TcpEchoClient(fileHandle: clientFileHandle)
        
        // Once it finishes, remove it from the clients dictionary
        client.readToEndOfFileCompletionHandler = { [weak self] in
            guard let self = self else { return }
            
            self.clients.removeValue(forKey: fileDescriptor)
            print("Server: Client removed (fileDescriptor) (total (self.clients.count))")
        }
        
        // Store the client in the clients dictionary
        clients[fileDescriptor] = client
        
        print("Server: New client (fileDescriptor) added (total (self.clients.count))")
    }
    
    deinit {
        // Remove all clients
        clients.removeAll()
        // Close the file handle
        try? fileHandle.close()
        // Invalidate the socket port
        port.invalidate()
        // Remove connection accepted observer
        if let observer = observer {
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

Then create a property (var server: TcpServer?) and initialize it (server = TcpServer(tcpPort: 8080)).

Test:

parallel -j 1 echo -n Foo '|' nc 127.0.0.1 8080 ::: {1..4}

Console output:

Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)

Comments:

  • This example is only a basic skeleton you can start with.
  • Don't be misleaded with the parallel test command.
    • I'm using it if I want to run multiple commands at once (same commands).
    • Everything in this example operates on the main queue.
  • Read documentation of all these classes, methods and notifications I do use. There're gotchas, you have to be familiar with run loops, etc.

Answered by zrzka on November 7, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP