Click here to Skip to main content
15,867,308 members
Articles / Mobile Apps / Windows Mobile

Full Multi-thread Client/Server Socket Class with ThreadPool

Rate me:
Please Sign up or sign in to vote.
4.92/5 (115 votes)
27 Sep 2009Apache4 min read 841.1K   35.4K   413   240
Complete Client/Server Socket Communication class with threadpool implementation. Easy to use and integrate into C++ application. Linux/UNIX port available.
Server Socket App - Screenshot

Client Socket App - Screenshot

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

Introduction

Recently, I updated one of my first articles here at The Code Project, the ServerSocket. While the base class (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 are 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 that you are already familiar with socket programming and have several years of experience under your belt. If that is 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 light 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 dedicated 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 you need multiple threads 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.

C++
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 = 0);
    bool ConnectTo(LPCTSTR pszHostName, LPCTSTR pszRemote,
			LPCTSTR pszServiceName, int nFamily, int nType);
    void Close();
    DWORD Read(LPBYTE lpBuffer, DWORD dwSize,
		LPSOCKADDR lpAddrIn = NULL, DWORD dwTimeout = INFINITE);
    DWORD Write(const LPBYTE lpBuffer, DWORD dwCount,
		const LPSOCKADDR lpAddrIn = NULL, DWORD dwTimeout = INFINITE);
    bool StartClient(LPCTSTR pszHost, LPCTSTR pszRemote,
		LPCTSTR pszServiceName, int nFamily, int nType);
    void Run();
    void Terminate(DWORD dwTimeout = 5000L);

    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:

C++
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 ) {}
};
FunctionDescription
OnThreadBeginCalled when thread starts
OnThreadExitCalled when thread is about to exit
OnDataReceivedCalled when new data arrives
OnConnectionDroppedCalled when an error is detected. The error is caused by loss of connection or socket being closed.
OnConnectionErrorCalled when an error is detected.

This interface is in fact quite optional, your program can be implemented as this:

C++
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 a class member from a thread pool. Callbacks can have the following signature:

C++
void ThreadFunc();
void ThreadFunc(ULONG_PTR);

Remember, you need Windows 2000 or higher to use QueueUserWorkItem. That should not be a problem unless you are targeting Windows CE. I was told no one uses Windows 95/98 anymore! :-)

C++
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_EXECUTEDEFAULT)
    {
        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();
};
C++
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:

C++
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 SocketHandle 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 post your questions and suggestions to the discussion thread.
Enjoy!

Reference

History

  • 02/12/2009 - Initial release (published as separate article)
  • 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: WindowsCE, 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)

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Software Developer (Senior)
United States United States
Ernest is a multi-discipline software engineer.
Skilled at software design and development for all Windows platforms.
-
MCSD (C#, .NET)
Interests: User Interface, GDI/GDI+, Scripting, Android, iOS, Windows Mobile.
Programming Skills: C/C++, C#, Java (Android), VB and ASP.NET.

I hope you will enjoy my contributions.

Comments and Discussions

 
Generalalso cpu 100% when send >1M data Pin
NewttleLi26-Mar-10 3:28
NewttleLi26-Mar-10 3:28 
GeneralRe: also cpu 100% when send >1M data Pin
Ernest Laurentin26-Mar-10 11:09
Ernest Laurentin26-Mar-10 11:09 
GeneralRe: also cpu 100% when send >1M data Pin
NewttleLi26-Mar-10 15:29
NewttleLi26-Mar-10 15:29 
GeneralRe: also cpu 100% when send >1M data Pin
NewttleLi28-Mar-10 1:40
NewttleLi28-Mar-10 1:40 
GeneralRe: also cpu 100% when send >1M data Pin
Ernest Laurentin28-Mar-10 5:18
Ernest Laurentin28-Mar-10 5:18 
GeneralMy vote of 2 Pin
oren.shnitzer22-Mar-10 1:19
oren.shnitzer22-Mar-10 1:19 
GeneralMy vote of 1 Pin
spinoza2-Mar-10 7:41
spinoza2-Mar-10 7:41 
GeneralRe: My vote of 1 Pin
Ernest Laurentin26-Mar-10 11:07
Ernest Laurentin26-Mar-10 11:07 
My article is not about best way to handle performance issues.
I thought you tried to write about it in your own article.
Is this the real reason to vote 1? You know, you should leave these voting 1 to other people.
QuestionHow to configure client/server for sending/receiving broadcast packets (using SO_BROADCAST) Pin
David Schumaker25-Feb-10 11:17
David Schumaker25-Feb-10 11:17 
QuestionGreat Job . Q:detect an abnormal network disconnect In Vista . Pin
selaShemi19-Jan-10 6:51
selaShemi19-Jan-10 6:51 
QuestionClient download ? Pin
dnybz24-Nov-09 6:48
dnybz24-Nov-09 6:48 
AnswerRe: Client download ? Pin
mg2000028-Jan-10 18:42
mg2000028-Jan-10 18:42 
QuestionHowTo compile to a DLL for use in VS2008 C# project Pin
pbisiac17-Nov-09 20:28
pbisiac17-Nov-09 20:28 
QuestionHow can I modify this program to use binary send / receive ? Pin
soyoja1-Nov-09 15:14
soyoja1-Nov-09 15:14 
GeneralI meet a problem, please help me! :) Pin
anhhtu28-Oct-09 17:35
anhhtu28-Oct-09 17:35 
GeneralRe: I meet a problem, please help me! :) Pin
Ernest Laurentin30-Oct-09 11:33
Ernest Laurentin30-Oct-09 11:33 
GeneralBroadcast data, small problem. [modified] Pin
Zeus200827-Oct-09 8:00
Zeus200827-Oct-09 8:00 
GeneralRe: Broadcast data, small problem. Pin
Ernest Laurentin30-Oct-09 11:28
Ernest Laurentin30-Oct-09 11:28 
GeneralCSocketHandle::GetAddressInfo() not compiling... Pin
sunghyuck kim26-Oct-09 17:59
sunghyuck kim26-Oct-09 17:59 
GeneralRe: CSocketHandle::GetAddressInfo() not compiling... Pin
Ernest Laurentin26-Oct-09 18:25
Ernest Laurentin26-Oct-09 18:25 
GeneralIPv6 Support (Windows and Linux) in the latest revision? It is great!! Pin
Tage Lejon28-Sep-09 22:54
Tage Lejon28-Sep-09 22:54 
GeneralRe: IPv6 Support (Windows and Linux) in the latest revision? It is great!! Pin
Ernest Laurentin29-Sep-09 3:23
Ernest Laurentin29-Sep-09 3:23 
General64bit issue - InterlockedExchange Pin
cmk28-Sep-09 7:47
cmk28-Sep-09 7:47 
GeneralRe: 64bit issue - InterlockedExchange [modified] Pin
Ernest Laurentin28-Sep-09 18:40
Ernest Laurentin28-Sep-09 18:40 
Generalnon-blocking sockets Pin
Rozis27-Sep-09 12:02
Rozis27-Sep-09 12:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.