Winsock Basics

éÓÔÏÞÎÉË: Anthony Jones and Jim Ohlund "Windows Network Programming" - published by Microsoft Press, 1999

Initializing Winsock

Every Winsock application must load the appropriate version of the Winsock DLL. If you fail to load the Winsock library before calling a Winsock function, the function will return a SOCKET_ERROR and the error will be WSANOTINITIALISED. Loading the Winsock library is accomplished by calling the WSAStartup function, which is defined as

intšWSAStartup(
  ššššWORDš wVersionRequested,
  ššššLPWSADATAš lpWSAData  
);  

The wVersionRequested parameter is used to specify the version of the Winsock library you want to load. The high-order byte specifies the minor version of the requested Winsock library, while the low-order byte is the major version. You can use the handy macro MAKEWORD(x, y), in which x is the high byte and y is the low byte, to obtain the correct value for wVersionRequested.

The lpWSAData parameter is a pointer to a LPWSADATA structure that WSAStartup fills with information related to the version of the library it loads:

typedefšstructšWSADataš  
{  šššš
   WORDšššššššššššwVersion;  šššš
   WORDšššššššššššwHighVersion;  šššš
   charšššššššššššszDescription[WSADESCRIPTION_LENš+š1];š  šššš
   charšššššššššššszSystemStatus[WSASYS_STATUS_LENš+š1];  šššš
   unsignedšshortšiMaxSockets;  šššš
   unsignedšshortšiMaxUdpDg;  šššš
   charšFARš*šššššlpVendorInfo;  
}šWSADATA,šFARš*šLPWSADATA;š  

WSAStartup sets the first field, wVersion, to the Winsock version you will be using. The wHighVersion parameter holds the highest version of the Winsock library available. Remember that in both of these fields, the high-order byte represents the Winsock minor version, while the low-order byte is the major version. The szDescription and szSystemStatus fields are set by the particular implementation of Winsock and aren't really useful. Do not use the next two fields, iMaxSockets and iMaxUdpDg. They are supposed to be the maximum number of concurrently open sockets and the maximum datagram size; however, to find the maximum datagram size you should query the protocol information through WSAEnumProtocols. The maximum number of concurrent sockets isn't some magic number-it depends more on how much physical memory is available. Finally, the lpVendorInfo field is reserved for vendor-specific information regarding the implementation of Winsock. This field is not used on any Win32 platforms.

Table 1 lists the latest versions of Winsock that the various Microsoft Windows platforms support. What's important to remember is the difference between major versions. Winsock 1.x does not support many of the advanced Winsock features detailed in this section. Additionally, for applications using Winsock 1, the include file Winsock.h is necessary; otherwise, for Winsock 2, Winsock2.h should be included.

Table 1. Supported Winsock versions

Platform Winsock Version
Windows 95 1.1 (2.2)
Windows 98 2.2
Windows NT 4.0 2.2
Windows 2000 2.2
Windows CE 1.1

Note that even though a platform supports Winsock 2, you do not have to request the latest version. That is, if you want to write an application that is supported on a majority of platforms, you should write it to the Winsock 1.1 specification. This application will run perfectly well on Windows NT 4.0 because all Winsock 1.1 calls are mapped through the Winsock 2 DLL. Also, if a newer version of the Winsock library becomes available for a platform that you use, it is often in your best interest to upgrade. These new versions contain bug fixes, and your old code should run without a problem-at least theoretically. In some cases, the behavior of the Winsock stack is different from what the specification defines. As a result, many programmers write their applications according to the behavior of the particular platform they are targeting instead of the specification. For example, under Windows NT 4.0, when a program is using the asynchronous window event model, an FD_WRITE is posted after every successful send or WSASend to indicate that you can write data. However, the specification says that an FD_WRITE is posted when the system is able to send data, such as when the application starts, and that a posted FD_WRITE means you should keep writing until you receive the error WSAEWOULDBLOCK. In fact, after the system sends all pending data and can process more send and WSASend calls, it will post an FD_WRITE event to your application window, at which time you can resume writing data to the network (Knowledge Base Article Q186245). This problem has been fixed in Service Pack 4 for Windows NT 4.0 as well as in Windows 2000.

For the most part, however, when writing new applications you will load the latest version of the Winsock library currently available. Remember that if, for example, Winsock 3 is released, your application that loads version 2.2 should run as expected. If you request a Winsock version later than that which the platform supports, WSAStartup will fail. Upon return, the wHighVersion of the WSADATA structure will be the latest version supported by the library on the current system.

Error Checking and Handling

We'll first cover error checking and error handling, as they are vital to writing a successful Winsock application. It is actually common for Winsock functions to return an error; however, many times the error is not critical and communication can still take place on that socket. The most common return value for an unsuccessful Winsock call is SOCKET_ERROR, although this is certainly not always the case. When covering each API call in detail, we'll point out the return value corresponding to an error. The constant SOCKET_ERROR actually is -1. If you make a call to a Winsock function and an error condition occurs, you can use the function WSAGetLastError to obtain a code that indicates specifically what happened. This function is defined as

intšWSAGetLastErrorš(void);  

A call to the function after an error occurs will return an integer code for the particular error that occurred. These error codes returned from WSAGetLastError all have predefined constant values that are declared in either Winsock.h or Winsock2.h, depending on the version of Winsock. The only difference between the two header files is that Winsock2.h contains more error codes for some of the newer API functions and capabilities introduced in Winsock 2. The constants defined for the various error codes (with #define directives) generally begin with WSAE.

Connection-Oriented Protocols

In this first section, we'll cover the Winsock functions necessary for both receiving connections and establishing connections. We'll first discuss how to listen for client connections and explore the process for accepting or rejecting a connection. Then we'll describe how to initiate a connection to a server. Finally, we will discuss how data is transferred in a connection session.

Server API Functions

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 bind a socket of the given protocol to its wellknown 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 the WSAAccept call. In the next few sections, we will discuss each API call that is required for binding and listening and for accepting a client connection. Figure illustrates the basic calls a server and a client must perform in order to establish a communication channel.

bind

Once the socket of a particular protocol is created, you must bind the socket 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:

SOCKETšššššššššššššššs;šššš  
structšsockaddr_inššštcpaddr;  
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));  

If the structure sockaddr_in looks mysterious to you, consult the TCP/IP addressing section in Chapter 6. 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 on port number 5150. The call to bind formally establishes this association of the socket with the IP interface and port.

On error, bind returns SOCKET_ERROR. The most common error encountered with bind is WSAEADDRINUSE. When you're using 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.

listen

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 2. 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 connection request is removed from the queue so that others can make a request. The backlog parameter is silently limited to a value determined by the underlying protocol provider. Illegal values are replaced with their nearest legal values. Additionally, 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.

accept and WSAAccept

Now you're ready to accept client connections. This is accomplished with either the accept or the WSAAccept function. The prototype for accept is

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

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 IP address information of the client making the connection request, while the addrlen parameter indicates the size of the structure. Additionally, 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 used to accept other client connections and is still in listening mode.

Winsock 2 introduced the function WSAAccept, which has the ability to conditionally accept a connection based on the return value of a condition function. The prototype for this new function is

SOCKETšWSAAccept(
  šššš SOCKETš s,
  šššš structšsockaddršFARš*š addr,
  šššš LPINTš addrlen,
  šššš LPCONDITIONPROCš lpfnCondition,
  šššš DWORDš dwCallbackData
);  

The first three parameters are the same as the Winsock 1 version of accept. The lpfnCondition argument is a pointer to a function that is called upon a client request. This function determines whether to accept the client's connection request. The prototype for this function is

intšCALLBACKšConditionFunc(
  ššššLPWSABUFšlpCallerId,
  ššššLPWSABUFšlpCallerData,
  ššššLPQOSšlpSQOS,
  ššššLPQOSšlpGQOS,
  ššššLPWSABUFšlpCalleeId,
  ššššLPWSABUFšlpCalleeData,
  ššššGROUPšFARš*šg,
  ššššDWORDšdwCallbackData
);  

The lpCallerId parameter is a value parameter that contains the address of the connecting entity. The WSABUF structure is commonly used by many Winsock 2 functions. It is declared as

typedefšstructš__WSABUF{
š  ššššu_longšššššlen;
š  ššššcharšFARš*šbuf;
} WSABUF,šFARš*šLPWSABUF;  

Depending on its use, the len field refers either to the size of the buffer pointed to by the buf field or to the amount of data contained in the data buffer buf.

For lpCallerId, the buf pointer points to an address structure for the given protocol on which the connection is made. To correctly access the information, simply cast the buf pointer to the appropriate SOCKADDR type. In the case of TCP/IP, this is, of course, a SOCKADDR_IN structure that will contain the IP address of the client making the connection. Most network protocols can be expected to support caller ID information at connection-request time.

The lpCallerData parameter contains any connection data sent by the client along with the connection request. If caller data was not specified, this parameter is NULL. Be aware that most network protocols, such as TCP, do not support connect data. Whether a protocol supports connect or disconnect data can be determined by consulting its entry in the Winsock catalog with the WSAEnumProtocols function. See Chapter 5 for the specifics.

The next two parameters, lpSQOS and lpGQOS, specify any quality of service (QOS) parameters that are being requested by the client. Both parameters reference a QOS structure that contains information regarding bandwidth requirements for both sending and receiving data. If the client is not requesting QOS, these parameters will be NULL. The difference between these two parameters is that lpSQOS refers to a single connection, while lpGQOS is used for socket groups. Socket groups are not implemented or supported in Winsock 1 or 2. (See Chapter 12 for further details about QOS.)

The lpCalleeId is another WSABUF structure containing the local address to which the client has connected. Again, the buf field of this structure points to a SOCKADDR object of the appropriate address family. This information is useful in the event that the server is running on a multihomed machine. Remember that if a server binds to the address INADDR_ANY, connection requests are serviced on any network interface. This parameter will contain the specific interface on which the connection occurred.

The lpCalleeData parameter is the complement of lpCallerData. The lpCalleeData parameter points to a WSABUF structure that the server can use to send data back to the client as a part of the connection request process. If the service provider supports this option, the len field indicates the maximum number of bytes the server can send back to the client as a part of this connection request. In this case, the server would copy any number of bytes up to this amount into the buf portion of the WSABUF structure and update the len field to indicate the number of bytes being transferred. If the server does not want to return any connect data, the conditional accept function should set the len field to 0 before returning. If the provider does not support connect data, the len field will be 0. Again, most protocols do not support data exchange upon accept. In fact, none of the currently supported protocols on any Win32 platform support this feature.

Once the server has processed parameters passed into the conditional function, the server must indicate whether to accept, reject, or defer the client's connection request. If the server is accepting the connection, the conditional function should return CF_ACCEPT. Upon rejection, the function should return CF_REJECT. If for some reason the decision cannot be made at this time, CF_DEFER can be returned. When the server is prepared to handle this connection request, it should call WSAAccept. Note that the condition function runs in the same thread as the WSAAccept function and should return as soon as possible. Also be aware that for the protocols supported by the current Win32 platforms, the conditional accept function does not imply that the client's connection request is delayed until a value is returned from this conditional function. In most cases, the underlying network stack has already accepted the connection at the time the conditional accept function is called. If the value CF_REJECT is returned, the underlying stack simply closes the connection. We won't go into the detailed usage of the conditional acceptance function now, as this information will be more useful in Chapter 12.

If an error occurs, INVALID_SOCKET is returned. The most common error encountered is WSAEWOULDBLOCK if the listening socket is in asynchronous or nonblocking mode and there is no connection to be accepted. When a conditional function returns CF_DEFER, WSAAccept returns the error WSATRY_AGAIN. If the condition function returns CF_REJECT, the WSAAccept error is WSAECONNREFUSED.