vs2008 MFC 线程池实现的完整的 Client/Server 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)通讯。这里的好消息是,它操控一个通讯循环,并用一种高效的方式来报告数据和几个重要的事件。这些对你来说,任务就真正变得直截了当。

  1. template <typename T, size_t tBufferSize = 2048>  
  2. class SocketClientImpl  
  3. {  
  4.     typedef SocketClientImpl<T, tBufferSize> thisClass;  
  5. public:  
  6.     SocketClientImpl()  
  7.     : _pInterface(0)  
  8.     , _thread(0)  
  9.     {  
  10.     }  
  11.   
  12.     void SetInterface(T* pInterface)  
  13.     {  
  14.         ::InterlockedExchangePointer(reinterpret_cast<void**>(&_pInterface), pInterface);  
  15.     }  
  16.   
  17.     bool IsOpen() const;  
  18.     bool CreateSocket(LPCTSTR pszHost, LPCTSTR pszServiceName,  
  19.             int nFamily, int nType, UINT uOptions = 0);  
  20.     bool ConnectTo(LPCTSTR pszHostName, LPCTSTR pszRemote,  
  21.             LPCTSTR pszServiceName, int nFamily, int nType);  
  22.     void Close();  
  23.     DWORD Read(LPBYTE lpBuffer, DWORD dwSize,  
  24.         LPSOCKADDR lpAddrIn = NULL, DWORD dwTimeout = INFINITE);  
  25.     DWORD Write(const LPBYTE lpBuffer, DWORD dwCount,  
  26.         const LPSOCKADDR lpAddrIn = NULL, DWORD dwTimeout = INFINITE);  
  27.     bool StartClient(LPCTSTR pszHost, LPCTSTR pszRemote,  
  28.         LPCTSTR pszServiceName, int nFamily, int nType);  
  29.     void Run();  
  30.     void Terminate(DWORD dwTimeout = 5000L);  
  31.   
  32.     static bool IsConnectionDropped(DWORD dwError);  
  33.   
  34. protected:  
  35.     static DWORD WINAPI SocketClientProc(thisClass* _this);  
  36.     T*              _pInterface;  
  37.     HANDLE          _thread;  
  38.     CSocketHandle   _socket;  
  39. };  

客户端接口报告以下事件:

  1. class ISocketClientHandler  
  2. {  
  3. public:  
  4.     virtual void OnThreadBegin(CSocketHandle* ) {}  
  5.     virtual void OnThreadExit(CSocketHandle* ) {}  
  6.     virtual void OnDataReceived(CSocketHandle* , const BYTE* ,  
  7.                 DWORD , const SockAddrIn& ) {}  
  8.     virtual void OnConnectionDropped(CSocketHandle* ) {}  
  9.     virtual void OnConnectionError(CSocketHandle* , DWORD ) {}  
  10. };  

函数描述
OnThreadBegin线程启动时调用
OnThreadExit线程要退出时调用
OnDataReceived有新数据到来时调用
OnConnectionDropped侦测到一个错误时调用,错误是由网络连接断开或socket被关闭引起的。
OnConnectionError侦测到一个错误时调用

这个接口实际上是很随意的,即是可选的,你的程序可以这样实现:
  1. class CMyDialog : public CDialog  
  2. {  
  3.     typedef SocketClientImpl<CMyDialog> CSocketClient; // CMyDialog handles events!  
  4. public:  
  5.     CMyDialog(CWnd* pParent = NULL);   // standard constructor  
  6.     virtual CMyDialog ();  
  7.   
  8.     // ...  
  9.     void OnThreadBegin(CSocketHandle* ) {}  
  10.     void OnThreadExit(CSocketHandle* ) {}  
  11.     void OnDataReceived(CSocketHandle* , const BYTE* , DWORD , const SockAddrIn& ) {}  
  12.     void OnConnectionDropped(CSocketHandle* ) {}  
  13.     void OnConnectionError(CSocketHandle* , DWORD ) {}  
  14.   
  15. protected:  
  16.     CSocketClient m_SocketClient;  
  17. };  

 

SocketServerImpl

第二个模板SocketServerImpl处理服务端所有的通讯任务。在UDP模式下,它与客户端的行为非常相似。在TCP模式下,在一个单独的线程池内,它代理管理每一个网络连接。线程池模板是在MSDN下通过Kenny Kerr (^)发布的修改版本,你应该可以在你的工程中重用它,而不会有任何问题。其好处是它可以用以从一个线程池中调用一个类成员。回调可以是以下签名:

  1. void ThreadFunc();  
  2. void ThreadFunc(ULONG_PTR);  

要记住,为使用QueueUserWorkItem,你需要Windows 2000 或更高版本,除非你在使用Windows CE,否则一般是不会有问题的。我所知道的,已经没有人使用Windows 95/98了。:-)

  1. class ThreadPool  
  2. {  
  3.     static const int MAX_THREADS = 50;  
  4.     template <typename T>  
  5.     struct ThreadParam  
  6.     {  
  7.         void (T::* _function)(); T* _pobject;  
  8.         ThreadParam(void (T::* function)(), T * pobject)  
  9.         : _function(function), _pobject(pobject) { }  
  10.     };  
  11. public:  
  12.     template <typename T>  
  13.     static bool QueueWorkItem(void (T::*function)(),  
  14.                                   T * pobject, ULONG nFlags = WT_EXECUTEDEFAULT)  
  15.     {  
  16.         std::auto_ptr< ThreadParam<T> > p(new ThreadParam<T>(function, pobject) );  
  17.         WT_SET_MAX_THREADPOOL_THREADS(nFlags, MAX_THREADS);  
  18.         bool result = false;  
  19.         if (::QueueUserWorkItem(WorkerThreadProc<T>,  
  20.                                 p.get(),  
  21.                                 nFlags))  
  22.         {  
  23.             p.release();  
  24.             result = true;  
  25.         }  
  26.         return result;  
  27.     }  
  28.   
  29. private:  
  30.     template <typename T>  
  31.     static DWORD WINAPI WorkerThreadProc(LPVOID pvParam)  
  32.     {  
  33.         std::auto_ptr< ThreadParam<T> > p(static_cast< ThreadParam<T>* >(pvParam));  
  34.         try {  
  35.             (p->_pobject->*p->_function)();  
  36.         }  
  37.         catch(...) {}  
  38.         return 0;  
  39.     }  
  40.   
  41.     ThreadPool();  
  42. };  

 

  1. template <typename T, size_t tBufferSize = 2048>  
  2. class SocketServerImpl  
  3. {  
  4.     typedef SocketServerImpl<T, tBufferSize> thisClass;  
  5. public:  
  6.     SocketServerImpl()  
  7.     : _pInterface(0)  
  8.     , _thread(0)  
  9.     {  
  10.     }  
  11.   
  12.     void SetInterface(T* pInterface)  
  13.     {  
  14.         ::InterlockedExchangePointer(reinterpret_cast<void**>  
  15.                     (&_pInterface), pInterface);  
  16.     }  
  17.   
  18.     bool IsOpen() const  
  19.     bool CreateSocket(LPCTSTR pszHost,  
  20.         LPCTSTR pszServiceName, int nFamily, int nType, UINT uOptions);  
  21.     void Close();  
  22.     DWORD Read(LPBYTE lpBuffer, DWORD dwSize,  
  23.         LPSOCKADDR lpAddrIn, DWORD dwTimeout);  
  24.     DWORD Write(const LPBYTE lpBuffer, DWORD dwCount,  
  25.         const LPSOCKADDR lpAddrIn, DWORD dwTimeout);  
  26.     bool Lock()  
  27.     {  
  28.         return _critSection.Lock();  
  29.     }  
  30.   
  31.     bool Unlock()  
  32.     {  
  33.         return _critSection.Unlock();  
  34.     }  
  35.   
  36.     bool CloseConnection(SOCKET sock);  
  37.     void CloseAllConnections();  
  38.     bool StartServer(LPCTSTR pszHost,  
  39.         LPCTSTR pszServiceName, int nFamily, int nType, UINT uOptions);  
  40.     void Run();  
  41.     void Terminate(DWORD dwTimeout);  
  42.     void OnConnection(ULONG_PTR s);  
  43.   
  44.     static bool IsConnectionDropped(DWORD dwError);  
  45.   
  46. protected:  
  47.     static DWORD WINAPI SocketServerProc(thisClass* _this);  
  48.     T*              _pInterface;  
  49.     HANDLE          _thread;  
  50.     ThreadSection   _critSection;  
  51.     CSocketHandle   _socket;  
  52.     SocketList      _sockets;  
  53. };  

服务器接口报告以下事件:

  1. class ISocketServerHandler  
  2. {  
  3. public:  
  4.     virtual void OnThreadBegin(CSocketHandle* ) {}  
  5.     virtual void OnThreadExit(CSocketHandle* )  {}  
  6.     virtual void OnThreadLoopEnter(CSocketHandle* ) {}  
  7.     virtual void OnThreadLoopLeave(CSocketHandle* ) {}  
  8.     virtual void OnAddConnection(CSocketHandle* , SOCKET ) {}  
  9.     virtual void OnRemoveConnection(CSocketHandle* , SOCKET ) {}  
  10.     virtual void OnDataReceived  
  11.     (CSocketHandle* , const BYTE* , DWORD , const SockAddrIn& ) {}  
  12.     virtual void OnConnectionFailure(CSocketHandle*, SOCKET) {}  
  13.     virtual void OnConnectionDropped(CSocketHandle* ) {}  
  14.     virtual void OnConnectionError(CSocketHandle* , DWORD ) {}  
  15. };  

这个接口也是可选的,但我希望你使用它,以使设计更干净。

异步Socket

Windows支持异步socket,你可以使用通讯类CSocketHandle来实现。你需要为你的工程定义SOCKHANDLE_USE_OVERLAPPED,异步通讯是非阻塞模式,因此它允许你在单个线程中处理多个请求,你也可能需要多个“读/写”缓冲区来等待I/O。异步socket是个大课题,可能需要单独一篇文章来讨论它,但我希望你考虑一下当前支持的这个设计,函数CSocketHandle::ReadEx 和CSocketHandle::WriteEx 让你可以使用这种模式,最新的模板ASocketServerImpl展示了在异步读取模式下如何使用SocketHandle类。主要的好处是,在TCP模式下,用一个线程处理所有的网络连接。

结论

本文中,我向你介绍了CSocketHandle类的新改进,我希望这个新接口让你工作更容易。当然,我一直广纳谏言,你可以自由地就此议题提出问题和建议。

参考文献

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Socket通讯是一种网络通信协议,用于实现不同计算机之间的数据传输。MFC是微软公司开发的一套基于C++的图形化应用程序框架,用于Windows操作系统上的用户界面设计和开发。 在MFC中,可以使用CAsyncSocket实现Socket通信。下面是一个简单的例子: 1. 创建一个MFC应用程序项目。 2. 在该项目的对话框中添加一个按钮控件,并为其添加一个响应函数。 3. 在响应函数中创建一个CAsyncSocket对象,并调用它的Create函数来创建一个Socket对象。 4. 设置Socket对象的通信协议、IP地址和端口号。 5. 调用Socket对象的Connect函数来连接到目标计算机。 6. 调用Socket对象的Send函数来发送数据。 7. 调用Socket对象的Receive函数来接收数据。 8. 关闭Socket对象。 下面是一个示例代码: void CMyDialog::OnButton1() { // 创建Socket对象 CAsyncSocket sock; sock.Create(); // 设置通信协议 sock.SetSockOpt(SO_REUSEADDR); // 设置IP地址和端口号 CString strIP = _T("127.0.0.1"); UINT nPort = 1234; sock.Bind(nPort, strIP); // 连接到目标计算机 strIP = _T("192.168.0.100"); nPort = 5678; sock.Connect(strIP, nPort); // 发送数据 CString strSend = _T("Hello, World!"); sock.Send(strSend, strSend.GetLength()); // 接收数据 CString strRecv; char buf[1024]; int nRecv = sock.Receive(buf, 1024); if (nRecv > 0) { strRecv = CString(buf, nRecv); } // 关闭Socket对象 sock.Close(); } 以上代码演示了如何使用MFC中的CAsyncSocket实现Socket通信。在实际应用中,可以根据实际需求来设置通信协议、IP地址和端口号,并使用Send和Receive函数来进行数据传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值