Creating a network client and server

Networking is a common requirement in modern applications. Phobos offers a module, std.socket, which provides a foundation for networked applications. We'll write a client and server pair to demonstrate how to use it. The client will send lines from standard input to the server. The server will accept any number of connections, say hello to new clients, and then echo back whatever it received.

How to do it…

We'll be building two separate applications: the client and the server.

Client

Let's create a client by executing the following steps:

  1. Create a Socket object.
  2. Connect to the server.
  3. Declare a buffer to hold the received data. The buffer must be preallocated to the maximum amount of data you want to handle in a single call to receive the data. Here, we'll use a static array of length 1024; it will have plenty of room to hold our message.
  4. Receive the hello message we sent in the server.
  5. Then, send each line of the message to the server, wait for the response, and print it out.

The code is as follows:

void main() {
    import std.socket, std.stdio;
    auto socket = new Socket(AddressFamily.INET, SocketType.STREAM);
    char[1024] buffer;
    socket.connect(new InternetAddress("localhost", 2525));
    auto received = socket.receive(buffer); 
    // wait for the server to say hello
    writeln("Server said: ", buffer[0 .. received]);
    foreach(line; stdin.byLine) {
      socket.send(line);
      writeln("Server said: ", buffer[0 .. socket.receive(buffer)]);
    }
}

Server

Let's create a server by executing the following steps:

  1. Create a Socket object.
  2. Bind it to the listening address.
  3. Call the listen() method to start listening.
  4. Create a list of connectedClients objects and a SocketSet object for multiplexing.
  5. Enter a main loop.
  6. Reset the SocketSets object.
  7. Add your listener and all connected clients to the readSet function.
  8. Call Socket.select() and pass your SocketSet objects to it.
  9. If any client is ready to read, receive its data with the socket’s receive method and reply to the client with the socket’s send method.
  10. If your listener was set in the readSet function, call accept to accept a new connection and send our welcome message.

The code is as follows:

void main() {
   import std.socket;
   auto listener = new Socket(AddressFamily.INET, SocketType.STREAM);
   listener.bind(new InternetAddress("localhost", 2525));
   listener.listen(10);
   auto readSet = new SocketSet();
   Socket[] connectedClients;
   char[1024] buffer;
   bool isRunning = true;
   while(isRunning) {
       readSet.reset();
       readSet.add(listener);
       foreach(client; connectedClients) readSet.add(client);
       if(Socket.select(readSet, null, null)) {
          foreach(client; connectedClients)
            if(readSet.isSet(client)) {
                // read from it and echo it back
                auto got = client.receive(buffer);
                client.send(buffer[0 .. got]);
            }
          if(readSet.isSet(listener)) {
             // the listener is ready to read, that means
             // a new client wants to connect. We accept it here.
             auto newSocket = listener.accept();
             newSocket.send("Hello!
"); // say hello
             connectedClients ~= newSocket; // add to our list
          }
       }
   }
}

How it works…

Phobos' std.socket module is a wrapper of the BSD socket API available on all major operating systems. The std.socket module is a thin wrapper. If you've used sockets in other languages, you'll feel at home with Phobos.

Clients simply connect and then send and receive data. The send and receive functions both take a buffer as their argument instead of returning a newly allocated array. This provides maximum performance by allowing you to reuse memory. Note that they may not use the whole buffer. These methods will wait until they can do something (unless you set the blocking property to false), and then they will do as much as they can immediately before returning. If only one byte is available on the network connection, receive will write that byte to the buffer and return the value; it won't wait to fill the buffer entirely. This lets you handle partial and variable size messages as soon as possible. Similarly, send may not send your whole message at once. A robust socket code should check the return value to ensure that all the data actually made it out.

On the server side, instead of connecting, you bind the Socket object to an address and then listen for connections. The argument to listen is the maximum number of clients to queue. This is typically a relatively small number, for example, 10.

There are several techniques to handle multiple connections. You can use threads, processes, or various event-based checks. Each technique has pros and cons in simplicity and speed. In the example, we used std.socket, an easy-to-use and portable function to multiplex several connections in an application. The select function doesn't scale well to large numbers of connections because it must loop through each one to check isSet, but for small numbers of connections, it performs adequately and is fairly simple to use.

The SocketSet class in std.socket is used to pass collections of sockets to select. You can create it outside your loop and then add your connections to it. Don't forget to add the listening socket too when it is ready to read. This means that a new client is trying to connect.

When select indicates that a socket is set in the SocketSet object, it is ready to be used immediately. You should handle the data as quickly as possible to minimize the wait time for other clients. The server simply continues these same steps in a loop until it is terminated.

There's more…

Here, we created a socket that uses TCP, the Internet protocol upon which many common application protocols are built, including HTTP for the Web, SMTP for e-mail, and more. You can also create UDP and Unix sockets with std.socket. To create a Unix socket (not available on Windows operating systems), pass AddressFamily.UNIX to the Socket constructor instead of AddressFamily.INET. Then, when connecting or binding, use the new UnixAddress instead of InternetAddress. Other than that, the API is the same as the TCP socket. Unix sockets only work with clients on the same computer, so they provide more security and speed than a TCP socket. However, they are only useful for specialized purposes. To use UDP—a connectionless protocol that is faster but less reliable than TCP—you can create the socket with SocketType.DGRAM and then use the sendTo and receiveFrom methods instead of send, receive, and connect.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.12.50