完整的使用线程池的多线程C/S Socket类
翻译水平有限,不明之处请阅读原文。
原文:http://www.codeproject.com/Articles/33352/Full-Multi-thread-Client-Server-Socket-Class-with
使用线程池实现的完整的 Client/Server Socket通讯类,很容易使用,也很容易被集成到C++应用程序中。也适用于Linux/Unix。
在“Best C++/MFC article of February 2009”赛中获奖。
代码下载请到原文地址。
当做客户端运行时,在命令行中输入:SocketServer.exe /client
简介
最近我在Code Project中更新了一篇文章:ServerSocket。虽然其基类(CSocketHandle)非常稳定、易用,但必须承认,一些最初的用以保持通讯接口完整性的设计对新的开发来说开始成了一个问题。
本文将解决这个问题,我提出了一个新的、改进版本的通讯类,并向你展示如何使用线程池提高你的网络应用的性能。
描述
首先,我假设你已经熟悉了socket编程,并在你的工作领域有了几年的经验。如果不是这样,我强烈向你推荐几个链接,它们可能会对你有帮助。它们是“all fired up and ready to go”,请阅读。我将尽力说明如何使用这个新类,以增强你的系统性能。
同步Socket
默认地,socket工作在阻塞模式,这就意味着你需要一个专门的线程来read/wait数据,同时需要另一个线程来write/send数据。现在使用新的模板类就变得容易多了。
典型地,一个客户端只需要一个线程,所以不会有什么问题,但如果你在开发服务器组件,并需要可靠的通讯或者是连接客户端的P2P,你迟早会发现自己需要多线程来处理请求。
SocketClientImpl
第一个模板SocketClientImpl封装了客户端的socket通讯,它可以使用TCP(SOCK_STREAM)或UDP(SOCK_DGRAM)通讯。这里的好消息是,它操控一个通讯循环,并用一种高效的方式来报告数据和几个重要的事件。这些对你来说,任务就真正变得直截了当。
- 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;
- };
客户端接口报告以下事件:
- 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 ) {}
- };
函数 | 描述 |
OnThreadBegin | 线程启动时调用 |
OnThreadExit | 线程要退出时调用 |
OnDataReceived | 有新数据到来时调用 |
OnConnectionDropped | 侦测到一个错误时调用,错误是由网络连接断开或socket被关闭引起的。 |
OnConnectionError | 侦测到一个错误时调用 |
这个接口实际上是很随意的,即是可选的,你的程序可以这样实现:
- 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
第二个模板SocketServerImpl处理服务端所有的通讯任务。在UDP模式下,它与客户端的行为非常相似。在TCP模式下,在一个单独的线程池内,它代理管理每一个网络连接。线程池模板是在MSDN下通过Kenny Kerr (^)发布的修改版本,你应该可以在你的工程中重用它,而不会有任何问题。其好处是它可以用以从一个线程池中调用一个类成员。回调可以是以下签名:
- void ThreadFunc();
- void ThreadFunc(ULONG_PTR);
要记住,为使用QueueUserWorkItem,你需要Windows 2000 或更高版本,除非你在使用Windows CE,否则一般是不会有问题的。我所知道的,已经没有人使用Windows 95/98了。:-)
- 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;
- };
服务器接口报告以下事件:
- 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 ) {}
- };
这个接口也是可选的,但我希望你使用它,以使设计更干净。
异步Socket
Windows支持异步socket,你可以使用通讯类CSocketHandle来实现。你需要为你的工程定义SOCKHANDLE_USE_OVERLAPPED,异步通讯是非阻塞模式,因此它允许你在单个线程中处理多个请求,你也可能需要多个“读/写”缓冲区来等待I/O。异步socket是个大课题,可能需要单独一篇文章来讨论它,但我希望你考虑一下当前支持的这个设计,函数CSocketHandle::ReadEx 和 CSocketHandle::WriteEx 让你可以使用这种模式,最新的模板ASocketServerImpl展示了在异步读取模式下如何使用SocketHandle类。主要的好处是,在TCP模式下,用一个线程处理所有的网络连接。
结论
本文中,我向你介绍了CSocketHandle类的新改进,我希望这个新接口让你工作更容易。当然,我一直广纳谏言,你可以自由地就此议题提出问题和建议。