Socket Server in Win32 Select Model

Windows provides different models for programming with sockets. This winsock tutorial explains, one of the models which uses select function. The other winsock models are using event object, overlapped model and IO Completion port.

This model design is based on Berkeley socket implementation. The basic idea is to use the select function to determine if a socket is to be read or to be written. This can be used without the problems of blocking.

The select function takes 3 parameters. All of them are of type FD_SET. One parameter of type FD_SET is used to check if the socket is to be read, the other is used to check if the socket is to be written and the 3 is used to check if there is any out of band data. The most important among the 3 parameters is of course the first two. The sample program explained in this winsock tutorial will give a good idea on how to use them. To summarize,

Read fd_set can check

  • Data is available for reading
  • Connection has been closed or terminated

Write fd_set can identify the following

  • Data can be sent
  • If a non-blocking connect call is being processed, the connection has succeeded.

If you look at the following winsock tutorial program, it uses ioctlsocket function to make the socket non-blocking. Then it uses select function in an infinite loop to check the type of network event and executes the commands accordingly.

Let us look at a sample program to understand this.


#include ....

#define PORT 1150

#define BUFFERSIZE 8192

typedef struct _MYSOCKET_INFORMATION {
CHAR Buffer[BUFFERSIZE];
WSABUF DataBuf;
SOCKET Socket;
DWORD SendBytes;
DWORD RecvBytes;
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;

BOOL CreateSocketInformation(SOCKET s);
void FreeSocketInformation(DWORD Index);
DWORD TotalSockets = 0;
LPSOCKET_INFORMATION SocketList[FD_SETSIZE];

void main(void)
{
//some of hte basic declarations required for this winsock tutorial
SOCKET ListenSocket;
SOCKET AcceptSocket;
SOCKADDR_IN InternetAddr;
WSADATA wsaData;
FD_SET Writer;
FD_SET Reader;
ULONG NonBlock;
DWORD Flags;
//  .. and other declarations
if ((Ret = WSAStartup(MAKEWORD(2,0),&wsaData)) != 0)
{
//startup failed
}

// Create a socket for the winsock tutorial.
if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Winsock tutorial error: WSASocket() failed %dn", WSAGetLastError());
return;
}

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(ListenSocket, (SOCKADDR *) &InternetAddr, sizeof(InternetAddr))
== SOCKET_ERROR)
{
printf("Winsock tutorial error: Binding failed %dn", WSAGetLastError());
return;
}

if (listen(ListenSocket, 5))
{
printf("Winsock tutorial error: listen failed %dn", WSAGetLastError());
return;
}

// Change the socket mode on the listening socket from blocking to non-block
NonBlock = 1;
if (ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("ioctlsocket() failed n");
return;
}

while(TRUE)
{
// Initialize the Read and Write socket set.
FD_ZERO(&Reader);
FD_ZERO(&Writer);
// Check for connection attempts.
FD_SET(ListenSocket, &Reader);
// Set Read and Write notification for each socket based on the
// current state the buffer.
for (i = 0; i < TotalSockets; i++)
if (SocketList[i]->RecvBytes > SocketList[i]->SendBytes)
FD_SET(SocketList[i]->Socket, &Writer);
else
FD_SET(SocketList[i]->Socket, &Reader);
if ((Total = select(0, &Reader, &Writer, NULL, NULL)) == SOCKET_ERROR)
{
printf("Winsock tutorial error: select function returned with error %dn", WSAGetLastError());
return;
}

// Check for arriving connections on the listening socket.
if (FD_ISSET(ListenSocket, &Reader))
{
Total--;
if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET)
{
// Set the accepted socket to non-blocking mode so the server will
// not get caught in a blocked condition on WSASends
NonBlock = 1;
if (ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("Winsock tutorial error: ioctlsocket() failed with error %dn", WSAGetLastError());
return;
}
if (CreateSocketInformation(AcceptSocket) == FALSE)
return;
}
else
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("accept() failed with error %dn", WSAGetLastError());
return;
}
}
}
// Check each socket for Read and Write notification for Total number of sockets
for ( i = 0; Total > 0 && i < TotalSockets; i++)
{
LPSOCKET_INFORMATION SocketInfo = SocketList[i];
// If the Reader is marked for this socket then this means data
// is available to be read on the socket.
if (FD_ISSET(SocketInfo->Socket, &Reader))
{
Total--;
SocketInfo->DataBuf.buf = SocketInfo->Buffer;
SocketInfo->DataBuf.len = BUFFERSIZE;
Flags = 0;
if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,
&Flags, NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("Winsock tutorial: Receive failed with errorn");
FreeSocketInformation(i);
}
continue;
}
else
{
SocketInfo->RecvBytes = RecvBytes;
printf("%sn",SocketInfo->DataBuf.buf);
// If zero bytes are received, this indicates connection is closed.
if (RecvBytes == 0)
{
FreeSocketInformation(i);
continue;
}
}
}
// If the Writer is marked on this socket then this means the internal
// data buffers are available for more data.
if (FD_ISSET(SocketInfo->Socket, &Writer))
{
Total--;
SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->SendBytes;
SocketInfo->DataBuf.len = SocketInfo->RecvBytes - SocketInfo->SendBytes;
if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,
NULL, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("Send failed with errorn");
FreeSocketInformation(i);
}

continue;
}
else
{
SocketInfo->SendBytes += SendBytes;
if (SocketInfo->SendBytes == SocketInfo->RecvBytes)
{
SocketInfo->SendBytes = 0;
SocketInfo->RecvBytes = 0;
}
}
}
}
}
}

BOOL CreateSocketInformation(SOCKET s)
{
LPSOCKET_INFORMATION SI;
printf("Accepted socketn");
if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("Winsock tutorial error: GlobalAlloc() failedn");
return FALSE;
}
// Prepare SocketInfo structure for use.
SI->Socket = s;
SI->SendBytes = 0;
SI->RecvBytes = 0;
SocketList[TotalSockets] = SI;
TotalSockets++;
return(TRUE);
}

void FreeSocketInformation(DWORD Index)
{
LPSOCKET_INFORMATION SI = SocketList[Index];
DWORD i;
closesocket(SI->Socket);
printf("Closing socketn");
GlobalFree(SI);

// Remove from the socket array
for (i = Index; i < TotalSockets; i++)
{
SocketList[i] = SocketList[i + 1];
}

TotalSockets--;
}

The base idea is follows. As for any server socket, the socket needs to be created, bind and then it should start listening at a specified port. Then the socket must be made Non-blocking by calling ioctlsocket. After that, check for any new connections, read or write using select function.

When the socket has to be checked for readability add the socket to the readfds set and wait for the function select to complete. When the call to select is complete, the socket is to be checked if it is still part of readfds set. If that is true, then the socket has some data to read. Any one of the three parameters must contain a socket handle while usage.

This winsock tutorial manipulates the fd_set structures using the following functions. FD_CLR, FD_ISSET, FD_SET and FD_ZERO. The initialization and verifying the fd_set handles have to be done by the above four macros and then the select function has to be called.

If you run the above program and try connecting using a client, it will receive the data and display it on the console.


Note:

The socket programs in MFC need the library ws2_32.lib to be referenced before linking. Otherwise the VC++ linker throws errors.

Please find the sample project here.