IOCPModel.h 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*
  2. ==========================================================================
  3. Purpose:
  4. * 这个类CIOCPModel是本代码的核心类,用于说明WinSock服务器端编程模型中的
  5. 完成端口(IOCP)的使用方法,并使用MFC对话框程序来调用这个类实现了基本的
  6. 服务器网络通信的功能。
  7. * 其中的PER_IO_DATA结构体是封装了用于每一个重叠操作的参数
  8. PER_HANDLE_DATA 是封装了用于每一个Socket的参数,也就是用于每一个完成端口的参数
  9. * 详细的文档说明请参考 http://blog.csdn.net/PiggyXP
  10. Notes:
  11. * 具体讲明了服务器端建立完成端口、建立工作者线程、投递Recv请求、投递Accept请求的方法,
  12. 所有的客户端连入的Socket都需要绑定到IOCP上,所有从客户端发来的数据,都会实时显示到
  13. 主界面中去。
  14. Author:
  15. * PiggyXP【小猪】
  16. Date:
  17. * 2009/10/04
  18. ==========================================================================
  19. */
  20. #pragma once
  21. // winsock 2 的头文件和库
  22. #include <winsock2.h>
  23. #include <MSWSock.h>
  24. #pragma comment(lib,"ws2_32.lib")
  25. // 缓冲区长度 (1024*8)
  26. // 之所以为什么设置8K,也是一个江湖上的经验值
  27. // 如果确实客户端发来的每组数据都比较少,那么就设置得小一些,省内存
  28. #define MAX_BUFFER_LEN 8192
  29. // 默认端口
  30. #define DEFAULT_PORT 12345
  31. // 默认IP地址
  32. #define DEFAULT_IP _T("127.0.0.1")
  33. //////////////////////////////////////////////////////////////////
  34. // 在完成端口上投递的I/O操作的类型
  35. typedef enum _OPERATION_TYPE
  36. {
  37. ACCEPT_POSTED, // 标志投递的Accept操作
  38. SEND_POSTED, // 标志投递的是发送操作
  39. RECV_POSTED, // 标志投递的是接收操作
  40. NULL_POSTED // 用于初始化,无意义
  41. }OPERATION_TYPE;
  42. //====================================================================================
  43. //
  44. // 单IO数据结构体定义(用于每一个重叠操作的参数)
  45. //
  46. //====================================================================================
  47. typedef struct _PER_IO_CONTEXT
  48. {
  49. OVERLAPPED m_Overlapped; // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)
  50. SOCKET m_sockAccept; // 这个网络操作所使用的Socket
  51. WSABUF m_wsaBuf; // WSA类型的缓冲区,用于给重叠操作传参数的
  52. char m_szBuffer[MAX_BUFFER_LEN]; // 这个是WSABUF里具体存字符的缓冲区
  53. OPERATION_TYPE m_OpType; // 标识网络操作的类型(对应上面的枚举)
  54. // 初始化
  55. _PER_IO_CONTEXT()
  56. {
  57. ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));
  58. ZeroMemory( m_szBuffer,MAX_BUFFER_LEN );
  59. m_sockAccept = INVALID_SOCKET;
  60. m_wsaBuf.buf = m_szBuffer;
  61. m_wsaBuf.len = MAX_BUFFER_LEN;
  62. m_OpType = NULL_POSTED;
  63. }
  64. // 释放掉Socket
  65. ~_PER_IO_CONTEXT()
  66. {
  67. if( m_sockAccept!=INVALID_SOCKET )
  68. {
  69. closesocket(m_sockAccept);
  70. m_sockAccept = INVALID_SOCKET;
  71. }
  72. }
  73. // 重置缓冲区内容
  74. void ResetBuffer()
  75. {
  76. ZeroMemory( m_szBuffer,MAX_BUFFER_LEN );
  77. }
  78. } PER_IO_CONTEXT, *PPER_IO_CONTEXT;
  79. //====================================================================================
  80. //
  81. // 单句柄数据结构体定义(用于每一个完成端口,也就是每一个Socket的参数)
  82. //
  83. //====================================================================================
  84. typedef struct _PER_SOCKET_CONTEXT
  85. {
  86. SOCKET m_Socket; // 每一个客户端连接的Socket
  87. SOCKADDR_IN m_ClientAddr; // 客户端的地址
  88. CArray<_PER_IO_CONTEXT*> m_arrayIoContext; // 客户端网络操作的上下文数据,
  89. // 也就是说对于每一个客户端Socket,是可以在上面同时投递多个IO请求的
  90. // 初始化
  91. _PER_SOCKET_CONTEXT()
  92. {
  93. m_Socket = INVALID_SOCKET;
  94. memset(&m_ClientAddr, 0, sizeof(m_ClientAddr));
  95. }
  96. // 释放资源
  97. ~_PER_SOCKET_CONTEXT()
  98. {
  99. if( m_Socket!=INVALID_SOCKET )
  100. {
  101. closesocket( m_Socket );
  102. m_Socket = INVALID_SOCKET;
  103. }
  104. // 释放掉所有的IO上下文数据
  105. for( int i=0;i<m_arrayIoContext.GetCount();i++ )
  106. {
  107. delete m_arrayIoContext.GetAt(i);
  108. }
  109. m_arrayIoContext.RemoveAll();
  110. }
  111. // 获取一个新的IoContext
  112. _PER_IO_CONTEXT* GetNewIoContext()
  113. {
  114. _PER_IO_CONTEXT* p = new _PER_IO_CONTEXT;
  115. m_arrayIoContext.Add( p );
  116. return p;
  117. }
  118. // 从数组中移除一个指定的IoContext
  119. void RemoveContext( _PER_IO_CONTEXT* pContext )
  120. {
  121. ASSERT( pContext!=NULL );
  122. for( int i=0;i<m_arrayIoContext.GetCount();i++ )
  123. {
  124. if( pContext==m_arrayIoContext.GetAt(i) )
  125. {
  126. delete pContext;
  127. pContext = NULL;
  128. m_arrayIoContext.RemoveAt(i);
  129. break;
  130. }
  131. }
  132. }
  133. } PER_SOCKET_CONTEXT, *PPER_SOCKET_CONTEXT;
  134. //====================================================================================
  135. //
  136. // CIOCPModel类定义
  137. //
  138. //====================================================================================
  139. // 工作者线程的线程参数
  140. class CIOCPModel;
  141. typedef struct _tagThreadParams_WORKER
  142. {
  143. CIOCPModel* pIOCPModel; // 类指针,用于调用类中的函数
  144. int nThreadNo; // 线程编号
  145. } THREADPARAMS_WORKER,*PTHREADPARAM_WORKER;
  146. // CIOCPModel类
  147. class CIOCPModel
  148. {
  149. public:
  150. CIOCPModel(void);
  151. ~CIOCPModel(void);
  152. public:
  153. // 启动服务器
  154. bool Start();
  155. // 停止服务器
  156. void Stop();
  157. // 加载Socket库
  158. bool LoadSocketLib();
  159. // 卸载Socket库,彻底完事
  160. void UnloadSocketLib() { WSACleanup(); }
  161. // 获得本机的IP地址
  162. CString GetLocalIP();
  163. // 设置监听端口
  164. void SetPort( const int& nPort ) { m_nPort=nPort; }
  165. // 设置主界面的指针,用于调用显示信息到界面中
  166. void SetMainDlg( CDialog* p ) { m_pMain=p; }
  167. protected:
  168. // 初始化IOCP
  169. bool _InitializeIOCP();
  170. // 初始化Socket
  171. bool _InitializeListenSocket();
  172. // 最后释放资源
  173. void _DeInitialize();
  174. // 投递Accept请求
  175. bool _PostAccept( PER_IO_CONTEXT* pAcceptIoContext );
  176. // 投递接收数据请求
  177. bool _PostRecv( PER_IO_CONTEXT* pIoContext );
  178. // 在有客户端连入的时候,进行处理
  179. bool _DoAccpet( PER_SOCKET_CONTEXT* pSocketContext, PER_IO_CONTEXT* pIoContext );
  180. // 在有接收的数据到达的时候,进行处理
  181. bool _DoRecv( PER_SOCKET_CONTEXT* pSocketContext, PER_IO_CONTEXT* pIoContext );
  182. // 将客户端的相关信息存储到数组中
  183. void _AddToContextList( PER_SOCKET_CONTEXT *pSocketContext );
  184. // 将客户端的信息从数组中移除
  185. void _RemoveContext( PER_SOCKET_CONTEXT *pSocketContext );
  186. // 清空客户端信息
  187. void _ClearContextList();
  188. // 将句柄绑定到完成端口中
  189. bool _AssociateWithIOCP( PER_SOCKET_CONTEXT *pContext);
  190. // 处理完成端口上的错误
  191. bool HandleError( PER_SOCKET_CONTEXT *pContext,const DWORD& dwErr );
  192. // 线程函数,为IOCP请求服务的工作者线程
  193. static DWORD WINAPI _WorkerThread(LPVOID lpParam);
  194. // 获得本机的处理器数量
  195. int _GetNoOfProcessors();
  196. // 判断客户端Socket是否已经断开
  197. bool _IsSocketAlive(SOCKET s);
  198. // 在主界面中显示信息
  199. void _ShowMessage( const CString szFormat,...) const;
  200. private:
  201. HANDLE m_hShutdownEvent; // 用来通知线程系统退出的事件,为了能够更好的退出线程
  202. HANDLE m_hIOCompletionPort; // 完成端口的句柄
  203. HANDLE* m_phWorkerThreads; // 工作者线程的句柄指针
  204. int m_nThreads; // 生成的线程数量
  205. CString m_strIP; // 服务器端的IP地址
  206. int m_nPort; // 服务器端的监听端口
  207. CDialog* m_pMain; // 主界面的界面指针,用于在主界面中显示消息
  208. CRITICAL_SECTION m_csContextList; // 用于Worker线程同步的互斥量
  209. CArray<PER_SOCKET_CONTEXT*> m_arrayClientContext; // 客户端Socket的Context信息
  210. PER_SOCKET_CONTEXT* m_pListenContext; // 用于监听的Socket的Context信息
  211. LPFN_ACCEPTEX m_lpfnAcceptEx; // AcceptEx 和 GetAcceptExSockaddrs 的函数指针,用于调用这两个扩展函数
  212. LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockAddrs;
  213. };