Socket Server Functions
In this discussion, a server is a process that accepts connections on a specified port. While sockets, like named pipes, can be used for peer-to-peer communication, this distinction is convenient and reflects the manner in which two systems connect to one another.Unless specifically mentioned, the socket type will always be SOCK_STREAM in the examples. SOCK_DGRAM will be described later in this chapter.
Binding a Socket
The next step is to bind the socket to its address and endpoint (the communication path from the application to a service). The socket call, followed by the bind, is analogous to creating a named pipe. There is, however, no name to distinguish sockets on a given machine. A port number is used instead as the service endpoint. A given server can have multiple endpoints. The bind function is shown here.
int bind (
SOCKET s,
const struct sockaddr *saddr,
int namelen);
Parameters
s is an unbound SOCKET returned by socket.saddr, filled in before the call, specifies the protocol and protocol-specific information, as described next. Among other things, the port number is included in this structure.namelen is sizeof(sockaddr).The return value is normally 0 or SOCKET_ERROR in case of error. The sockaddr structure is defined as follows.
struct sockaddr {
u_short sa_family;
char sa_data [14];
};
typedef struct sockaddr SOCKADDR, *PSOCKADDR;
The first member, sa_family, is the protocol. The second member, sa_data, is protocol-specific. The Internet version of sockaddr is sockaddr_in.
struct sockaddr_in {
short sin_family; /* AF_INET */
u_short sin_port;
struct in_addr sin_addr; /* 4-byte IP addr */
char sin_zero [8];
};
typedef struct sockaddr_in SOCKADDR_IN,
*PSOCKADDR_IN;
Note the use of a short integer for the port number. The port number and other information must also be in the proper byte order, big-endian, so as to allow interoperability. The sin_addr member has a submember, s_addr, which is filled in with the familiar 4-byte IP address, such as 127.0.0.1, to indicate the system from which connections will be accepted. Normally, connections from any system will be accepted, so the value INADDR_ANY is used, although this symbolic value must be converted to the correct form, as shown in the next code fragment.The inet_addr function can be used to convert an IP address text string into the form required, so that you can initialize the sin_addr.s_addr member of a sockaddr_in variable, as follows:
sa.sin_addr.s_addr = inet_addr ("192.13.12.1");
A bound socket, with a protocol, port number, and IP address, is sometimes said to be a named socket.
Putting a Bound Socket into the Listening State
listen makes a server socket available for client connection. There is no analogous named pipe function.
int listen (SOCKET s, int nQueueSize);
nQueueSize indicates the number of connection requests you are willing to have queued at the socket. There is no upper bound in Winsock Version 2.0, but Version 1.1 has a limit of SOMAXCON (which is 5).
Accepting a Client Connection
Finally, a server can wait for a client to connect, using the accept function, which returns a new connected socket that is used in the I/O operations. Notice that the original socket, now in the listening state, is used solely as an accept parameter and is not used directly for I/O.accept blocks until a client connection request arrives, and then it returns the new I/O socket. It is possible, but out of scope, to make a socket be nonblocking, and the server (Program 12-2) uses a separate accepting thread to allow for nonblocking servers.
SOCKET accept (
SOCKET s,
LPSOCKADDR lpAddr,
LPINT lpAddrLen);
Parameters
s, the first argument, is the listening socket. Preceding socket, bind, and listen calls are required to put the socket into the listening state.lpAddr points to a sockaddr_in structure that gives the address of the client system.lpAddrLen points to a variable that will contain the length of the returned sockaddr_in structure. It is necessary to initialize this variable to sizeof (struct sockaddr_in) before the accept call.
Disconnecting and Closing Sockets
Disconnect a socket using shutdown (s, how). The how argument is either 1 or 2 to indicate whether sending only (1) or both sending and receiving (2) are to be disconnected. shutdown does not free resources associated with the socket, but it does assure that all data is sent or received before the socket is closed. Nonetheless, an application should not reuse a socket after calling shutdown.Once you are finished with a socket, you can close it with the closesocket (SOCKET s) function. The server first closes the socket created by accept, not the listening socket. The server should not close the listening socket until the server shuts down or will no longer accept client connections. Even if you are treating a socket as a HANDLE and using ReadFile and WriteFile, CloseHandle alone will not destroy the socket; use closesocket.
Example: Preparing for and Accepting a Client Connection
The following code fragment shows how to create a socket and then accept client connections.This example uses two standard functions, htons ("host to network short") and htonl ("host to network long") that convert integers to big-endian form, as required by IP.The server port can be any short integer, but user-defined services are normally in the range 10255000. Lower port numbers are reserved for well-known services such as telnet and ftp, while higher numbers are likely to be assigned to other standard services.
struct sockaddr_in SrvSAddr; /* Server address struct. */
struct sockaddr_in ConnectAddr;
SOCKET SrvSock, sockio;
...
SrvSock = socket (AF_INET, SOCK_STREAM, 0);
SrvSAddr.sin_family = AF_INET;
SrvSAddr.sin_addr.s_addr = htonl (INADDR_ANY);
SrvSAddr.sin_port = htons (SERVER_PORT);
bind (SrvSock, (struct sockaddr *) &SrvSAddr,
sizeof SrvSAddr);
listen (SrvSock, 5);
AddrLen = sizeof (ConnectAddr);
sockio = accept (SrvSock,
(struct sockaddr *) &ConnectAddr, &AddrLen);
... Receive requests and send responses ...
shutdown (sockio);
closesocket (sockio);
