Introduction
This article is a continuation of my article on IOCP. In that article I had demonstrated the use of I/O Completion ports to create scalable client/server applications. In this article I will demonstrate how to use the select()
function to create scalable client/server applications. In this implementation client and server send and display simple string messages.
The select()
function works with synchronous sockets and doesn't require threads to be created.
The client I have provided here clientselect.exe is the same as clientiocp.exe in my earlier article. I have just renamed it. The code for this client can be found in my earlier article.
Using the code
The select()
function will allow a developer to allocate the sockets in three different sets and it will monitor the sockets for state changes. We can process the socket based on its status. The three sets that are created for sockets are:
- Read Set – Check the sockets belonging to this group for readability. A socket will be considered readable when:
- A connection is pending on the listening socket
- Data is received on the socket
- Connection is closed or terminated
- Write Set – Check the sockets belonging to this group for writability. A socket will be considered writable when data can be sent on the socket
- Exception Set – Check the sockets belonging to this group for errors.
The sets are implemented using an fd_set
structure. The definition of fd_set
can be found in winsock2.h.
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
The following are macros that can be used to manipulate the sets. The source code of these macros can be found in winsock2.h.
FD_CLR
– Removes a socket from the setFD_ISSET
– Helps in identifying if a socket belongs to a specified setFD_SET
– Assigns a socket to a specified setFD_ZERO
– Resets the set
For this implementation the client information will be stored in a singly linked list of CClientContext
structure.
class CClientContext {
private:
int m_nTotalBytes;
int m_nSentBytes;
SOCKET m_Socket; char m_szBuffer[MAX_BUFFER_LEN];
CClientContext *m_pNext;
public:
void SetTotalBytes(int n)
{
m_nTotalBytes = n;
}
int GetTotalBytes()
{
return m_nTotalBytes;
}
void SetSentBytes(int n)
{
m_nSentBytes = n;
}
void IncrSentBytes(int n)
{
m_nSentBytes += n;
}
int GetSentBytes()
{
return m_nSentBytes;
}
void SetSocket(SOCKET s)
{
m_Socket = s;
}
SOCKET GetSocket()
{
return m_Socket;
}
void SetBuffer(char *szBuffer)
{
strcpy(m_szBuffer, szBuffer);
}
void GetBuffer(char *szBuffer)
{
strcpy(szBuffer, m_szBuffer);
}
char* GetBuffer()
{
return m_szBuffer;
}
void ZeroBuffer()
{
ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
}
CClientContext* GetNext()
{
return m_pNext;
}
void SetNext(CClientContext *pNext)
{
m_pNext = pNext;
}
CClientContext()
{
m_Socket = SOCKET_ERROR;
ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
m_nTotalBytes = 0;
m_nSentBytes = 0;
m_pNext = NULL;
}
~CClientContext()
{
closesocket(m_Socket);
}
};
Following is InitSets()
function. This function will initialize the sets. Assign the sockets to appropriate sets. This function will be called before calling the select()
function.
void InitSets(SOCKET ListenSocket)
{
FD_ZERO(&g_ReadSet);
FD_ZERO(&g_WriteSet);
FD_ZERO(&g_ExceptSet);
FD_SET(ListenSocket, &g_ReadSet);
FD_SET(ListenSocket, &g_ExceptSet);
CClientContext *pClientContext = GetClientContextHead();
while(pClientContext)
{
if(pClientContext->GetSentBytes() < pClientContext->GetTotalBytes())
{
FD_SET(pClientContext->GetSocket(), &g_WriteSet);
}
else
{
FD_SET(pClientContext->GetSocket(), &g_ReadSet);
}
FD_SET(pClientContext->GetSocket(), &g_ExceptSet);
pClientContext = pClientContext->GetNext();
}
}
Following is AcceptConnections()
function. This function will monitor the sockets using select()
. It will also process the sockets according to their status.
void AcceptConnections(SOCKET ListenSocket)
{
while (true)
{
InitSets(ListenSocket);
if (select(0, &g_ReadSet, &g_WriteSet, &g_ExceptSet, 0) > 0)
{
if (FD_ISSET(ListenSocket, &g_ReadSet))
{
sockaddr_in ClientAddress;
int nClientLength = sizeof(ClientAddress);
SOCKET Socket = accept(ListenSocket,
(sockaddr*)&ClientAddress, &nClientLength);
if (INVALID_SOCKET == Socket)
{
printf("\nError occurred while accepting socket:
%ld.", GetSocketSpecificError(ListenSocket));
}
printf("\nClient connected from: %s", inet_ntoa
(ClientAddress.sin_addr));
u_long nNoBlock = 1;
ioctlsocket(Socket, FIONBIO, &nNoBlock);
CClientContext *pClientContext = new CClientContext;
pClientContext->SetSocket(Socket);
AddClientContextToList(pClientContext);
}
if (FD_ISSET(ListenSocket, &g_ExceptSet))
{
printf("\nError occurred while accepting socket: %ld.",
GetSocketSpecificError(ListenSocket));
continue;
}
CClientContext *pClientContext = GetClientContextHead();
while (pClientContext)
{
if (FD_ISSET(pClientContext->GetSocket(), &g_ReadSet))
{
int nBytes = recv(pClientContext->GetSocket(),
pClientContext->GetBuffer(), MAX_BUFFER_LEN, 0);
if ((0 == nBytes) || (SOCKET_ERROR == nBytes))
{
if (0 != nBytes) {
printf("\nError occurred while
receiving on the socket: %d.",
GetSocketSpecificError
(pClientContext->GetSocket()));
}
pClientContext = DeleteClientContext
(pClientContext);
continue;
}
pClientContext->SetTotalBytes(nBytes);
pClientContext->SetSentBytes(0);
printf("\nThe following message was received: %s",
pClientContext->GetBuffer());
}
if (FD_ISSET(pClientContext->GetSocket(), &g_WriteSet))
{
int nBytes = 0;
if (0 < (pClientContext->GetTotalBytes() -
pClientContext->GetSentBytes()))
{
nBytes = send(pClientContext->GetSocket(),
(pClientContext->GetBuffer() +
pClientContext->GetSentBytes()),
(pClientContext->GetTotalBytes() -
pClientContext->GetSentBytes()), 0);
if (SOCKET_ERROR == nBytes)
{
printf("\nError occurred while
sending on the socket: %d.",
GetSocketSpecificError
(pClientContext->GetSocket()));
pClientContext = DeleteClientContext
(pClientContext);
continue;
}
if (nBytes ==
(pClientContext->GetTotalBytes() -
pClientContext->GetSentBytes()))
{
pClientContext->SetTotalBytes(0);
pClientContext->SetSentBytes(0);
}
else
{
pClientContext->IncrSentBytes(nBytes);
}
}
}
if (FD_ISSET(pClientContext->GetSocket(), &g_ExceptSet))
{
printf("\nError occurred on the socket: %d.",
GetSocketSpecificError(pClientContext->GetSocket()));
pClientContext = DeleteClientContext(pClientContext);
continue;
}
pClientContext = pClientContext->GetNext();
} }
else {
printf("\nError occurred while executing select(): %ld.",
WSAGetLastError());
return; }
}
}
I have created a function GetSocketSpecificError()
to get the error on a specific socket. When working with select()
we can't rely on WSAGetLastError()
because multiple sockets may have errors and we require socket specific errors.
int GetSocketSpecificError(SOCKET Socket)
{
int nOptionValue;
int nOptionValueLength = sizeof(nOptionValue);
getsockopt(Socket, SOL_SOCKET, SO_ERROR, (char*)&nOptionValue,
&nOptionValueLength);
return nOptionValue;
}
The following are linked list manipulation functions. These will be used to work with CClientContext
singly linked list.
CClientContext* GetClientContextHead()
{
return g_pClientContextHead;
}
void AddClientContextToList(CClientContext *pClientContext)
{
pClientContext->SetNext(g_pClientContextHead);
g_pClientContextHead = pClientContext;
}
CClientContext * DeleteClientContext(CClientContext *pClientContext)
{
if (pClientContext == g_pClientContextHead)
{
CClientContext *pTemp = g_pClientContextHead;
g_pClientContextHead = g_pClientContextHead->GetNext();
delete pTemp;
return g_pClientContextHead;
}
CClientContext *pPrev = g_pClientContextHead;
CClientContext *pCurr = g_pClientContextHead->GetNext();
while (pCurr)
{
if (pCurr == pClientContext)
{
CClientContext *pTemp = pCurr->GetNext();
pPrev->SetNext(pTemp);
delete pCurr;
return pTemp;
}
pPrev = pCurr;
pCurr = pCurr->GetNext();
}
return NULL;
}
Screen Shots
History
- August 7, 2007: Initial Release