An Intro to Windows Socket (Winsock2) Programming 12

 

 

 

 

 

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 following Figure describes briefly the flow of TCP packet between client and server sides.

 

The TCP client-server packet flow diagram

 

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 (sliding window demo). 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

nLeft = nBytes;

idx = 0;

 

while (nLeft > 0)

{

    // Assume s is a valid, connected stream socket

    ret = send(s, &sendbuff[idx], nLeft, 0);

    if (ret == SOCKET_ERROR)

    {

        // Error handler

    }

    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 handler

    }

    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. A flag that describes what types of operation is summarized in the following Table. Possible values for this flag are listed in the Winsock2.h header file.

 

Value

Meaning

SD_SEND (0)

Shutdown send operations.

SD_RECEIVE (1)

Shutdown receive operations.

SD_BOTH (2)

Shutdown both send and receive operations.

 

If no error occurs, shutdown returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError().

 

Error code

Meaning

WSANOTINITIALISED

A successful WSAStartup() call must occur before using this function.

WSAENETDOWN

The network subsystem has failed.

WSAEINVAL

The how parameter is not valid, or is not consistent with the socket type. For example, SD_SEND is used with a UNI_RECV socket type.

WSAEINPROGRESS

A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function.

WSAENOTCONN

The socket is not connected (connection-oriented sockets only).

WSAENOTSOCK

The descriptor is not a socket.

 

Once the shutdown() function is called to disable send, receive, or both, there is no method to re-enable send or receive for the existing socket connection. An application should not rely on being able to reuse a socket after it has been shut down. In particular, a Windows Sockets provider is not required to support the use of connect() on a socket that has been shut down. If an application wants to reuse a socket, then the DisconnectEx() function should be called with the dwFlags parameter set to TF_REUSE_SOCKET to close a connection on a socket and prepare the socket handle to be reused. When the DisconnectEx() request completes, the socket handle can be passed to the AcceptEx() or ConnectEx() function.

If an application wants to reuse a socket, the TransmitFile() or TransmitPackets() functions can be called with the dwFlags parameter set with TF_DISCONNECT and TF_REUSE_SOCKET to disconnect after all the data has been queued for transmission and prepare the socket handle to be reused. When the TransmitFile() request completes, the socket handle can be passed to the function call previously used to establish the connection, such as AcceptEx() or ConnectEx(). When the TransmitPackets() function completes, the socket handle can be passed to the AcceptEx() function. Take note that the socket level disconnect is subject to the behavior of the underlying transport. For example, a TCP socket may be subject to the TCP TIME_WAIT state, causing the DisconnectEx(), TransmitFile(), or TransmitPackets() call to be delayed.

 

closesocket()

 

The closesocket() function closes a socket and is defined as:

 

int closesocket (SOCKET s);

 

If no error occurs, closesocket() returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError().

 

Error code

Meaning

WSANOTINITIALISED

A successful WSAStartup() call must occur before using this function.

WSAENETDOWN

The network subsystem has failed.

WSAENOTSOCK

The descriptor is not a socket.

WSAEINPROGRESS

A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function.

WSAEINTR

The (blocking) Windows Socket 1.1 call was canceled through WSACancelBlockingCall().

WSAEWOULDBLOCK

The socket is marked as nonblocking, but the l_onoff member of the linger structure is set to non-zero and the l_linger member of the linger structure is set to a nonzero timeout value.

 

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. In addition, one other factor influences the behavior of closesocket(): whether the socket option SO_LINGER has been set. An application should always have a matching call to closesocket() for each successful call to socket to return any socket resources to the system.

 

 

 

 

< Winsock2 11 | Windows Socket 2 (Winssock2) | Win32 Programming | Winsock2 13 >