Setting Up Client and Server Sockets

Most Windows Sockets applications are asymmetrical; that is, there are generally two components to the network application—a client and a server. Frequently, these components are isolated into separate programs. Sometimes, these components are integrated into a single application (such as our sample application). When both the client and server components of a networking application are integrated, the application is generally referred to as a "peer" application. Both the client and the server components go through different procedures to ready themselves for networking by making a number of Windows Sockets API calls. The following state diagrams illustrate the state transitions for setting up client- and server-side socket applications:

Setting up a server-side stream-based application

Setting up a client-side stream socket-based application

The socket() call creates an endpoint for both client- and server-side application communication. When calling socket(), the application specifies the protocol family and either the socket type (stream or datagram), or the specific protocol which it expects to use (for example, TCP). Both the client- and server-side of a network application use the socket() call to define their respective endpoints. socket() returns a socket descriptor, an integer which uniquely identifies the socket created within Windows Sockets.

Server-side connection setup

Once the socket is created, the server-side associates the freshly created socket descriptor and a local endpoint address via the bind() API. The local endpoint address is comprised of two pieces of data, the IP address and the port ID for the socket. The local IP address is used to determine which interfaces the server application will accept connection requests on; the port ID identifies the TCP or UDP port on which connections will be accepted. It is for these two values that the network byte-ordering routines (htonl(), htons(), etc.) were created. These values must always be represented in network byte order.

Alternatively, an application may substitute the value INADDR_ANY in place of a valid local IP address, and the system will accept incoming requests on any local interface, and will send requests on the "most appropriate" local interface. In fact, most server applications do exactly this. To associate a socket with any valid system port, provide a value of 0 for the .sin_port member of the sockaddr_in structure. This will select an unused system port between 1025 and 5000. As mentioned before, most server applications listen on a specified port, and client applications use this mechanism to obtain an unused local port. Once an application uses this mechanism to obtain a valid local port, it may call getsockname() to determine the port the system selected.

The listen() API sets up a connection queue. It accepts only two parameters, the socket descriptor and the queue length. The queue length identifies the number of outstanding connection requests that will be allowed to queue up on a particular port/address pair, before denying service to incoming connections.

The accept() API completes a stream-based server-side connection by accepting an incoming connection request, assigning a new socket to the connection, and returning the original socket to the listening state. The new socket is returned to the application, and the server can begin interacting with the client over the network.

Client-side connection setup

From the client's perspective, the application also creates a socket using the socket() call. The bind() command is used to bind the socket to a locally specified endpoint address which the server will use to transmit data back to the client. Once a local endpoint association is made, the connect() API establishes a connection with a remote endpoint. This routine initiates the network connection between the two systems. Once the connection is made, the client can begin interaction with the server on the network.

Although the client may choose to call bind(), it is not necessary to do so. Calling connect() with an unbound socket will simply force the system to choose an IP interface and unique port ID and mark the socket as bound. Most client-side applications neglect the bind() call as there are rarely specific requirements for a particular local interface/port ID pair.

The following code fragments create and connect a pair of stream-based sockets using the API flow outlined above:

Server-side (connection-oriented)


#define            SERVICE_PORT        5001

SOCKET             srv_sock,        cli_sock;
struct sockaddr_in        srv_addr,        cli_addr;
char             buf[MAX_BUF_LEN];

/* Create the server-side socket */

srv_sock=socket(AF_INET,SOCK_STREAM,0);
if (srv_sock==INVALID_SOCKET){
    sprintf(buf,"Windows Sockets error %d: Couldn't create socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

srv_addr.sin_family=AF_INET;
srv_addr.sin_addr.s_addr=INADDR_ANY;
srv_addr.sin_port=SERVICE_PORT;        /* specific port for server to listen on */

/* Bind socket to the appropriate port and interface (INADDR_ANY) */

if (bind(srv_sock,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR){
    sprintf(buf,"Windows Sockets error %d: Couldn't bind socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);    
    shutdown_app();
}

/* Listen for incoming connections */

if (listen(srv_sock,1)==SOCKET_ERROR){

    sprintf(buf,"Windows Sockets error %d: Couldn't set up listen on socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

/* Accept and service incoming connection requests indefinitely */

for ( ; ; ) {
    cli_sock=accept(srv_sock,(LPSOCKADDR)&cli_addr,&addr_len);
    if (cli_sock==INVALID SOCKET){
    
        sprintf(buf,"Windows Sockets error %d: Couldn't accept incoming \
             connection on socket.",WSAGetLastError());
        MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);    
        shutdown_app();
    }
    .
    /* Client-server network interaction takes place here */
    .
    closesocket(cli_sock);
}

Client-side (connection-oriented)


/* Static IP address for remote server for example. In reality, this would be    
   specified as a hostname or IP address by the user */

#define    SERVER        "131.107.1.121"        

struct     sockaddr_in    srv_addr,cli_addr;
LPSERVENT            srv_info;
LPHOSTENT            host_info;
SOCKET            cli_sock;
.
/* Set up client socket */

cli_sock=socket(PF_INET,SOCK_STREAM,0);

if (cli_sock==INVALID_SOCKET){
    
    sprintf(buf,"Windows Sockets error %d: Couldn't create socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

cli_addr.sin_family=AF_INET;
cli_addr.sin_addr.s_addr=INADDR_ANY;        
cli_addr.sin_port=0;                /* no specific port req'd */

/* Bind client socket to any local interface and port */

if (bind(cli_sock,(LPSOCKADDR)&cli_addr,sizeof(cli_addr))==SOCKET_ERROR){
    
    sprintf(buf,"Windows Sockets error %d: Couldn't bind socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

/* Get the remote port ID to connect to for FTP service */

srv_info=getservbyname("ftp","tcp");

if (srv_info== NULL) {

    sprintf(buf,"Windows Sockets error %d: Couldn't resolve FTP service port.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(SERVER); 
srv_addr.sin_port=srv_info->s_port;

/* Connect to FTP server at address SERVER */

if (connect(cli_sock,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR){

    sprintf(buf,"Windows Sockets error %d: Couldn't connect socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}
/* Client-server network interaction takes place here */

Although the above fragment makes use of the bind() API, it would be just as effective to skip over this call as there are no specific local port ID requirements for this client. The only advantage that bind() offers is the accessibility of the port which the system chose via the .sin_port member of the cli_addr structure which will be set upon success of the bind() call.

For connectionless, or "datagram" sockets, Windows Sockets usage is a little simpler. Since the communication in datagram sockets is connectionless, it is not necessary to use the APIs necessary for creating a connection, namely: connect(), listen(), and accept(). The flow of Windows Sockets APIs that a typical connectionless client-server pair will generally traverse follows:

Setting up a server-side datagram-based application

Setting up a client-side stream-based application

As pictured above, a client may choose to connect() the datagram socket for convenience of multiple sends to the remote endpoint. Connecting a datagram socket will cause all sends to go to the connected address, and any datagrams received from a remote address different than the connected address are discarded by the system. Generally, connectionless clients use the sendto() API to transmit application data. The sendto() call requires that the destination's endpoint address be specified with every call to the API. By connecting a datagram socket, a client sending a large amount of data to the same destination can simply use the send() API to transmit, without having to specify a remote endpoint with every call, and the client need not concern itself with the possibility of receiving unwanted data from other hosts. Depending on the type of application you are developing, and the Windows Sockets implementation below your application, connecting datagram sockets may improve performance of your application.

Some sample code illustrates how the TFTP protocol (a connectionless protocol for file transfer) client and server might be implemented over Windows Sockets:

Server-side (connectionless)


SOCKET             srv_sock;
struct sockaddr_in        srv_addr;

.
.
/* Create server socket for connectionless service */

srv_sock=socket(PF_INET,SOCK_DGRAM,0);

if (srv_sock==INVALID_SOCKET){

    sprintf(buf,"Windows Sockets error %d: Couldn't create socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

/* Resolve TFTP service port to listen on */

srv_info=getservbyname("tftpd","udp");

if (srv_info== NULL) {

    sprintf(buf,"Windows Sockets error %d: Couldn't resolve TFTPd service port.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;   /* Allow the server to accept connections                         /* over any interface */
srv_addr.sin_port=srv_info->s_port;

/* Bind remote server's address and port */

if (bind(srv_sock,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR){
    
    sprintf(buf,"Windows Sockets error %d: Couldn't bind socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

/* Client-server network interaction takes place here */

Client-side (connectionless)


/* Static IP address for remote server for example. In reality, this would be    
   specified as a hostname or IP address by the user */

#define    SERVER        "131.107.1.121"        

struct sockaddr_in        srv_addr,cli_addr;
LPSERVENT            srv_info;
LPHOSTENT            host_info;
SOCKET            cli_sock;

.
.
/* Create client-side datagram socket */

cli_sock=socket(PF_INET,SOCK_DGRAM);
if (cli_sock==INVALID_SOCKET){
    
    sprintf(buf,"Windows Sockets error %d: Couldn't create socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

cli_addr.sin_family=AF_INET;
cli_addr.sin_addr.s_addr=INADDR_ANY;        
cli_addr.sin_port=0;                /* no specific local port req'd */

/* Bind local socket */
if (bind(cli_sock,(LPSOCKADDR)&cli_addr,sizeof(cli_addr))==SOCKET_ERROR){
    
    sprintf(buf,"Windows Sockets error %d: Couldn't bind socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

/* Resolve port information for TFTP service */

srv_info=getservbyname("tftp","udp");
if (srv_info== NULL) {

    sprintf(buf,"Windows Sockets error %d: Couldn't resolve TFTP service port.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(SERVER);
srv_addr.sin_port=srv_info->s_port;

if (connect(cli_sock,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR){
    sprintf(buf,"Windows Sockets error %d: Couldn't connect socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_app();
}
/* Client-server network interaction takes place here */

Since a connectionless client (such as TFTP) will undoubtedly be doing successive transmissions with the server (especially during long transfers), we have chosen to connect the socket, allowing the use of the send() and recv() APIs rather than sendto() and recvfrom(). The use of connect() with datagram sockets is purely optional.