Generic Network Server
This code implements a generic TCP/IP listener. I have derived from this class to produce multithreaded servers.
This interface assumes that the user is interested in working at the Winsock level of detail. If I were going to transform this code to be cross-platform or for use by those with no experience with Winsock then I would abstract and hide the Window's specifics (e.g., SOCKET and other Winsock-specific structures and functions).
Note that this is a console application [uses fprintf(stderr,...)] - the message mechanism could be easily abstracted but doing so would have violated the goal of simplicity, until such time as that abstraction is necessary.
Compatibility/System Requirements
This code was compiled with VC7 on Windows 2000 and Windows XP systems.
Code
// Servertools
// Copyright (c) 2004, 2005 Robin Eric Fredericksen
// -- All rights reserved.
#include <WinSock2.h>
//==================================================
// The Interface
//==================================================
class CServer
{
public:
enum
{
DEFAULT_LISTEN_IP = INADDR_ANY, // all interfaces
DEFAULT_LISTEN_PORT = 10000,
DEFAULT_SOCKET_TIMEOUT_ms = 8000,
DEFAULT_CONNECTION_QUEUE = 5
};
protected:
bool m_bWSAInitialized;
bool m_bSocketInitialized;
WSADATA m_oWSAData;
void SetListenAddress(DWORD _dwHBO_IP, USHORT _HBO_Port);
sockaddr_in m_oListenAddress;
SOCKET m_sListenSocket;
bool InitWSA(void);
void PrintWSALastError(void);
public:
CServer(void);
virtual ~CServer();
bool InitSuccess(void) { return( m_bWSAInitialized &&
m_bSocketInitialized ); }
// return default interface IP in host byte order
DWORD DefaultIP(void);
const char * DefaultDottedIP(void);
const char * DottedIP(DWORD _HBO_IP);
// parms in host byte order
bool BindTo( DWORD _dwHBO_IP, USHORT _HBO_Port,
USHORT _usMaxWaitingConnections=DEFAULT_CONNECTION_QUEUE
);
// never returns! call from thread that knows this
// calls DispatchConnection upon successful accept() call
void StartListening(void);
// override this to do the work in your derived class
virtual void DispatchConnection(SOCKET _sWorkSocket);
};
//==================================================
// The implementation
//==================================================
//==================================================
virtual void DispatchConnection(SOCKET _sWorkSocket)
{
// just say no
shutdown(_sWorkSocket, SD_BOTH);
Sleep(1000);
closesocket(_sWorkSocket);
}
//==================================================
bool CServer::InitWSA(void)
{
return( 0 == WSAStartup(MAKEWORD(2, 0), &m_oWSAData) );
}
//==================================================
void CServer::SetListenAddress(DWORD _dwHBO_IP, USHORT _HBO_Port)
{
m_oListenAddress.sin_family = AF_INET;
m_oListenAddress.sin_addr.s_addr = htonl(_dwHBO_IP);
m_oListenAddress.sin_port = htons(_HBO_Port);
}
//==================================================
CServer::CServer(void)
{
m_bWSAInitialized = InitWSA();
m_bSocketInitialized = false;
SetListenAddress(DEFAULT_LISTEN_IP, DEFAULT_LISTEN_PORT);
}
//==================================================
CServer::~CServer()
{
if( m_bWSAInitialized ) WSACleanup();
}
//==================================================
const char * CServer::DefaultDottedIP(void)
{
DWORD dwIP = DefaultIP();
return( DottedIP( dwIP ) );
}
//==================================================
const char * CServer::DottedIP(DWORD _HBO_IP)
{
DWORD dwIP = htonl(_HBO_IP);
return( inet_ntoa( *(in_addr*)&dwIP ) );
}
//==================================================
DWORD CServer::DefaultIP(void)
{
DWORD dwIP = INADDR_NONE;
if( m_bWSAInitialized )
{
hostent * poHostName = gethostbyname(NULL);
if( poHostName )
{
dwIP = *((DWORD*)poHostName->h_addr);
}
}
return( ntohl(dwIP) );
}
//==================================================
void CServer::PrintWSALastError(void)
{
DWORD dwError = WSAGetLastError();
fprintf(stderr, _T("!!WSALastError(%u:%X:%d)\n"),
dwError,dwError,dwError );
}
//==================================================
bool CServer::BindTo(DWORD _dwHBO_IP, USHORT _HBO_Port,
USHORT _usMaxWaitingConnections)
{
if( !m_bWSAInitialized ) return( false );
SetListenAddress(_dwHBO_IP, _HBO_Port);
// do all the work of setting up the listening socket
m_sListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if( INVALID_SOCKET == m_sListenSocket )
{
// record the error here for later inspection
fprintf(stderr, _T("!!Could not create socket\n") );
PrintWSALastError();
return( false );
}
// do this so that we don't run out of source ports...
BOOL bReuse = TRUE;
int iResult = setsockopt(m_sListenSocket,
SOL_SOCKET, SO_REUSEADDR,
(LPCSTR)&bReuse, sizeof(bReuse) );
if( SOCKET_ERROR == iResult )
{
// provide feedback/warning on failure for later inspection
fprintf(stderr, _T("Could not setsockopt(SO_REUSEADDR)\n") );
PrintWSALastError();
// not a critical error
}
// make sure the timeouts are what we want
DWORD dwTimeout_ms = DEFAULT_SOCKET_TIMEOUT_ms;
iResult = setsockopt(m_sListenSocket, SOL_SOCKET, SO_RCVTIMEO,
(const char*)&dwTimeout_ms, sizeof(dwTimeout_ms) );
if( SOCKET_ERROR == iResult )
{
// send warning message
fprintf(stderr, _T("Could not setsockopt(SO_RCVTIMEO, %d)\n"),
dwTimeout_ms );
PrintWSALastError();
// not a critical error
}
iResult = setsockopt(m_sListenSocket, SOL_SOCKET, SO_SNDTIMEO,
(const char*)&dwTimeout_ms, sizeof(dwTimeout_ms) );
if( SOCKET_ERROR == iResult )
{
// send warning message
fprintf(stderr, _T("Could not setsockopt(SO_SNDTIMEO, %d)\n"),
dwTimeout_ms );
PrintWSALastError();
// not a critical error
}
iResult = bind(m_sListenSocket, (sockaddr*)&m_oListenAddress,
sizeof(m_oListenAddress) );
if( SOCKET_ERROR == iResult )
{
// send warning message
fprintf(stderr, _T("!!Could not bind(%s:%u)\n"),
DottedIP(_dwHBO_IP),
_HBO_Port);
PrintWSALastError();
// critical error
return(false);
}
iResult = listen(m_sListenSocket, _usMaxWaitingConnections );
if( SOCKET_ERROR == iResult )
{
// send warning message
fprintf(stderr, _T("!!Could not listen(%u:%u)\n"), m_sListenSocket,
_usMaxWaitingConnections);
PrintWSALastError();
// critical error
return(false);
}
m_bSocketInitialized = true;
fprintf(stderr, _T("Server listening on (%s:%u) with queue length %d\n"),
DottedIP(DefaultIP()),
_HBO_Port,
_usMaxWaitingConnections
);
return( m_bSocketInitialized );
}
//==================================================
void CServer::StartListening(void)
{
if( !m_bSocketInitialized )
{
fprintf(stderr,_T("Listening socket not yet initialized\n") );
return;
}
while(TRUE)
{
// this does not time out because the socket is blocking.
SOCKET sNewSocket = accept(m_sListenSocket, NULL, NULL);
if( SOCKET_ERROR == sNewSocket )
{
// record error message
fprintf(stderr,_T("Error on accept()\n") );
PrintWSALastError();
break; // quit from the loop and exit the server
}
// hand off the connection
DispatchConnection( sNewSocket );
}
}