Full Multi-thread Client/Server Socket Class with ThreadPool


Full Multi-thread Client/Server Socket Class with ThreadPool

Ernest Laurentin27 Sep 2009 

  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.

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:

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 arrives
OnConnectionDropped Called when an error is detected. The error is caused by loss of 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 a 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 be a problem unless you are targeting Windows CE. I was told no one uses Windows 95/98 anymore! Smile | :)

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();
};
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 functionsCSocketHandle::ReadEx and CSocketHandle::WriteEx give you access to this mode. The latest templateASocketServerImpl 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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@Override public HomePagePopupResp getPopupData(HomePagePopupReq req) throws BusinessException { HomePagePopupResp homePagePopupResp = new HomePagePopupResp(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); try { // 创建线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 20, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024)); // 添加任务 threadPool.submit(new Runnable() { @Override public void run() { // /api/project-m-sit01/srv/intelFive/queryNoFinishMeetTenDay 通过用户ID查询距离回收日期10天内的已电约完成且未面见完成的客户(每天进入app首页弹出一次) List<NoFinishThreeDayResp> queryNoFinishMeetTenDayList = intelFiveService.queryNoFinishMeetTenDay(req.getMeetNoFinishTenDayReq()); if (CollectionUtil.isNotEmpty(queryNoFinishMeetTenDayList)){ homePagePopupResp.setQueryNoFinishMeetTenDayList(queryNoFinishMeetTenDayList); } log.info(String.format("/outer/homePage/popup >>>>任务QueryNoFinishMeetTenDayList执行完成")); } }); threadPool.submit(new Runnable() { @Override public void run() { // /api/project-m-sit01/srv/intelFive/queryNoFinishThreeDay 通过用户ID查询距离回收日期3天内的未完成电约的客户(每天进入app首页弹出一次) List<NoFinishThreeDayResp> noFinishThreeDayRespList = intelFiveService.queryNoFinishThreeDay(req.getUserCustRecordReq()); if (CollectionUtil.isNotEmpty(noFinishThreeDayRespList)){ homePagePopupResp.setNoFinishThreeDayRespList(noFinishThreeDayRespList); } log.info(String.format("/outer/homePage/popup >>>>任务NoFinishThreeDayRespList执行完成")); } }); threadPool.submit(new Runnable() { @Override public void run() { ///api/assess-sit01/srv/assess/poster/popupPoster 弹出海报,登录APP时调用 Map listR = assessRemoteService.doPopupPoster(req.getPosterReq()); if (ObjectUtil.isNotNull(listR)){ homePagePopupResp.setPosterRespList(listR); } log.info(String.format("/outer/homePage/popup >>>>任务PosterRespList执行完成")); } }); threadPool.shutdown(); }catch (Exception e){ throw new BusinessException("/outer/homePage/popup 调用失败"); } // ///api/project-m-sit01/srv/smart5/queryNewsCount?isChange=1 查询未接来电弹框状态 // AppelManqueClickResp appelManqueClickResp = appelManqueService.queryNewsCount(req.getIsChange()); // homePagePopupResp.setAppelManqueClickResp(appelManqueClickResp); return homePagePopupResp; } 该段代码 子线程如何获取请求头参数
06-08

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值