[Previous] [Contents] [Next]

Lesson 3:
A Winsock TCP server

 

Introduction

Now that we’ve got a TCP client, we’ll create a server to which the client can connect. It waits a client connect, waits for it to send some data, and responds with a pre-defined message. There are two major differences between the client code and the server code. The server has to bind itself to an interface on the computer. Then, instead of connecting to a computer, it puts itself into a listening mode and accepts connections. I‘ll discuss all of these topics when we encounter them in the code. Note: This is a single-threaded server, it can only handle one client at a time. A multi-threaded server however, can handle multiple clients at a time.

 

Initializing and creating the socket

The first part of our code is almost exactly the same as the client’s code.

 

// author:          frenchwhale (http://frenchwhales_site.tripod.com)
//                  for the WinSock tutorial
//
// file name:       wsserver.c
//
// description:     A TCP server that uses Winsock to
//                  listen for connections, receives data,
//                  and answers with a pre-specified message
// Copyright 2002 frenchwhale

 

#define MESSAGE         "Hello, Client!\n"

 

#include <winsock.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int main(int argc, int **argv)

{

   WSADATA wsda;             // Structure to store information returned from

                        // WSAStartup

 

   char szRepMessage[80];               // Store the reply message

   int iMessageLen;

 

   int ret;                             // Return values

 

   char szInBuffer[128];

   int iBufferLen;

 

   int iPort, iAddrLen;

 

   SOCKET sListen, sClient;                    // Our TCP socket handle

   SOCKADDR_IN addr,            // The local interface

               remote_addr;     // The address of the connecting host

 

   // Check arguments

   if(argc != 2 || (argc==2 && strcmp((char *) &argv[1][0], "/?")==0))

   {

      printf("wsserver port\n");

      printf("   port: the listening port\n");

      exit(1);

   }

 

   // Get the remote port

   iPort = atoi((char *) &argv[1][0]);

 

   if(iPort<0 || iPort>65563)   // 0 < iPort < 65563

   {

      printf("Invalid port number! (%s)\n", argv[2]);

      exit(1);

   }

 

   strcpy(szRepMessage, MESSAGE);

   iMessageLen = strlen(szRepMessage);

 

                         

   // Load version 1.1 of Winsock

 

   WSAStartup(MAKEWORD(1,1), &wsda);

 

   // Create a TCP socket

   printf("Creating socket...");

   sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

 

   // Error?

   if(sListen == SOCKET_ERROR)

   {

      printf("Error\nCall to socket(AF_INET, SOCK_STREAM, IPPROTO_IP); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

   printf("OK\n");

 

 

I don’t think you’ll have many difficulties understanding the code. First, it checks and initializes the command-line arguments, then it initializes Winsock and creates a socket. (If you are unfamiliar with any of these functions, see Lesson 2).

 

 

[Fig. 3-1, a multihomed computer]

 

Binding, listening and accepting

A server’s main purpose is to listen for client connections and process the client’s commands. In order to listen, you have to bind your listening socket to an interface. Often, a computer has only one interface that it uses to access the network, if a computer is connected to multiple networks (like a router), it is called a multihomed computer. If you want to listen for connections from one of the network, you have to bind it to its corresponding interface. When you bind your listening socket, you specify the IP-address of the interface. If you want to listen on all the interfaces, specify INADDR_ANY as the IP-address, remember that you have to specify the address it in network byte order so it is necessary to call htonl on INADDR_ANY. When a client connects to a server, he needs to know which port he should connect to just like the server needs to know which port it should be listening to. Both the address and the port are specified in a SOCKADDR_IN structure, in the same way as you specify the server’s address and port number when connecting with a client.

The bind function is declared as follows:

 

int bind(SOCKET s, struct sockaddr FAR *addr, int addrlen);

 

Its declaration is pretty self-explaining, s is the socket handle, addr is a pointer to the address it should be bound to and finally the addrlen argument is the length of the sockaddr structure supplied as the second argument.

Now that you have your listening socket bound, you have to put it into listening mode with the function listen:

 

int listen(SOCKET s, int backlog);

 

The s argument is the socket you want to put in listening mode, the second argument is the backlog. The backlog is the number of unanswered (unaccepted) connections you can have waiting to be answered.

Once you’ve put the socket into listening mode, you have to call the accept function in order to accept and take care of a client connection. If there aren’t any connections waiting, accept will block until someone connects. The function is declared like this:

 

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

 

Where s is the socket handle, addr is a pointer to a sockaddr structure, in which the client’s IP-address will be stored. addrlen is a pointer to an int holding the number of bytes allocated for the sockaddr structure, upon return, the int will hold the number of bytes actually used to store the sockaddr structure. The sockaddr handle that the function returns is a new socket handle used to communicate with this particular client, the socket handle passed as an argument is called the listening socket, you’ll only use it when calling bind, listen or accept. The new socket handle however, is used to call send or recv to communicate with the client. When you have called accept, you use the returned socket handle to send or receive data to and from the client and when you’re ready, you call accept to wait for new clients.

       Now we’re ready to look at some code:

 

    printf("Binding socket to port %d...", iPort);

 

   addr.sin_family = AF_INET;

   addr.sin_port = htons(iPort);

   addr.sin_addr.s_addr = htonl(INADDR_ANY);          // Listen on any interface

 

   ret = bind(sListen, (struct sockaddr *) &addr, sizeof(addr));

 

   // Error?

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to bind(sListen, (struct sockaddr *) &addr, sizeof(addr)); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

   printf("OK\n");

 

   printf("Putting socket into listening mode...");

 

   ret = listen(sListen, 10);        // Backlog 10

 

   // Error?

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to listen(sListen, 10); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

   printf("OK\n");

 

   printf("Waiting for connections (Press Ctrl-C to exit)...");

 

   iAddrLen = sizeof(remote_addr);

 

   sClient = accept(sListen, (struct sockaddr *) &remote_addr, &iAddrLen);

 

   // Error?

   if(sClient == SOCKET_ERROR)

   {

      printf("Error\nCall to accept(sListen, (struct sockaddr *) remote_addr, sizeof(remote_addr)); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

      The only thing this code does is: binding the socket, checking for errors, putting it into listening mode, checking for errors, calling accept to wait for a connection and checking for errors. Notice that the only way to close the server is to press Ctrl-C, this is because when you call accept, the program’s sole task is to wait for client connections, during this time it doesn’t receive any user input. This problem can be corrected using multi-threaded servers with one controlling thread which handles user input, one listening thread which calls accept in a while loop and finally one thread per client connected. A multi-threaded server also allows multiple clients to connect and exchange information simultaneously.

 

Exchange of data

The next part is by far the easiest part in this lesson, it displays the IP-address of the client, receives data from the client and responds with its own pre-specified message.

 

   printf("%s connected\n",

          inet_ntoa(remote_addr.sin_addr));

 

   // Receive data

 

   printf("Waiting for a response...");

   ret = recv(sClient, szInBuffer, sizeof(szInBuffer), 0);

 

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to recv(sClient, szInBuffer, sizeof(szInBuffer), 0); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

   printf("Response recieved\n");

 

   iBufferLen = ret;         // recv() returns the number of bytes read

 

   szInBuffer[iBufferLen] = '\0'; // convert to cstring

 

   printf("Data recieved:\n%s\n", szInBuffer);

 

   // Ready to send data

 

   printf("Sending reply...");

   ret = send(sClient, szRepMessage, iMessageLen, 0);

 

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to send(sClient, szRepMessage, iMessageLen, 0) failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

   printf("OK\n");

 

   printf("Closing client socket...");

   closesocket(sClient);

   printf("OK\n");

 

   return 0;

}

 

First, the code converts the client’s IP-address from a 32-bit INADDR into a string in the form aaa.bbb.ccc.ddd and prints it. After that, the program waits for the client to send dome data, note that when you call recv, you have to pass the socket handle returned from accept (sClient) in order for it to work. When the data is received, the server adds a ‘\0’ and prints it and then it sends its own pre-specified message. The client socket is then closed and the program returns.

 

Client/Server interaction

Now that we have both a client and a server, we can start the server and let the client connect to it. This can be done with only one host using the special IP-address 127.0.0.1 or the hostname localhost, these addresses and hostnames are called loopback addresses, everything you send to them is looped back to you, so if you set up a server on your computer, you can connect to it using 127.0.0.1 as the server’s address.

 

The telnet program

Windows includes a simple client application called telnet, although it’s originally destined to be used with the protocol called telnet (port 23), a protocol used to access other computers on a network, but this application can be used to connect to any other port. You can use this program to figure out how different protocols work or to test your own servers. To use it, choose Run from the Start menu and write:

 

     telnet [server] [port]

 

Where server is the server’s IP-address and port is the port you wish to connect to, when you start the program, it will connect to the server at the specified port. If you leave the parameters out, you will have to specify the server and port in the program when you wish to connect.

 

Source codes for Lesson 3:

n       WSSERVER.C – Listens for connections on the port specified as the argument

 

[Previous] [Contents] [Next]