现在的位置: 首页 > 综合 > 正文

完成端口IOCP之TCP服务器

2013年01月04日 ⁄ 综合 ⁄ 共 14890字 ⁄ 字号 评论关闭

        继续之前的异步重叠的系列,本文实践的为TCPSrv的完成端口实现,之前文章中层提到过该概念,也是被奉为WINDOWS SOCKET编程的最优解。但是否真的是最优,本文没测试,很多东西,个人认为看情形用远比抱着一个什么最好的情结更实用。

         依旧的风格,直接上代码,至于想捣鼓IOCP的人,看完整篇的代码也应该懂了。我还是相信代码以及运行结果远比一本书的文字来的有说服力。当然我的代码中必要的注释都还是存在的,以协助说明思路。

         编写主体前准备工作:

View Code

  1 #include <afxtempl.h>
  2 #include "winsock2.h"
  3 #include <Mswsock.h>
  4 #include <list>
  5 #include <vector>
  6 #include <string>
  7 #include <process.h>
  8 #include <time.h>
  9 #include <map>
 10 
 11 #include "iostream"
 12 using namespace std;
 13 
 14 #define SOCKET_BUF_SIZE                10240                    // 发送、接收缓冲区最大长度,10K
 15 #define OVERLAPPED_DELIVER_SEND        0                        // 发送数据
 16 #define OVERLAPPED_DELIVER_RECV        2                        // 接收数据
 17 
 18 class MyTcpSrv;
 19 
 20 // 客户端套接字元
 21 typedef struct _CLIENT_ELEMENT
 22 {
 23     SOCKET                    s_socket;                        // 客户端SOCKET套接字
 24     SOCKADDR_IN                s_clientInfo;                    // 客户端终端信息
 25     time_t                    s_tVisitTM;                        // 访问时间
 26     bool                    s_bFirstFrame;                    // 第一帧标志位
 27 
 28     _CLIENT_ELEMENT()
 29     {
 30         s_socket = INVALID_SOCKET;
 31     }
 32 } TS_CLIENT_ELEMENT, * LPTS_CLIENT_ELEMENT;
 33 
 34 // 通信载体,此处使用尾随参数的解决思路,故而OVERLAPPED必须为第一个参数
 35 typedef struct _SOCKET_INFORMATION
 36 {
 37     OVERLAPPED                s_overlapped;
 38     CHAR                    s_buffer[SOCKET_BUF_SIZE];        // 存储接收的字符
 39     int                        s_nBufLen;                        // 接收的长度
 40     WSABUF                    s_dataBuf;                        // 接收缓冲区,为OS自动填充区
 41     LPTS_CLIENT_ELEMENT        s_lpClientElement;                // 客户端套接字元
 42     UINT                    s_uDeliverType;                    // 异步请求类型
 43     int                        s_uAntiSendSize;                // 预期发送字节数
 44     bool                    s_bIsDiscard;                    // 作废标志位
 45     MyTcpSrv*                s_pThis;                        // 所有者
 46 
 47     _SOCKET_INFORMATION()
 48     {
 49         s_lpClientElement = NULL;
 50         s_pThis = NULL;
 51     }
 52 } SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
 53 
 54 // TCPSrv、UDP网络报文数据结构
 55 typedef struct _netPara
 56 {
 57     UCHAR                    s_szBuf[SOCKET_BUF_SIZE];        // 数据缓存
 58     WORD                    s_wBufLen;                        // 数据长度
 59 
 60     // 追加字符
 61     void Append(_netPara* pNet)
 62     {
 63         memcpy(s_szBuf + s_wBufLen,pNet->s_szBuf,min(SOCKET_BUF_SIZE - s_wBufLen, pNet->s_wBufLen));
 64         s_wBufLen += pNet->s_wBufLen;
 65     }
 66     // 释放
 67     void Release(UINT uSize)
 68     {
 69         UCHAR szBuf[SOCKET_BUF_SIZE];
 70         memcpy(szBuf, s_szBuf + uSize, s_wBufLen - uSize);
 71         memset(s_szBuf, 0, SOCKET_BUF_SIZE);
 72         memcpy(s_szBuf, szBuf, s_wBufLen - uSize);
 73         s_wBufLen = s_wBufLen - uSize;
 74     }
 75 }SNetPara, * LPNetPara;
 76 
 77 
 78 // 终端信息结构
 79 typedef struct _terminal_info
 80 {
 81     string                    s_ip;                            // IP地址
 82     USHORT                    s_uPort;                        // 端口号
 83     // 重载<运算符,以支持作为map的key
 84     friend inline bool operator < (const _terminal_info &ls, const _terminal_info &rs);
 85     friend inline bool operator == (const _terminal_info &ls, const _terminal_info &rs);
 86 }TERMINAL_INFO, * LPTERMINAL_INFO;
 87 
 88 // LESS
 89 bool operator < (const _terminal_info& ls, const _terminal_info& rs)
 90 {
 91     return (ls.s_ip < rs.s_ip || (ls.s_ip == rs.s_ip && ls.s_uPort < rs.s_uPort));
 92 }
 93 
 94 // EQUAL
 95 bool operator == (const _terminal_info& ls, const _terminal_info& rs)
 96 {
 97     return (ls.s_ip == rs.s_ip && ls.s_uPort == rs.s_uPort);
 98 }
 99 
100 typedef vector<TS_CLIENT_ELEMENT>        VClnt;                // 客户端信息列表
101 typedef list<SNetPara>                    LNetPara;            // 接收信息列表
102 
103 typedef map<TERMINAL_INFO, SNetPara>    MNetDatagram;        // 网络数据包 

          主体结构设计:

View Code
 1 // TCP服务器端
 2 class MyTcpSrv
 3 {
 4 private:
 5     CRITICAL_SECTION        m_csLock;                        // 临界对象,冲突锁
 6 
 7     HANDLE                    m_hIOCP;                        // 完成端口句柄
 8 
 9     SOCKADDR_IN                m_LocalAddr;                    // 本地终端地址
10     SOCKET                    m_sLinstenSocket;                // 侦听套接字
11     HANDLE                    m_hListenThread;                // 侦听线程
12     UINT                    m_nListenThreadId;                // 侦听线程ID
13     HANDLE                    m_hListenEventExit;                // 侦听线程退出事件对象,用于退出侦听线程时通知子线程关闭
14     SYSTEM_INFO                m_sysInfo;                        // 系统信息,其中包含处理器等信息
15     VClnt                    m_clntList;                        // 客户端清单
16     MNetDatagram            m_netParaMap;                    // 接收信息列表
17 
18 public:
19     MyTcpSrv()
20     {
21         InitializeCriticalSection(&m_csLock);
22         m_hIOCP = INVALID_HANDLE_VALUE;
23         m_sLinstenSocket = INVALID_SOCKET;
24         m_hListenThread = INVALID_HANDLE_VALUE;
25         m_hListenEventExit = INVALID_HANDLE_VALUE;
26     }
27     ~MyTcpSrv()
28     {
29         DeleteCriticalSection(&m_csLock);
30     }
31 
32 private:
33     // 加载SOCK DLL
34     bool LoadSock();
35 
36     // 设置本地终端
37     bool SetLocalAddr(WORD nPort, const char *szAddr);
38 
39     // 初始化完成端口
40     bool InitialIOCP();
41 
42     // 设置侦听套接字
43     bool SetupListenSocket();
44 
45     // 投递接收请求
46     void DeliverRecv(LPSOCKET_INFORMATION lpInfo);
47 
48     // 投递发送请求
49     void DeliverSend(LPSOCKET_INFORMATION lpInfo);
50 
51     // 启动侦听线程
52     bool RunListenThread();
53 
54     // 侦听例程
55     UINT static WINAPI TCPListenThreadProc(LPVOID pParam);
56 
57     // 工作例程
58     UINT static WINAPI ServerWorkerThread(LPVOID CompletionPortID);
59 
60     // 擦除记录
61     void Erase(LPSOCKET_INFORMATION lpSocketInfo);
62 
63     // 解析获取的数据
64     void OnRecvData(LPSOCKET_INFORMATION lpSocketInfo);
65 
66     // 停止侦听
67     void StopListening();
68 
69     // 断开与指定客户端连接
70     void DisconnectUser(SOCKADDR_IN clientAddr);
71 
72     // 断开与所有用户连接
73     void DisconnectAllUsers();
74 
75     // 卸载SOCKDLL
76     void UnloadSock();
77 
78 public
79     // 创建通道
80     bool Create(LPTERMINAL_INFO pInfo);
81 
82     // 关闭通道
83     void Close();
84     
85     // 读取数据
86     int Read(byte byRecv[], UINT uMaxSize, LPTERMINAL_INFO pInfo);
87 
88     // 发送数据
89     int Write(byte bySend[], UINT uAntiSize, LPTERMINAL_INFO pInfo);
90 
91     // 重置服务器
92     bool Reset();
93 
94     // 获取客户端列表信息
95     VClnt GetClinetList()
96     {
97         return m_clntList;
98     }
99 };

         
主体中成员方法的实现:

View Code
  1 #include "TCPSrv.h"
  2 
  3 // 加载SOCK DLL
  4 bool MyTcpSrv::LoadSock()
  5 {
  6     WSADATA wsaData;
  7     return  WSAStartup(MAKEWORD(22), &wsaData) == 0 ? true : false;
  8 }
  9 
 10 // 卸载SOCK DLL
 11 void MyTcpSrv::UnloadSock()
 12 {
 13     WSACleanup();
 14 }
 15 
 16 // 设置本地终端
 17 bool MyTcpSrv::SetLocalAddr(WORD nPort, const char *szAddr)
 18 {
 19     memset(&m_LocalAddr, 0sizeof(SOCKADDR_IN));
 20     m_LocalAddr.sin_family = AF_INET;
 21     m_LocalAddr.sin_port = htons(nPort);
 22     if(szAddr == NULL)
 23         m_LocalAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 24     else
 25     {
 26         if((m_LocalAddr.sin_addr.s_addr = inet_addr(szAddr)) == INADDR_NONE)
 27         {
 28             HOSTENT *host = NULL;
 29             host = gethostbyname(szAddr);
 30             if(host == NULL)
 31                 return false;
 32             CopyMemory(&m_LocalAddr.sin_addr, host->h_addr, host->h_length);
 33         }
 34     }
 35     return true;
 36 }
 37 
 38 // 初始化完成端口
 39 bool MyTcpSrv::InitialIOCP()
 40 {
 41     // 创建完成端口,其中第三个参数为零,表示完成端口最大同时支持CPU个线程
 42     if ((m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 00)) == NULL)
 43         return false;
 44 
 45     // 获取系统信息,计算所需工作线程数,创建工作线程
 46     SYSTEM_INFO sysInfo;
 47     GetSystemInfo(&sysInfo);
 48     UINT ThreadID;
 49     for(USHORT i = 0; i < sysInfo.dwNumberOfProcessors * 2 + 2; i++)
 50     {
 51         HANDLE hThread;
 52         if((hThread = (HANDLE)_beginthreadex(NULL, 0, ServerWorkerThread, this0, &ThreadID)) == NULL)
 53             return false;//创建工作线程失败            
 54         CloseHandle(hThread);
 55     }
 56     return true;
 57 }
 58 
 59 // 设置侦听套接字
 60 bool MyTcpSrv::SetupListenSocket()
 61 {
 62     // 创建侦听套接字
 63     if ((m_sLinstenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
 64         return false// 创建侦听套接字失败
 65 
 66     // 绑定端口
 67     if (bind(m_sLinstenSocket, (PSOCKADDR) &m_LocalAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
 68         return false// 绑定端口失败
 69 
 70     // 进入侦听被动模式,准备接收
 71     if (listen(m_sLinstenSocket, SOMAXCONN) == SOCKET_ERROR)
 72         return false// 侦听失败
 73 
 74     return true;
 75 }
 76 
 77 // 启动侦听线程
 78 bool MyTcpSrv::RunListenThread()
 79 {
 80     // 初始化信号
 81     m_hListenEventExit = CreateEvent(NULL, TRUE, FALSE, NULL);
 82     if((m_hListenThread = (HANDLE)_beginthreadex(NULL, 0, TCPListenThreadProc, this0, &m_nListenThreadId)) == NULL)
 83     {
 84         closesocket(m_sLinstenSocket);
 85         return false// 启动侦听线程失败
 86     }
 87     return true;
 88 }
 89 
 90 // 侦听执行体
 91 UINT WINAPI MyTcpSrv::TCPListenThreadProc(LPVOID pParam)
 92 {    
 93     // 接收套接字
 94     TS_CLIENT_ELEMENT        AcceptElement;
 95     WSAEVENT                wsaListenEvent;
 96     OVERLAPPED                ListenOverlapped;
 97     CHAR                    szAcceptBuffer [2 * (sizeof(SOCKADDR_IN) + 16)];
 98     DWORD                    dwBytesReceived;
 99     LPSOCKADDR                lpServerSockInfo;
100     LPSOCKADDR                lpClientSockInfo;
101     int                        nServerAddrLen;
102     int                        nClientAddrLen;
103     LPSOCKET_INFORMATION    lpSocketInfo;
104     MyTcpSrv*                pthis;
105 
106     pthis = (MyTcpSrv*)pParam;
107     wsaListenEvent = WSACreateEvent();
108 
109     // 不断接收连接接入
110     while(true)
111     {
112         ZeroMemory(&AcceptElement, sizeof(TS_CLIENT_ELEMENT));
113         if ((AcceptElement.s_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) 
114             return 0L;//创建套接字失败
115 
116         ZeroMemory(&ListenOverlapped, sizeof(WSAOVERLAPPED));
117         WSAResetEvent(wsaListenEvent);
118         ListenOverlapped.hEvent = wsaListenEvent;
119 
120         // 异步接收套接字连接
121         if (!AcceptEx(pthis->m_sLinstenSocket, AcceptElement.s_socket, (PVOID)szAcceptBuffer, 0sizeof(SOCKADDR_IN)\
122             + 16sizeof(SOCKADDR_IN) + 16, &dwBytesReceived, &ListenOverlapped))
123         {
124             if (WSAGetLastError() != ERROR_IO_PENDING)
125                 return 0L;//接受套接字失败
126         }
127 
128         // 等待AcceptEx返回
129         while(TRUE)
130         {
131             HANDLE hEvents[2];
132             hEvents[0]  = ListenOverlapped.hEvent;
133             hEvents[1]  = pthis->m_hListenEventExit;
134 
135             DWORD dwEventIndex = WSAWaitForMultipleEvents(2, hEvents, FALSE, WSA_INFINITE, TRUE);
136             if (dwEventIndex == WSA_WAIT_FAILED)
137                 return 0L// 异常
138             if ((dwEventIndex-WAIT_OBJECT_0) == 1)
139                 return 0L// 接到退出线程通知
140 
141             if (dwEventIndex != WAIT_IO_COMPLETION)
142                 break;
143         }
144 
145         // 获取客户端详细信息,并登记
146         GetAcceptExSockaddrs(szAcceptBuffer, 0sizeof(SOCKADDR_IN) + 16,  sizeof(SOCKADDR_IN) + 16, \
147             &lpServerSockInfo, &nServerAddrLen, &lpClientSockInfo, &nClientAddrLen);
148         memcpy(&AcceptElement.s_clientInfo, lpClientSockInfo, sizeof(sockaddr_in));
149         time(&AcceptElement.s_tVisitTM);// 获取访问时间
150         AcceptElement.s_bFirstFrame = TRUE;
151         lpSocketInfo = new SOCKET_INFORMATION;// 创建通信载体
152         pthis->m_clntList.push_back(AcceptElement);
153         lpSocketInfo->s_lpClientElement = &(pthis->m_clntList.back());
154         lpSocketInfo->s_dataBuf.len = SOCKET_BUF_SIZE;
155         lpSocketInfo->s_dataBuf.buf = lpSocketInfo->s_buffer;
156         lpSocketInfo->s_nBufLen = 0;
157         lpSocketInfo->s_pThis = pthis;
158 
159         // 将获取到的SOCKET绑定到完成端口
160         if (CreateIoCompletionPort((HANDLE)lpSocketInfo->s_lpClientElement->s_socket, lpSocketInfo->s_pThis->m_hIOCP, (DWORD)lpSocketInfo, 0) == NULL)
161             return 0L// 绑定完成端口失败
162 
163         // 递交异步接收请求
164         lpSocketInfo->s_pThis->DeliverRecv(lpSocketInfo);
165     }
166     return 1L;
167 }
168 
169 // 递交异步接收请求
170 void MyTcpSrv::DeliverRecv(LPSOCKET_INFORMATION lpSocketInfo)
171 {
172     DWORD        dwRecvBytes = 0;
173     DWORD        dwFlags = 0;
174 
175     // 整理通信载体
176     ZeroMemory(&(lpSocketInfo->s_overlapped), sizeof(WSAOVERLAPPED));
177     lpSocketInfo->s_dataBuf.len = SOCKET_BUF_SIZE;
178     lpSocketInfo->s_dataBuf.buf = lpSocketInfo->s_buffer;
179     lpSocketInfo->s_nBufLen = 0;
180     lpSocketInfo->s_pThis = this;
181     lpSocketInfo->s_uDeliverType = OVERLAPPED_DELIVER_RECV;
182 
183     if (WSARecv(lpSocketInfo->s_lpClientElement->s_socket, &(lpSocketInfo->s_dataBuf), 1, &dwRecvBytes, \
184         &dwFlags, &(lpSocketInfo->s_overlapped), NULL) == SOCKET_ERROR)
185     {
186         // 异步投递失败
187         if (WSAGetLastError() != WSA_IO_PENDING)
188             Erase(lpSocketInfo);
189     }
190 }
191 
192 // 递交异步发送请求
193 void MyTcpSrv::DeliverSend(LPSOCKET_INFORMATION lpSocketInfo)
194 {
195     DWORD dwSendBytes = 0;
196     DWORD dwFlags = 0;
197 
198     // 已经全部发送完毕
199     if (lpSocketInfo->s_uAntiSendSize - lpSocketInfo->s_nBufLen <= 0)
200     {
201         delete lpSocketInfo;
202         return;
203     }
204 
205     // 整理通信载体
206     ZeroMemory(&(lpSocketInfo->s_overlapped), sizeof(WSAOVERLAPPED));
207     lpSocketInfo->s_dataBuf.len = lpSocketInfo->s_uAntiSendSize - lpSocketInfo->s_nBufLen;
208     lpSocketInfo->s_dataBuf.buf = lpSocketInfo->s_buffer + lpSocketInfo->s_nBufLen;
209     lpSocketInfo->s_pThis = this;
210     lpSocketInfo->s_uDeliverType = OVERLAPPED_DELIVER_SEND;    // 发送
211     
212     // 异步发送请求
213     if(WSASend(lpSocketInfo->s_lpClientElement->s_socket, &lpSocketInfo->s_dataBuf, 1, &dwSendBytes, dwFlags, &lpSocketInfo->s_overlapped, NULL) == SOCKET_ERROR)
214     {
215         // 异步发送请求失败
216         if (WSAGetLastError() != WSA_IO_PENDING)
217         {
218             delete lpSocketInfo;// 释放通信载体
219             return
220         }        
221     }
222 }
223 
224 // 擦除记录
225 void MyTcpSrv::Erase(LPSOCKET_INFORMATION lpSocketInfo)
226 {
227     // 关闭连接
228     closesocket(lpSocketInfo->s_lpClientElement->s_socket);
229     
230     // 清除记录
231     EnterCriticalSection(&m_csLock);
232     VClnt::iterator ite;
233     for(ite=m_clntList.begin();ite != m_clntList.end();++ite)
234     {
235         if (ite->s_socket == lpSocketInfo->s_lpClientElement->s_socket)
236         {
237             m_clntList.erase(ite);
238             break;
239         }
240     }
241     LeaveCriticalSection(&m_csLock);
242 
243    

抱歉!评论已关闭.