A server is a process that waits for any number of client connections with the purpose of servicing their requests. A server must listen for connections on a well-known name. In TCP/IP, this name is the IP address of the local interface and a port number. Every protocol has a different addressing scheme and therefore a different naming method. The first step in Winsock is to create a socket with either the socket() or WSASocket() call and bind the socket of the given protocol to its well-known name, which is accomplished with the bind() API call. The next step is to put the socket into listening mode, which is performed (appropriately enough) with the listen() API function. Finally, when a client attempts a connection, the server must accept the connection with either the accept() or WSAAccept() call. In the next few sections, we will discuss each API call that is required for binding, listening, and accepting a client connection.
Once the socket of a particular protocol is created, you must bind it to a well-known address. The bind() function associates the given socket with a well-known address. This function is declared as:
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
The first parameter, s, is the socket on which you want to wait for client connections.
The second parameter is of type struct sockaddr, which is simply a generic buffer. You must actually fill out an address buffer specific to the protocol you are using and cast that as a struct sockaddr when calling bind(). The Winsock header file defines the type SOCKADDR as struct sockaddr. We'll use this type throughout the chapter for brevity.
The third parameter is simply the size of the protocol-specific address structure being passed. For example, the following code illustrates how this is done on a TCP connection:
int port = 5150;
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
tcpaddr.sin_family = AF_INET;
tcpaddr.sin_port = htons(port);
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));
From the example, you'll see a stream socket being created, followed by setting up the TCP/IP address structure on which client connections will be accepted. In this case, the socket is being bound to the default IP interface by using a special address, INADDR_ANY, and occupies port number 5150. We could have specified an explicit IP address available on the system, but INADDR_ANY allows us to bind to all available interfaces on the system so that any incoming client connection on any interface (but the correct port) will be accepted by our listening socket. The call to bind() formally establishes this association of the socket with the local IP interface and port. On error, bind() returns SOCKET_ERROR. The most common error encountered with bind is WSAEADDRINUSE. With TCP/IP, the WSAEADDRINUSE error indicates that another process is already bound to the local IP interface and port number or that the IP interface and port number are in the TIME_WAIT state. If you call bind again on a socket that is already bound, WSAEFAULT will be returned.
The next piece of the equation is to put the socket into listening mode. The bind() function merely associates the socket with a given address. The API function that tells a socket to wait for incoming connections is listen(), which is defined as:
int listen(SOCKET s, int backlog);
Again, the first parameter is a bound socket.
The backlog parameter specifies the maximum queue length for pending connections. This is important when several simultaneous requests are made to the server. For example, let's say the backlog parameter is set to two. If three client requests are made at the same time, the first two will be placed in a “pending” queue so that the application can service their requests. The third connection request will fail with WSAECONNREFUSED. Note that once the server accepts a connection, the request is removed from the queue so that others can make a request. The backlog parameter is silently limited to a value that the underlying protocol provider determines. Illegal values are replaced with their nearest legal values. In addition, there is no standard provision for finding the actual backlog value.
The errors associated with listen() are fairly straightforward. By far the most common is WSAEINVAL, which usually indicates that you forgot to call bind() before listen(). Otherwise, it is possible to receive the WSAEADDRINUSE error on the listen() call as opposed to the bind() call. This error occurs most often on the bind() call.
Now you're ready to accept client connections. This is accomplished with the accept(), WSAAccept(), or AcceptEx() function. (AcceptEx(), an extended version of accept similar to other Win32 Ex versions) The prototype for accept is:
SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
The parameter s is the bound socket that is in a listening state.
The second parameter should be the address of a valid SOCKADDR_IN structure.
While addrlen should be a reference to the length of the SOCKADDR_IN structure. For a socket of another protocol, substitute the SOCKADDR_IN with the SOCKADDR structure corresponding to that protocol. A call to accept() services the first connection request in the queue of pending connections. When the accept() function returns, the addr structure contains the IPv4 address information of the client making the connection request, and the addrlen parameter indicates the size of the structure. In addition, accept() returns a new socket descriptor that corresponds to the accepted client connection. For all subsequent operations with this client, the new socket should be used. The original listening socket is still open to accept other client connections and is still in listening mode.
If an error occurs, INVALID_SOCKET is returned. The most common error encountered is WSAEWOULDBLOCK if the listening socket is in asynchronous or non-blocking mode and there is no connection to be accepted. Block, non-blocking, and other socket modes are covered in another chapter. Winsock 2 introduced the function WSAAccept(), which has the capability to conditionally accept a connection based on the return value of a condition function. At this point, we have described all the necessary elements to construct a simple Winsock TCP/IP server application.