#include "JetByteTools\Admin\Admin.h" #include "JetByteTools\SocketTools\WinsockWrapper.h" #include "JetByteTools\Win32Tools\Exception.h" #include "JetByteTools\Win32Tools\Utils.h" #include "JetByteTools\Win32Tools\DebugTrace.h" #include "SocketServer.h" #include "JetByteTools\SocketTools\FullAddress.h" #pragma hdrstop #include "JetByteTools\Win32Tools\StringConverter.h" #include "JetByteTools\Win32Tools\SEHException.h" #include "ServerCommon\IOPool.h" #include "ServerCommon\StreamSocketAllocator.h" #include "ServerCommon\BufferAllocator.h" #include "ServerCommon\CommandLine.h" #include "ServerCommon\SimpleServerShutdownHandler.h" #include "JetByteTools\IOTools\AsyncFileLog.h" #include <iostream> /////////////////////////////////////////////////////////////////////////////// // Using directives /////////////////////////////////////////////////////////////////////////////// using JetByteTools::Win32::_tstring; using JetByteTools::Win32::CException; using JetByteTools::Core::OutputEx; using JetByteTools::Win32::CDebugTrace; using JetByteTools::Win32::CSEHException; using JetByteTools::Win32::CStringConverter; using JetByteTools::Socket::CFullAddress; using JetByteTools::Socket::CConnectionLimiter; using JetByteTools::Socket::ListenBacklog; using JetByteTools::IO::CAsyncFileLog; using std::cerr; using std::endl; /////////////////////////////////////////////////////////////////////////////// // Program entry point /////////////////////////////////////////////////////////////////////////////// int main(int /*argc*/, char * /*argv[ ]*/) { CSEHException::Translator sehTranslator; try { CAsyncFileLog log(_T("EchoServer.log")); CDebugTrace::LogInstaller logInstaller(log); try { CCommandLine commandLine(_T("EchoServer"), CCommandLine::TCPServer); if (commandLine.Parse()) { CIOPool pool( commandLine.NumberOfIOThreads(), commandLine.DisplayDebug()); pool.Start(); CStreamSocketAllocator socketAllocator( commandLine.SocketPoolSize(), commandLine.SpinCount(), commandLine.DisplayDebug()); CBufferAllocator bufferAllocator( commandLine.BufferSize(), commandLine.BufferPoolSize(), commandLine.DisplayDebug()); const CFullAddress address( commandLine.Server(), commandLine.Port()); const ListenBacklog listenBacklog = commandLine.ListenBacklog(); CConnectionLimiter connectionLimiter(commandLine.MaxConnections()); CSocketServer server( commandLine.NoWelcomeMessage() ? "" : "Welcome to echo server\r\n", address, listenBacklog, pool, socketAllocator, bufferAllocator, connectionLimiter); server.Start(); server.StartAcceptingConnections(); CSimpleServerShutdownHandler shutdownHandler(server); shutdownHandler.WaitForShutdownRequest(); server.WaitForShutdownToComplete(); pool.WaitForShutdownToComplete(); bufferAllocator.Flush(); socketAllocator.ReleaseSockets(); } } catch(const CException &e) { OutputEx(_T("Exception: ") + e.GetDetails()); } catch(const CSEHException &e) { OutputEx(_T("SEH Exception: ") + e.GetDetails()); } catch(...) { OutputEx(_T("Unexpected exception")); } } catch(const CException &e) { const _tstring message = _T("Exception: ") + e.GetDetails(); cerr << CStringConverter::TtoA(message) << endl; } catch(const CSEHException &e) { const _tstring message = _T("SEH Exception: ") + e.GetDetails(); cerr << CStringConverter::TtoA(message) << endl; } catch(...) { cerr << "Unexpected exception" << endl; } return 0; }
std::cerr
and those that occur after our trace log is created to the trace log. EchoServer - v6.4 - Copyright (c) 2009 JetByte Limited Usage: EchoServer -port xxxx Command line parameters: r -port The port to listen on. o -server The server address to bind to either in IPv4 dotted IP or IPv6 hex formats. Defaults to IPv4 INADDR_ANY. o -spinCount The spin count used in per socket critical sections. o -numberOfIOThreads Defaults to 0 (2 x processors) o -socketPoolSize Defaults to 10. o -bufferPoolSize Defaults to 10. o -bufferSize Defaults to 1024 o -listenBacklog Defaults to 5. o -maxConnections Defaults to no limit o -noWelcomeMessage Does not display a welcome message o -displayDebug r = required, o = optional
-port XXX
to tell us which TCP port to listen on, the server will begin to construct the objects that it needs in order to run. -server 192.168.0.44
) or an IPv6 address (-server [ffff:ffff:ffff:ffff:ffff]
and the server will listen appropriately. listenBacklog
which is the maximum length of the queue of pending connections. If set to SOMAXCONN
, the underlying service provider responsible for the server's listening socket will set the backlog to a maximum reasonable value. Something small usually works well for most simple servers, and you can increase it if you find that your server is refusing connections. Note that this isn't the number of connections that your server can handle, it's just the number of connections that are queing to be accepted by the server, the server accepts connections very quickly and so this queue of pending connections can usually be quite small. If you use the Echo Server Test harness to stress test your server you can issue many hundreds of connections at the same time; this is an easy way to cause the server to fail due to insufficient listen backlog but is not necessarilly especially realistic. You can either batch the test harnesses connection attempts into "reasonable" sized batches and add a connection batch delay or you can increase the server's listen backlog. class CSocketServer : public JetByteTools::Socket::CStreamSocketServer, private CStreamSocketServerExCallback { public : CSocketServer( const std::string &welcomeMessage, const JetByteTools::Socket::IFullAddress &address, const JetByteTools::Socket::ListenBacklog listenBacklog, JetByteTools::IO::IIOPool &pool, JetByteTools::Socket::IAllocateStreamSockets &socketAllocator, JetByteTools::IO::IAllocateBuffers &bufferAllocator, JetByteTools::Socket::ILimitConnections &connectionLimiter = JetByteTools::Socket::CConnectionLimiter::NoLimitLimiter); ~CSocketServer(); private : // Implement just the bits of IStreamSocketServerCallback that we need virtual void OnConnectionEstablished( JetByteTools::Socket::IStreamSocket &socket, const JetByteTools::Socket::IAddress &address); virtual void OnReadCompleted( JetByteTools::Socket::IStreamSocket &socket, JetByteTools::IO::IBuffer &buffer); // Our business logic void EchoMessage( JetByteTools::Socket::IStreamSocket &socket, JetByteTools::IO::IBuffer &buffer) const; const std::string m_welcomeMessage; /// No copies do not implement CSocketServer(const CSocketServer &rhs); /// No copies do not implement CSocketServer &operator=(const CSocketServer &rhs); };
CSocketServer::~CSocketServer() { WaitForShutdownToComplete(); }
void CSocketServer::OnConnectionEstablished( IStreamSocket &socket, const IAddress & /*address*/) { Output(_T("OnConnectionEstablished")); if (socket.TryWrite(m_welcomeMessage.c_str(), GetStringLengthAsDWORD(m_welcomeMessage))) { socket.TryRead(); } }
/ref JetByteTools::Socket::CSocketServer::TryWrite "TryWrite()"
and /ref JetByteTools::Socket::CSocketServer::TryRead "TryRead()"
so that we don't throw an exception out of the callback if the read or write fails (which it can do if the client disconnects straight after connecting!). It doesn't actually matter if we allow exceptions to leak out of our callback handler as the framework will handle them for us. The normal processing of a socket connection's lifetime will deal correctly with any connections that close during connection establishment so, in this simple server at least, there's nothing we need to do or worry about. void CSocketServer::OnReadCompleted( IStreamSocket &socket, IBuffer &buffer) { try { EchoMessage(socket, buffer); socket.Read(); } catch(const CException &e) { Output(_T("ReadCompleted - Exception - ") + e.GetDetails()); socket.Shutdown(); } catch(...) { Output(_T("ReadCompleted - Unexpected exception")); socket.Shutdown(); } }
OnReadCompleted()
handler is called and we are given a buffer which contains the bytes that were read from the TCP stream. Now, remember, TCP connections are an unstructured stream of bytes, so this buffer may contain one or one hundred bytes, it doesn't matter that the client at the other end sent a "packet" of exactly 100 bytes in a single call to send, we can recieve any number of those bytes as the result of our read completing. We may, or may not, get the rest of the bytes later on; we probably will and, when testing on a LAN in your office you're unlikely to see too much packet fragmentation, but, you have to assume that every lump of data that is sent to you will arrive one byte at a time, each as the result of a separate call to OnReadCompleted()
. Our "business logic" is simply to echo bytes so we needn't worry about how many bytes we have read from the TCP stream. Other servers, such as the PacketEchoServer and the SimpleProtocolServer need to be more careful in how they accumulate bytes for processing. void CSocketServer::EchoMessage( IStreamSocket &socket, IBuffer &buffer) const { DEBUG_ONLY(Output(_T("Data Echoed -\r\n") + DumpData(buffer.GetMemory(), buffer.GetUsed(), 60, true))); socket.Write(buffer); }