Showing posts with label network. Show all posts
Showing posts with label network. Show all posts

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.


Saturday, January 14, 2017

Using the BlueSocket framework to create an Echo client

In our last post we showed how to use the BlueSocket framework from IBM to create an echo server. In this post we will show how to use the same BlueSocket framework to create a client application that can communicate with the echo server from the previous post.

The echo client will first connect to the server to make sure it is running and then ask the user for input. When the user inputs a line of text and presses enter, the text will be sent to the server which should “echo” that text back to the client.

Whatever system you will be developing your application for, the BlueSocket github repository has instructions on how to include it with your project. The sample project on the Mastering Swift github repository uses Swift’s Package Manager to include the framework. The project, in the github repository is titled echoClient.

We will start by creating an EchoClient class and importing the frameworks that we need.

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

class EchoClient {

}

In this code we import the Glibc or Darwin frameworks depending on the system we are running on.  We then import both the Socket and Foundation frameworks.  Now we need to define a couple of properties for our class.

    let bufferSize = 1024
    let port: Int
    let server: String
    var listenSocket: Socket? = nil

In this code we define four properties.  The first is the bufferSize property that defines the maximum number of characters that we can receive from the echo server at one time.  The port and server properties will define the server and port number that the echo server is running on.  The listenSocket property is the socket that we will listen to in order to receive the response back form the server.

Now we will create an initializer and a deinitializer for the EchoClient class.
   
    init(port: Int, server: String) {
        self.port = port
        self.server = server
    }
   
    deinit {
        listenSocket?.close()
    }


The initializer requires that the code calling it to provide both the port and server that we are connecting too.  We will see how to call this initializer at the end of the post.  The deinitializer disconnects the client from the server.

Now lets create the method that will connect to the server.  We will call this method start().
 
  func start() throws {
      let socket = try Socket.create()
      listenSocket = socket
      try socket.connect(to: server, port: Int32(port))
      var dataRead = Data(capacity: bufferSize)
      var cont = true
       repeat {
          print("Enter Text:")
          if let entered = readLine(strippingNewline: true) {
            try socket.write(from: entered)
            if entered.hasPrefix("quit") {
               cont = false
            }
            let bytesRead = try socket.read(into: &dataRead)
            if bytesRead > 0 {
              if let readStr = String(data: dataRead, encoding: .utf8) {
                print("Received: '\(readStr)'")
              }       
              dataRead.count = 0
            }
          }
       } while cont
    }


This code starts off by using the create() method to create a socket and then uses the connect(to:port:) method of the socket to connect to the server.  We create a dataRead variable that will contain the text that is echoed back from the server.  The capacity of the dataRead variable is the size defined by the bufferSize constant.

We now create a repeat loop that will repeat as long as the cont variable is true.  We use the readLine(strippingNewLine:) method to read the input from the user.  Once the user enters the text we use the write(from:) method to send the text to the server.  We also check to see if the users entered “quit” and if so we set the cont variable to false to end the repeat loop after this iteration.

Next we use the read(into:) method of the socket to read the response from the server.  The read(into:) method returns the number of bytes read.  If the number is greater than zero, we convert the response to a string and print it to the console.  That is all there is to it.

We would use this class like this:
do {
  var echoClient = EchoClient(port: 3333, server: "127.0.0.1")
  try echoClient.start()
} catch let error {
  print("Error: \(error)")
}

In this code we connect to the server running on the localhost (127.0.0.1) on port 3333.

As we can see with this post and the post where we created the echo server, using IBM’s BlueSocket library makes it very easy to create client and server applications with Swift.  In the next post we will add concurrency to the echo server so it can communicate with multiple servers at the same time.