Sunday, January 15, 2017

Multi-Client echo server with the BlueSocket framework and libdispatch

In a previous post we showed how to create an echo server using IBM’s BlueSocket framework.  This server was single threaded therefore it could only communicate with one client at a time.  In this post we will show how we can add libdispatch (GCD) to our code to create a multi-client server.
To read an explanation on how the server itself was written, please refer to the previous post Using the BlueSocket framework tocreate an echo server.  The complete code for this project can be found in the Mastering Swift github repository.
The first thing we need to do is add the dispatch framework to the import list.  We should now have the following imports: 

#if os(Linux)
    import Glibc
#else
    import Darwin
#endif
import Foundation
import Socket
import Dispatch

Now we will need to change the start() method a little bit so when it calls the newConnection(socket:) method it uses a separate thread.  The new start() method looks like this:

    func start() throws {
        let socket = try Socket.create()

        listenSocket = socket
        try socket.listen(on: port)
        print("Listening port: \(socket.listeningPort)")
        let queue = DispatchQueue(label: "clientQueue.hoffman.jon", attributes: .concurrent)
        repeat {
            let connectedSocket = try socket.acceptClientConnection()
            print("Connection from: \(connectedSocket.remoteHostname)")
            queue.async{self.newConnection(socket: connectedSocket)}
        } while acceptNewConnection     
    }

There are two parts of this code that changed.  The first is the let queue = DispatchQueue(label: "clientQueue.hoffman.jon", attributes: .concurrent) line which was added to our code.  This line creates a new concurrent dispatch queue.  The second change is we used the queue.async() method to submit the newConnection(socket:) call to the dispatch queue.  You can use the EchoServer class exactly they same way as we did in the previous post.

Those are the only changes you need to make the code a multi-client server.  Here is the full code for the multi-client EchoServer class :

class EchoServer {

    let bufferSize = 1024
    let port: Int
    var listenSocket: Socket? = nil
    var connected = [Int32: Socket]()
    var acceptNewConnection = true

    init(port: Int) {
        self.port = port
    }

    deinit {
        for socket in connected.values {
            socket.close()
        }
        listenSocket?.close()
    }

    func start() throws {
        let socket = try Socket.create()

        listenSocket = socket
        try socket.listen(on: port)
        print("Listening port: \(socket.listeningPort)")
        let queue = DispatchQueue(label: "clientQueue.hoffman.jon", attributes: .concurrent)
        repeat {
            let connectedSocket = try socket.acceptClientConnection()
            print("Connection from: \(connectedSocket.remoteHostname)")
            queue.async{self.newConnection(socket: connectedSocket)}
        } while acceptNewConnection     
    }

    func newConnection(socket: Socket) {
        connected[socket.socketfd] = socket

        var cont = true
        var dataRead = Data(capacity: bufferSize)
        repeat {
            do {
                let bytes = try socket.read(into: &dataRead)
                if bytes > 0 {
                    if let readStr = String(data: dataRead, encoding: .utf8) {
                        print("Received: \(readStr)")
                        try socket.write(from: readStr)
                        if readStr.hasPrefix("quit") {
                            cont = false
                            socket.close()
                        }
                        dataRead.count = 0
                    }
                }
            } catch let error {
                print("error: \(error)")
            }
        } while cont
        connected.removeValue(forKey: socket.socketfd)
        socket.close()
    }
}

You can download the full code listing from the MasteringSwift github repository.


No comments:

Post a Comment