Connection-Oriented Communication

Connection-Oriented Communication

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

In IP, connection-oriented communication is accomplished through the TCP/IP protocol. TCP provides reliable error-free data transmission between two computers. When applications communicate using TCP, a virtual connection is established between the source computer and the destination computer. Once a connection is established, data can be exchanged between the computers as a two-way stream of bytes.

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 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. Figure 1-1 illustrates the basic calls a server and a client must perform in order to establish a communication channel.

Figure 1-1 Winsock basics for server and client

Binding

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:

SOCKET               s;     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));

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.

Listening

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.

Accepting Connections

Now you're ready to accept client connections. This is accomplished with the accept, WSAAccept, or AcceptEx function. (AcceptEx, an extended version of accept, is described in detail in Chapter 6.) 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 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 Chapter 5. Winsock 2 introduced the function WSAAccept, which has the capability to conditionally accept a connection based on the return value of a condition function. Chapter 10 will describe WSAAccept in greater detail.

At this point, we have described all the necessary elements to construct a simple Winsock TCP/IP server application. The following program fragment demonstrates how to write a simple server that can accept one TCP/IP connection. We did not perform any error checking on the calls to make reading the code less confusing. You will find a complete version of this application in a file named TCPSERVER on the companion CD.

#include <winsock2.h> void main(void) {    WSADATA              wsaData;    SOCKET               ListeningSocket;    SOCKET               NewConnection;    SOCKADDR_IN          ServerAddr;    SOCKADDR_IN          ClientAddr;    int                  Port = 5150;        // Initialize Winsock version 2.2    WSAStartup(MAKEWORD(2,2), &wsaData);           // Create a new socket to listen for client connections.         ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);          // Set up a SOCKADDR_IN structure that will tell bind that we       // want to listen for connections on all interfaces using port       // 5150. Notice how we convert the Port variable from host byte       // order to network byte order.          ServerAddr.sin_family = AF_INET;       ServerAddr.sin_port = htons(Port);           ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);          // Associate the address information with the socket using bind.          bind(ListeningSocket, (SOCKADDR *)&ServerAddr,        sizeof(ServerAddr));    // Listen for client connections. We used a backlog of 5, which    // is normal for many applications.       listen(ListeningSocket, 5);     // Accept a new connection when one arrives.       NewConnection = accept(ListeningSocket, (SOCKADDR *)                            &ClientAddr,&ClientAddrLen));    // At this point you can do two things with these sockets. Wait    // for more connections by calling accept again on ListeningSocket    // and start sending or receiving data on NewConnection. We will    // describe how to send and receive data later in the chapter.    // When you are finished sending and receiving data on the    // NewConnection socket and are finished accepting new connections    // on ListeningSocket, you should close the sockets using the    // closesocket API. We will describe socket closure later in the     // chapter.       closesocket(NewConnection);       closesocket(ListeningSocket);    // When your application is finished handling the connections,     // call WSACleanup.       WSACleanup(); }

Now that you understand how to construct a server that can receive a client connection, we will describe how to construct a client.

Client API Functions

The client is much simpler and involves fewer steps to set up a successful connection. There are only three steps for a client:

  1. Create a socket.

  2. Set up a SOCKADDR address structure with the name of server you are going to connect to (dependent on underlying protocol). For TCP/IP, this is the server's IP address and port number its application is listening on.

  3. Initiate the connection with connect or WSAConnect.

You already know how to create the socket and construct a SOCKADDR structure, so the only remaining step is establishing a connection.

TCP States

As a Winsock programmer, you are not required to know the actual TCP states, but by knowing them you will gain a better understanding of how the Winsock API calls affect change in the underlying protocol. In addition, many programmers run into a common problem when closing sockets: the TCP states surrounding a socket closure are of the most interest.

The start state of every socket is the CLOSED state. When a client initiates a connection, it sends a SYN packet to the server and puts the client socket in the SYN_SENT state. When the server receives the SYN packet, it sends a SYN-ACK packet, which the client responds to with an ACK packet. At this point, the client's socket is in the ESTABLISHED state. If the server never sends a SYN-ACK packet, the client times out and reverts to the CLOSED state.

When a server's socket is bound and is listening on a local interface and port, the state of the socket is LISTEN. When a client attempts a connection, the server receives a SYN packet and responds with a SYN-ACK packet. The state of the server's socket changes to SYN_RCVD. Finally, the client sends an ACK packet, which causes the state of the server's socket to change to ESTABLISHED.

Once the application is in the ESTABLISHED state, there are two paths for closure. If your application initiates the closure, it is known as an active socket closure; otherwise, the socket closure is passive. Figure 1-2 illustrates both an active and a passive closure. If you actively initiate a closure, your application sends a FIN packet. When your application calls closesocket or shutdown (with SD_SEND as its second argument), your application sends a FIN packet to the peer, and the state of your socket changes to FIN_WAIT_1. Normally, the peer responds with an ACK packet, and your socket's state becomes FIN_WAIT_2. If the peer also closes the connection, it sends a FIN packet and your computer responds by sending an ACK packet and placing your socket in the TIME_WAIT state.

The TIME_WAIT state is also called the 2MSL wait state. MSL stands for Maximum Segment Lifetime and represents the amount of time a packet can exist on the network before being discarded. Each IP packet has a time-to-live (TTL) field, which when decremented to 0 causes the packet to be discarded. Each router on the network that handles the packet decrements the TTL by 1 and passes the packet on. Once an application enters the TIME_WAIT state, it remains there for twice the MSL time. This allows TCP to re-send the final ACK in case it's lost, causing the FIN to be retransmitted. After the 2MSL wait state completes, the socket goes to the CLOSED state.

On an active close, two other paths lead to the TIME_WAIT state. In our previous discussion, only one side issues a FIN and receives an ACK response, but the peer is still free to send data until it too closes. This is where the other two paths come into play. In one path—the simultaneous close—a computer and its peer at the other side of a connection issue a close at the same time; the computer sends a FIN packet to the peer and receives a FIN packet from the peer. Then the computer sends an ACK packet in response to the peer's FIN packet and changes its socket to the CLOSING state. Once the computer receives the last ACK packet from the peer, the computer's socket state becomes TIME_WAIT.

Figure 1-2 TCP socket closure states

The other path for an active closure is just a variation on the simultaneous close: the socket transitions from the FIN_WAIT_1 state directly to the TIME_WAIT state. This occurs when an application sends a FIN packet but shortly thereafter receives a FIN-ACK packet from the peer. In this case, the peer is acknowledging the application's FIN packet and sending its own, to which the application responds with an ACK packet.

The major effect of the TIME_WAIT state is that while a TCP connection is in the 2MSL wait state, the socket pair defining that connection cannot be reused. A socket pair is the combination of local IP–local port and remote IP–remote port. Some TCP implementations do not allow the reuse of any port number in a socket pair in the TIME_WAIT state. Microsoft's implementation does not suffer from this deficiency. However, if a connection is attempted in which the socket pair is already in the TIME_WAIT state, the connection attempt will fail with error WSAEADDRINUSE. One way around this (besides waiting for the socket pair that is using that local port to leave the TIME_WAIT state) is to use the socket option SO_REUSEADDR. Chapter 7 covers the SO_REUSEADDR option in detail.

The last point of discussion for socket states is the passive closure. In this scenario, an application receives a FIN packet from the peer and responds with an ACK packet. At this point, the application's socket changes to the CLOSE_WAIT state. Because the peer has closed its end, it can't send any more data, but the application still can until it also closes its end of the connection. To close its end of the connection, the application sends its own FIN, causing the application's TCP socket state to become LAST_ACK. After the application receives an ACK packet from the peer, the application's socket reverts to the CLOSED state.

For more information regarding the TCP/IP protocol, consult RFC 793. This RFC and others can be found at http://www.rfc-editor.org.

connect

Connecting a socket is accomplished by calling connect, WSAConnect, or ConnectEx. We'll look at the Winsock 1 version of this function, which is defined as

int connect(     SOCKET s,     const struct sockaddr FAR* name,     int namelen );

The parameters are fairly self-explanatory: s is the valid TCP socket on which to establish the connection, name is the socket address structure (SOCKADDR_IN) for TCP that describes the server to connect to, and namelen is the length of the name variable.

If the computer you're attempting to connect to does not have a process listening on the given port, the connect call fails with the WSAECONNREFUSED error. The other error you might encounter is WSAETIMEDOUT, which occurs if the destination you're trying to reach is unavailable (either because of a communication-hardware failure on the route to the host or because the host is not currently on the network).

The following program fragment demonstrates how to write a simple client that can connect to the server application described earlier. You will find a complete version of this application in a file called TCPCLIENT on the companion CD.

#include <winsock2.h> void main(void) {    WSADATA              wsaData;    SOCKET               s;    SOCKADDR_IN          ServerAddr;    int                  Port = 5150;        // Initialize Winsock version 2.2    WSAStartup(MAKEWORD(2,2), &wsaData);        // Create a new socket to make a client connection.         s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);        // Set up a SOCKADDR_IN structure that will be used to connect    // to a listening server on port 5150. For demonstration    // purposes, let's assume our server's IP address is 136.149.3.29.    // Obviously, you will want to prompt the user for an IP address    // and fill in this field with the user's data.       ServerAddr.sin_family = AF_INET;       ServerAddr.sin_port = htons(Port);           ServerAddr.sin_addr.s_addr = inet_addr("136.149.3.29");    // Make a connection to the server with socket s.       connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr));            // At this point you can start sending or receiving data on    // the socket s. We will describe sending and receiving data    // later in the chapter.    // When you are finished sending and receiving data on socket s,    // you should close the socket using the closesocket API. We will    // describe socket closure later in the chapter.       closesocket(s);    // When your application is finished handling the connection, call    // WSACleanup.       WSACleanup(); }

Now that you can set up communication for a connection-oriented server and client, you are ready to begin handling data transmission.

Data Transmission

Sending and receiving data is what network programming is all about. For sending data on a connected socket, there are two API functions: send and WSASend. The second function is specific to Winsock 2. Likewise, two functions are for receiving data on a connected socket: recv and WSARecv. The latter is also a Winsock 2 call. An important thing to keep in mind is that all buffers associated with sending and receiving data are of the simple char type which is just simple byte-oriented data. In reality, it can be a buffer with any raw data in it—whether it's binary or string data doesn't matter.

In addition, the error code returned by all send and receive functions is SOCKET_ERROR. Once an error is returned, call WSAGetLastError to obtain extended error information. The two most common errors encountered are WSAECONNABORTED and WSAECONNRESET. Both of these deal with the connection being closed—either through a timeout or through the peer closing the connection. Another common error is WSAEWOULDBLOCK, which is normally encountered when either nonblocking or asynchronous sockets are used. This error basically means that the specified function cannot be completed at this time. In Chapter 5, we will describe various Winsock I/O methods that can help you avoid some of these errors.

send and WSASend

The first API function to send data on a connected socket is send, which is prototyped as

int send(     SOCKET s,      const char FAR * buf,      int len,      int flags );

The SOCKET parameter is the connected socket to send the data on. The second parameter, buf, is a pointer to the character buffer that contains the data to be sent. The third parameter, len, specifies the number of characters in the buffer to send. Finally, the flags parameter can be either 0, MSG_DONTROUTE, or MSG_OOB. Alternatively, the flags parameter can be a bitwise OR any of those flags. The MSG_DONTROUTE flag tells the transport not to route the packets it sends. It is up to the underlying transport to honor this request (for example, if the transport protocol doesn't support this option, it will be ignored). The MSG_OOB flag signifies that the data should be sent out of band.

On a good return, send returns the number of bytes sent; otherwise, if an error occurs, SOCKET_ERROR will be returned. A common error is WSAECO-NNABORTED, which occurs when the virtual circuit terminates because of a timeout failure or a protocol error. When this occurs, the socket should be closed, as it is no longer usable. The error WSAECONNRESET occurs when the application on the remote host resets the virtual circuit by executing a hard close or terminating unexpectedly, or when the remote host is rebooted. Again, the socket should be closed after this error occurs. The last common error is WSAETIMEDOUT, which occurs when the connection is dropped because of a network failure or the remote connected system going down without notice.

The Winsock 2 version of the send API function, WSASend, is defined as

int WSASend(     SOCKET s,     LPWSABUF lpBuffers,     DWORD dwBufferCount,     LPDWORD lpNumberOfBytesSent,     DWORD dwFlags,     LPWSAOVERLAPPED lpOverlapped,     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );

The socket is a valid handle to a connection session. The second parameter is a pointer to one or more WSABUF structures. This can be either a single structure or an array of such structures. The third parameter indicates the number of WSABUF structures being passed. Remember that each WSABUF structure is a character buffer and the length of that buffer. You might wonder why you would want to send more than one buffer at a time. This is called scatter-gather I/O and will be discussed later in this chapter; however, in the case of data sent using multiple buffers on a connected socket, each buffer is sent from the first to the last WSABUF structure in the array. The lpNumberOfBytesSent is a pointer to a DWORD that on return from the WSASend call contains the total number of bytes sent. The dwFlags parameter is equivalent to its counterpart in send. The last two parameters, lpOverlapped and lpCompletionRoutine, are used for overlapped I/O. Overlapped I/O is one of the asynchronous I/O models that Winsock supports and is discussed in detail in Chapter 5.

The WSASend function sets lpNumberOfBytesSent to the number of bytes written. The function returns 0 on success and SOCKET_ERROR on any error, and generally encounters the same errors as the send function. There is one final send function you should be aware of: WSASendDisconnect.

WSASendDisconnect

This function is rather specialized and not generally used. The function prototype is

int WSASendDisconnect (     SOCKET s,      LPWSABUF lpOutboundDisconnectData );

Out-of-Band Data

When an application on a connected stream socket needs to send data that is more important than regular data on the stream, it can mark the important data as out-of-band (OOB) data. The application on the other end of a connection can receive and process OOB data through a separate logical channel that is conceptually independent of the data stream.

In TCP, OOB data is implemented via an urgent 1-bit marker (called URG) and a 16-bit pointer in the TCP segment header that identify a specific downstream byte as urgent data. Two specific ways of implementing urgent data currently exist for TCP. RFC 793, which describes TCP and introduces the concept of urgent data, indicates that the urgent pointer in the TCP header is a positive offset to the byte that follows the urgent data byte. However, RFC 1122 describes the urgent offset as pointing to the urgent byte itself.

The Winsock specification uses the term OOB to refer to both protocol-independent OOB data and TCP's implementation of OOB data (urgent data). To check whether pending data contains urgent data, you must call the ioctlsocket function with the SIOCATMARK option. Chapter 7 discusses how to use SIOCATMARK.

Winsock provides several methods for obtaining the urgent data. Either the urgent data is inlined so that it appears in the normal data stream, or inlining can be turned off so that a discrete call to a receive function returns only the urgent data. The socket option SO_OOBINLINE, also discussed in detail in Chapter 7, controls the behavior of OOB data.

Telnet and Rlogin use urgent data for several reasons. However, unless you plan to write your own Telnet or Rlogin, you should stay away from urgent data. It's not well defined and might be implemented differently on platforms other than Windows. If you require a method of signaling the peer for urgent reasons, implement a separate control socket for this urgent data and reserve the main socket connection for normal data transfers.

The function initiates a shutdown of the socket and sends disconnect data. Of course, this function is available only to those transport protocols that support graceful close and disconnect data. None of the transport providers currently support disconnect data. The WSASendDisconnect function behaves like a call to the shutdown function (which is described later) with an SD_SEND argument, but it also sends the data contained in its lpOutboundDisconnectData parameter. Subsequent sends are not allowed on the socket. Upon failure, WSASendDisconnect returns SOCKET_ERROR. This function can encounter some of the same errors as the send function.

recv and WSARecv

The recv function is the most basic way to accept incoming data on a connected socket. This function is defined as

int recv(     SOCKET s,      char FAR* buf,      int len,      int flags );

The first parameter, s, is the socket on which data will be received. The second parameter, buf, is the character buffer that will receive the data, and len is either the number of bytes you want to receive or the size of the buffer, buf. Finally, the flags parameter can be one of the following values: 0, MSG_PEEK, or MSG_OOB. In addition, you can bitwise OR any one of these flags together. Of course, 0 specifies no special actions. MSG_PEEK causes the data that is available to be copied into the supplied receive buffer, but this data is not removed from the system's buffer. The number of bytes pending is also returned.

Message peeking is bad. Not only does it degrade performance, as you now need to make two system calls (one to peek and one without the MSG_PEEK flag to actually remove the data), but it is also unreliable under certain circumstances. The data returned might not reflect the entire amount available. Also, by leaving data in the system buffers, the system has less space to contain incoming data. As a result, the system reduces the TCP window size for all senders. This prevents your application from achieving the maximum possible throughput. The best thing to do is to copy all the data you can into your own buffer and manipulate it there.

There are some considerations when using recv on a message- or datagram-based socket such as UDP, which we will describe later. If the data pending is larger than the supplied buffer, the buffer is filled with as much data as it will contain. In this event, the recv call generates the error WSAEMSGSIZE. Note that the message-size error occurs with message-oriented protocols. Stream protocols such as TCP buffer incoming data and will return as much data as the application requests, even if the amount of pending data is greater. Thus, for streaming protocols you will not encounter the WSAEMSGSIZE error.

The WSARecv function adds some new capabilities over recv, such as overlapped I/O and partial datagram notifications. The definition of WSARecv is

int WSARecv(     SOCKET s,      LPWSABUF lpBuffers,      DWORD dwBufferCount,      LPDWORD lpNumberOfBytesRecvd,      LPDWORD lpFlags,      LPWSAOVERLAPPED lpOverlapped,      LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );

Parameter s is the connected socket. The second and third parameters are the buffers to receive the data. The lpBuffers parameter is an array of WSABUF structures, and dwBufferCount indicates the number of WSABUF structures in the array. The lpNumberOfBytesReceived parameter points to the number of bytes received by this call if the receive operation completes immediately. The lpFlags parameter can be one of the values MSG_PEEK, MSG_OOB, or MSG_PARTIAL, or a bitwise OR combination of those values. The MSG_PARTIAL flag has several different meanings depending on where it is used or encountered. For message-oriented protocols that support partial messaging (like AppleTalk), this flag is set upon return from WSARecv (if the entire message could not be returned in this call because of insufficient buffer space). In this case, subsequent WSARecv calls set this flag until the entire message is returned, when the MSG_PARTIAL flag is cleared. If this flag is passed as an input parameter, the receive operation should complete as soon as data is available, even if it is only a portion of the entire message. The MSG_PARTIAL flag is used only with message-oriented protocols, not with streaming ones. In addition, not all protocols support partial messages. The protocol entry for each protocol contains a flag indicating whether it supports this feature. See Chapter 2 for more information. The lpOverlapped and lpCompletionRoutine parameters are used in overlapped I/O operations, discussed in Chapter 5. There is one other specialized receive function you should be aware of: WSARecvDisconnect.

WSARecvDisconnect

This function is the opposite of WSASendDisconnect and is defined as follows:

int WSARecvDisconnect(     SOCKET s,      LPWSABUF lpInboundDisconnectData );

Like its sending counterpart, the parameters of WSASendDisconnect are the connected socket handle and a valid WSABUF structure with the data to be received. The data received can be only disconnect data that is sent by a WSASendDisconnect on the other side; it cannot be used to receive normal data. In addition, once the data is received, this function disables reception from the remote party, which is equivalent to calling the shutdown function (which is described later) with SD_RECEIVE.

Stream Protocols

Because most connection-oriented communication, such as TCP, is streaming protocols, we'll briefly describe them here. A streaming protocol is one that the sender and receiver may break up or coalesce data into smaller or larger groups. The main thing to be aware of with any function that sends or receives data on a stream socket is that you are not guaranteed to read or write the amount of data you request. Let's say you have a character buffer with 2048 bytes of data you want to send with the send function. The code to send this is

char sendbuff[2048]; int  nBytes = 2048; // Fill sendbuff with 2048 bytes of data // Assume s is a valid, connected stream socket ret = send(s, sendbuff, nBytes, 0);

It is possible for send to return having sent less than 2048 bytes. The ret variable will be set to the number of bytes sent because the system allocates a certain amount of buffer space for each socket to send and receive data. In the case of sending data, the internal buffers hold data to be sent until such time as the data can be placed on the wire. Several common situations can cause this. For example, simply transmitting a huge amount of data will cause these buffers to become filled quickly. Also, for TCP/IP, there is what is known as the window size. The receiving end will adjust this window size to indicate how much data it can receive. If the receiver is being flooded with data, it might set the window size to 0 to catch up with the pending data. This will force the sender to stop until it receives a new window size greater than 0. In the case of our send call, there might be buffer space to hold only 1024 bytes, in which case you would have to resubmit the remaining 1024 bytes. The following code ensures that all your bytes are sent:

char sendbuff[2048]; int  nBytes = 2048,      nLeft,      idx; // Fill sendbuff with 2048 bytes of data // Assume s is a valid, connected stream socket nLeft = nBytes; idx = 0; while (nLeft > 0) {     ret = send(s, &sendbuff[idx], nLeft, 0);     if (ret == SOCKET_ERROR)     {         // Error     }     nLeft -= ret;     idx += ret; }

The same principle holds true for receiving data on a stream socket but is less significant. Because stream sockets are a continuous stream of data, when an application reads, it isn't generally concerned with how much data it should read. If your application requires discrete messages over a stream protocol, you might have to do a little work. If all the messages are the same size, life is pretty simple, and the code for reading, say, 512-byte messages would look like this:

char    recvbuff[1024]; int     ret,         nLeft,         idx; nLeft = 512; idx = 0; while (nLeft > 0) {     ret = recv(s, &recvbuff[idx], nLeft, 0);     if (ret == SOCKET_ERROR)     {         // Error     }     idx += ret;     nLeft -= ret; }

Things get a little complicated if your message sizes vary. It is necessary to impose your own protocol to let the receiver know how big the forthcoming message will be. For example, the first four bytes written to the receiver will always be the integer size in bytes of the forthcoming message. The receiver will start every read by looking at the first four bytes, converting them to an integer, and determining how many additional bytes that message comprises.

Scatter-Gather I/O

Scatter-gather support is a concept originally introduced in Berkeley Sockets with the functions recv and writev. This feature is available with the Winsock 2 functions WSARecv, WSARecvFrom, WSASend, and WSASendTo. It is most useful for applications that send and receive data that is formatted in a very specific way. For example, messages from a client to a server might always be composed of a fixed 32-byte header specifying some operation, followed by a 64-byte data block and terminated with a 16-byte trailer. In this example, WSASend can be called with an array of three WSABUF structures, each corresponding to the three message types. On the receiving end, WSARecv is called with three WSABUF structures, each containing data buffers of 32 bytes, 64 bytes, and 16 bytes.

When using stream-based sockets, scatter-gather operations simply treat the supplied data buffers in the WSABUF structures as one contiguous buffer. Also, the receive call might return before all buffers are full. On message-based sockets, each call to a receive operation receives a single message up to the buffer size supplied. If the buffer space is insufficient, the call fails with WSAEMSGSIZE and the data is truncated to fit the available space. Of course, with protocols that support partial messages, the MSG_PARTIAL flag can be used to prevent data loss.

Breaking the Connection

Once you are finished with a socket connection, you must close it and release any resources associated with that socket handle. To actually release the resources associated with an open socket handle, use the closesocket call. Be aware, however, that closesocket can have some adverse effects—depending on how it is called—that can lead to data loss. For this reason, a connection should be gracefully terminated with the shutdown function before a call to the closesocket function. These two API functions are discussed next.

shutdown

To ensure that all data an application sends is received by the peer, a well-written application should notify the receiver that no more data is to be sent. Likewise, the peer should do the same. This is known as a graceful close and is performed by the shutdown function, defined as

int shutdown(     SOCKET s,      int how );

The how parameter can be SD_RECEIVE, SD_SEND, or SD_BOTH. For SD_RECEIVE, subsequent calls to any receive function on the socket are disallowed. This has no effect on the lower protocol layers. And for TCP sockets, if data is queued for receive or if data subsequently arrives, the connection is reset. However, on UDP sockets incoming data is still accepted and queued (because shutdown has no meaning for connectionless protocols). For SD_SEND, subsequent calls to any send function are disallowed. For TCP sockets, this causes a FIN packet to be generated after all data is sent and acknowledged by the receiver. Finally, specifying SD_BOTH disables both sends and receives.

Note that not all connection-oriented protocols support graceful closure, which is what the shutdown API performs. For these protocols (such as ATM), only closesocket needs to be called to terminate the session.

closesocket

The closesocket function closes a socket and is defined as

int closesocket (SOCKET s);

Calling closesocket releases the socket descriptor and any further calls using the socket fail with WSAENOTSOCK. If there are no other references to this socket, all resources associated with the descriptor are released. This includes discarding any queued data.

Pending synchronous calls issued by any thread in this process are canceled without posting any notification messages. Pending overlapped operations are also canceled. Any event, completion routine, or completion port that is associated with the overlapped operation is performed but will fail with the error WSA_OPERATION_ABORTED. Socket I/O models are discussed in greater depth in Chapter 5. In addition, one other factor influences the behavior of closesocket: whether the socket option SO_LINGER has been set. Consult the description for the SO_LINGER option in Chapter 7 for a complete explanation.



Network Programming for Microsoft Windows
Network Programming for Microsoft Windows (Microsoft Professional Series)
ISBN: 0735605602
EAN: 2147483647
Year: 2001
Pages: 172
Authors: Anthony Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net