Win32 Socket Class

Win32 Socket Class

SocketServer.zip - 261Kb
SocketHandle.zip - 52Kb
SocketPort_Linux.zip - 14Kb
SocketHandle1_3.zip - 48Kb
SocketPort_Linux1_3.zip - 13Kb

Socket Server
Socket Client
 
To run the application as client, type SocketServer.exe /client from command prompt.

Introduction

This is an updated version of the Socket communication class that I released a couple of years ago. While the interface CSocketHandle is quite stable and easy to use, one has to admit that some of the initial design decisions to keep the communication interface intact is starting to be an issue for newer development.
This is the goal of this article, I present the new and improved version of the communication class and show how you can take advantage of thread pooling to increase performance for your network solutions.

Description

First, I assume you are already familiar with socket programming and have several years of experiences under your belt. If not the case, I highly recommend some links that you will find in the reference section that may guide you along the way. For those who are "all fired up and ready to go", please read on, I will try to shed some lights on how you can use the new classes to enhance the performance of your system.

Synchronous Sockets

By default, sockets operate in blocking mode, that means you will need a dedicate thread to read/wait for data while another one will write/send data on the other side. This is now made easier for you by using the new template class. Typically a client needs only one thread, so there is no problem there but if you are developing server components and need reliable communication or point-to-point link with your clients, sooner or later you will find that multiple threads are needed to handle your requests.

SocketClientImpl
The first template SocketClientImpl encapsulates socket communication from a client perspective. It can be used to communicate using TCP (SOCK_STREAM) or UDP (SOCK_DGRAM). The good news here is that it handles the communication loop and will report data and several important events in an efficient manner. All this to make the task really straightforward for you.
template <typename T, size_t tBufferSize = 2048>
class SocketClientImpl
{
    typedef SocketClientImpl<T, tBufferSize> thisClass;
public:
    SocketClientImpl()
    : _pInterface(0)
    , _thread(0)
    {
    }

    void SetInterface(T* pInterface)
    {
        ::InterlockedExchangePointer(reinterpret_cast<VOID**>(&_pInterface), pInterface);
    }

    bool IsOpen() const;
    bool CreateSocket(LPCTSTR pszHost, LPCTSTR pszServiceName, int nFamily, int nType, UINT uOptions);
    bool ConnectTo(LPCTSTR pszHostName, LPCTSTR pszRemote, LPCTSTR pszServiceName, int nFamily, int nType);
    void Close();
    DWORD Read(LPBYTE lpBuffer, DWORD dwSize, LPSOCKADDR lpAddrIn, DWORD dwTimeout);
    DWORD Write(const LPBYTE lpBuffer, DWORD dwCount, const LPSOCKADDR lpAddrIn, DWORD dwTimeout);
    bool StartClient(LPCTSTR pszHost, LPCTSTR pszRemote, LPCTSTR pszServiceName, int nFamily, int nType);
    void Run();
    void Terminate(DWORD dwTimeout);

    static bool IsConnectionDropped(DWORD dwError);

protected:
    static DWORD WINAPI SocketClientProc(thisClass* _this);
    T*              _pInterface;
    HANDLE          _thread;
    CSocketHandle   _socket;
};
The client interface reports the following events:
class ISocketClientHandler
{
public:
    virtual void OnThreadBegin(CSocketHandle* ) {}
    virtual void OnThreadExit(CSocketHandle* ) {}
    virtual void OnDataReceived(CSocketHandle* , const BYTE* , DWORD , const SockAddrIn& ) {}
    virtual void OnConnectionDropped(CSocketHandle* ) {}
    virtual void OnConnectionError(CSocketHandle* , DWORD ) {}
};

Function Description
OnThreadBegin Called when thread starts
OnThreadExit Called when thread is about to exit
OnDataReceived Called when new data arrived
OnConnectionDropped Called when an error is detected. The error is caused by loss or connection or socket being closed.
OnConnectionError Called when an error is detected.

This interface is in fact quite optional, your program can be implemented as this:
class CMyDialog : public CDialog
{
    typedef SocketClientImpl<CMyDialog> CSocketClient; // CMyDialog handles events!
public:
    CMyDialog(CWnd* pParent = NULL);   // standard constructor
    virtual CMyDialog ();

    // ...
    void OnThreadBegin(CSocketHandle* ) {}
    void OnThreadExit(CSocketHandle* ) {}
    void OnDataReceived(CSocketHandle* , const BYTE* , DWORD , const SockAddrIn& ) {}
    void OnConnectionDropped(CSocketHandle* ) {}
    void OnConnectionError(CSocketHandle* , DWORD ) {}

protected:
    CSocketClient m_SocketClient;
};

SocketServerImpl
The second template SocketServerImpl handles all the communication tasks from a server perspective. In UDP mode, it behaves pretty much the same way as for the client. In TCP, it delegates the management for each connection in a separate pooling thread. The pooling thread template is a modified version that was published under MSDN by Kenny Kerr. You should be able to reuse it in your project without any issue. The good thing is it can be used to call class member from a thread pool. Callbacks can have the following signature:
    void ThreadFunc();
    void ThreadFunc(ULONG_PTR);
Remember, you need Windows 2000 or higher to use QueueUserWorkItem. That should not a be problem unless you are targeting Windows CE. I was told no one uses Windows 95/98 anymore! :-)
class ThreadPool
{
    static const int MAX_THREADS = 50;
    template <typename T>
    struct ThreadParam
    {
        void (T::* _function)(); T* _pobject;
        ThreadParam(void (T::* function)(), T * pobject)
        : _function(function), _pobject(pobject) { }
    };
public:
    template <typename T>
    static bool QueueWorkItem(void (T::*function)(),
                                  T * pobject, ULONG nFlags = WT_EXECUTELONGFUNCTION)
    {
        std::auto_ptr< ThreadParam<T> > p(new ThreadParam<T>(function, pobject) );
        WT_SET_MAX_THREADPOOL_THREADS(nFlags, MAX_THREADS);
        bool result = false;
        if (::QueueUserWorkItem(WorkerThreadProc<T>,
                                p.get(),
                                nFlags))
        {
            p.release();
            result = true;
        }
        return result;
    }

private:
    template <typename T>
    static DWORD WINAPI WorkerThreadProc(LPVOID pvParam)
    {
        std::auto_ptr< ThreadParam<T> > p(static_cast< ThreadParam<T>* >(pvParam));
        try {
            (p->_pobject->*p->_function)();
        }
        catch(...) {}
        return 0;
    }

    ThreadPool();
};
template <typename T, size_t tBufferSize = 2048>
class SocketServerImpl
{
    typedef SocketServerImpl<T, tBufferSize> thisClass;
public:
    SocketServerImpl()
    : _pInterface(0)
    , _thread(0)
    {
    }

    void SetInterface(T* pInterface)
    {
        ::InterlockedExchangePointer(reinterpret_cast<void**>(&_pInterface), pInterface);
    }

    bool IsOpen() const
    bool CreateSocket(LPCTSTR pszHost, LPCTSTR pszServiceName, int nFamily, int nType, UINT uOptions);
    void Close();
    DWORD Read(LPBYTE lpBuffer, DWORD dwSize, LPSOCKADDR lpAddrIn, DWORD dwTimeout);
    DWORD Write(const LPBYTE lpBuffer, DWORD dwCount, const LPSOCKADDR lpAddrIn, DWORD dwTimeout);
    bool Lock()
    {
        return _critSection.Lock();
    }

    bool Unlock()
    {
        return _critSection.Unlock();
    }

    bool CloseConnection(SOCKET sock);
    void CloseAllConnections();
    bool StartServer(LPCTSTR pszHost, LPCTSTR pszServiceName, int nFamily, int nType, UINT uOptions);
    void Run();
    void Terminate(DWORD dwTimeout);
    void OnConnection(ULONG_PTR s);

    static bool IsConnectionDropped(DWORD dwError);

protected:
    static DWORD WINAPI SocketServerProc(thisClass* _this);
    T*              _pInterface;
    HANDLE          _thread;
    ThreadSection   _critSection;
    CSocketHandle   _socket;
    SocketList      _sockets;
};
The server interface reports the following events:
class ISocketServerHandler
{
public:
    virtual void OnThreadBegin(CSocketHandle* ) {}
    virtual void OnThreadExit(CSocketHandle* )  {}
    virtual void OnThreadLoopEnter(CSocketHandle* ) {}
    virtual void OnThreadLoopLeave(CSocketHandle* ) {}
    virtual void OnAddConnection(CSocketHandle* , SOCKET ) {}
    virtual void OnRemoveConnection(CSocketHandle* , SOCKET ) {}
    virtual void OnDataReceived(CSocketHandle* , const BYTE* , DWORD , const SockAddrIn& ) {}
    virtual void OnConnectionFailure(CSocketHandle*, SOCKET) {}
    virtual void OnConnectionDropped(CSocketHandle* ) {}
    virtual void OnConnectionError(CSocketHandle* , DWORD ) {}
};
This interface is also optional but I hope you will decide to use it as it makes the design cleaner.

Asynchronous Sockets

Windows supports asynchronous sockets. The communication class CSocketHandle makes it accessible to you as well. You will need to define SOCKHANDLE_USE_OVERLAPPED for your project. Asynchronous communication is a non-blocking mode, thus, allows you to handle multiple requests in a single thread. You may also provide multiple read/write buffers to queue your I/O. Asynchronous sockets is a big subject, probably deserves an article by itself but I hope you will consider the design that is currently supported. The functions CSocketHandle::ReadEx and CSocketHandle::WriteEx give you access to this mode. The latest template ASocketServerImpl shows how to use CSocketHandle class in Asynchronous read mode. The main advantage is that in TCP mode, one thread is used to handle all your connections.

Conclusion

In this article, I present the new improvements for the CSocketHandle class. I hope the new interface will make it easier for you. Of course, I'm always open to suggestions, feel free to use the feedback page to send me your questions and suggestions.
Enjoy!

Reference

Windows Sockets
Berkeley Sockets (Wikipedia)
ThreadPool template

History

02/12/2009: Update article with latest demo for CSocketHandle
02/17/2009: Updated Threadpool startup flag for Windows XP
03/14/2009: Fixed hang issue (99% CPU) in templates
03/29/2009: Asynchronous mode Server template (+ Support: Windows CE, UNIX/Linux)
04/05/2009: Fixed resource leak in server templates
08/07/2009: Fixed Asynchronous mode build (use SOCKHANDLE_USE_OVERLAPPED)
09/26/2009: IPv6 Support (Windows and Linux, not Cygwin)
0 Comments