winsock编程IOCP模型实现代码
Posted 炽离
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了winsock编程IOCP模型实现代码相关的知识,希望对你有一定的参考价值。
winsock编程IOCP模型实现代码
话不多说,上代码。借鉴《windows核心编程》部分源码和CSDN小猪部分代码。
stdafx.h依赖头文件:
1 #include <iostream> 2 #include <WinSock2.h> 3 #include <MSWSock.h> 4 #include <vector> 5 #include "Singleton.h" 6 #include "IOCPWrapper.h" 7 #include "OverlappedIOInfo.h" 8 #include "TaskSvc.h" 9 10 using namespace std;
其中,TaskSvc.h、Singleton.h源码可以在我的blog里面找到。
IOCPWrapper.h源码:
1 /****************************************************************************** 2 Module: IOCP.h 3 Notices: Copyright (c) 2007 Jeffrey Richter & Christophe Nasarre 4 Purpose: This class wraps an I/O Completion Port. 5 Revise: IOCP封装类,由《windows核心编程》第10章示例程序源码改编所得 6 ******************************************************************************/ 7 #pragma once 8 9 class CIOCP 10 { 11 public: 12 CIOCP(int nMaxConcurrency = -1) 13 { 14 m_hIOCP = NULL; 15 if (nMaxConcurrency != -1) 16 Create(nMaxConcurrency); 17 } 18 ~CIOCP() 19 { 20 if (m_hIOCP != NULL) 21 VERIFY(CloseHandle(m_hIOCP)); 22 } 23 24 //关闭IOCP 25 BOOL Close() 26 { 27 BOOL bResult = CloseHandle(m_hIOCP); 28 m_hIOCP = NULL; 29 return(bResult); 30 } 31 32 //创建IOCP,nMaxConcurrency指定最大线程并发数量,0默认为cpu数量 33 BOOL Create(int nMaxConcurrency = 0) 34 { 35 m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, nMaxConcurrency); 36 ASSERT(m_hIOCP != NULL); 37 return(m_hIOCP != NULL); 38 } 39 40 //为设备(文件、socket、邮件槽、管道等)关联一个IOCP 41 BOOL AssociateDevice(HANDLE hDevice, ULONG_PTR CompKey) 42 { 43 BOOL fOk = (CreateIoCompletionPort(hDevice, m_hIOCP, CompKey, 0) == m_hIOCP); 44 ASSERT(fOk); 45 return(fOk); 46 } 47 48 //为socket关联一个IOCP 49 BOOL AssociateSocket(SOCKET hSocket, ULONG_PTR CompKey) 50 { 51 return(AssociateDevice((HANDLE) hSocket, CompKey)); 52 } 53 54 //为iocp传递事件通知 55 BOOL PostStatus(ULONG_PTR CompKey, DWORD dwNumBytes = 0,OVERLAPPED* po = NULL) 56 { 57 BOOL fOk = PostQueuedCompletionStatus(m_hIOCP, dwNumBytes, CompKey, po); 58 ASSERT(fOk); 59 return(fOk); 60 } 61 62 //从IO完成队列中获取事件通知。IO完成队列无事件时,该函数将阻塞 63 BOOL GetStatus(ULONG_PTR* pCompKey, PDWORD pdwNumBytes,OVERLAPPED** ppo, DWORD dwMilliseconds = INFINITE) 64 { 65 return(GetQueuedCompletionStatus(m_hIOCP, pdwNumBytes,pCompKey, ppo, dwMilliseconds)); 66 } 67 68 //获取IOCP对象 69 const HANDLE GetIOCP() 70 { 71 return m_hIOCP; 72 } 73 private: 74 //IOCP句柄 75 HANDLE m_hIOCP; 76 }; 77 78 ///////////////////////////////// End of File /////////////////////////////////
OverlappedIOInfo.h源码
1 /****************************************************************************** 2 Module: OverlappedIOInfo.h 3 Notices: Copyright (c) 20161201 whg 4 Purpose: 5 IOCP网络编程模型中,需要用到GetQueuedCompletionStatus函数获取已完成事件。 6 但该函数的返回参数无socket或buffer的描述信息。 7 8 一个简单的解决办法,创建一个新的结构,该结构第一个参数是OVERLAPPED。 9 由于AcceptEx、WSASend等重叠IO操作传入的是Overlapped结构体的地址,调用AcceptEx等重叠IO操作, 10 在Overlapped结构体后面开辟新的空间,写入socket或buffer的信息,即可将socket或buffer的信息由 11 GetQueuedCompletionStatus带回。 12 13 参考《windows核心编程》和CSDN PiggyXP 14 ******************************************************************************/ 15 16 #pragma once 17 18 #define MAXBUF 8*1024 19 20 enum IOOperType{ 21 TYPE_ACP, //accept事件到达,有新连接请求 22 TYPE_RECV, //数据接收事件 23 TYPE_SEND, //数据发送事件 24 TYPE_CLOSE, //关闭事件 25 TYPE_NO_OPER 26 }; 27 28 class COverlappedIOInfo : public OVERLAPPED 29 { 30 public: 31 COverlappedIOInfo(void) 32 { 33 m_sSock = INVALID_SOCKET; 34 ResetOverlapped(); 35 ResetRecvBuffer(); 36 ResetSendBuffer(); 37 } 38 ~COverlappedIOInfo(void) 39 { 40 if (m_sSock != INVALID_SOCKET) 41 { 42 closesocket(m_sSock); 43 m_sSock = INVALID_SOCKET; 44 } 45 } 46 void ResetOverlapped() 47 { 48 Internal = InternalHigh = 0; 49 Offset = OffsetHigh = 0; 50 hEvent = NULL; 51 } 52 void ResetRecvBuffer() 53 { 54 ZeroMemory(m_cRecvBuf,MAXBUF); 55 m_recvBuf.buf = m_cRecvBuf; 56 m_recvBuf.len = MAXBUF; 57 } 58 void ResetSendBuffer() 59 { 60 ZeroMemory(m_cSendBuf,MAXBUF); 61 m_sendBuf.buf = m_cSendBuf; 62 m_sendBuf.len = MAXBUF; 63 } 64 public: 65 //套接字 66 SOCKET m_sSock; 67 //接收缓冲区,用于AcceptEx、WSARecv操作 68 WSABUF m_recvBuf; 69 char m_cRecvBuf[MAXBUF]; 70 //发送缓冲区,用于WSASend操作 71 WSABUF m_sendBuf; 72 char m_cSendBuf[MAXBUF]; 73 //对端地址 74 sockaddr_in m_addr; 75 };
server.h
1 #pragma once 2 3 4 class CServer:public CTaskSvc 5 { 6 #define ACCEPT_SOCKET_NUM 10 7 8 public: 9 CServer(void); 10 ~CServer(void); 11 bool StartListen(unsigned short port,std::string ip); 12 13 protected: 14 virtual void svc(); 15 16 private: 17 //启动CPU*2个线程,返回已启动线程个数 18 UINT StartThreadPull(); 19 //获取AcceptEx和GetAcceptExSockaddrs函数指针 20 bool GetLPFNAcceptEXAndGetAcceptSockAddrs(); 21 //利用AcceptEx监听accept请求 22 bool PostAccept(COverlappedIOInfo* ol); 23 //处理accept请求,NumberOfBytes=0表示没有收到第一帧数据,>0表示收到第一帧数据 24 bool DoAccept(COverlappedIOInfo* ol,DWORD NumberOfBytes=0); 25 //投递recv请求 26 bool PostRecv(COverlappedIOInfo* ol); 27 //处理recv请求 28 bool DoRecv(COverlappedIOInfo* ol); 29 //从已连接socket列表中移除socket及释放空间 30 bool DeleteLink(SOCKET s); 31 //释放3个部分步骤: 32 //1:清空IOCP线程队列,退出线程 33 //2: 清空等待accept的套接字m_vecAcps 34 //3: 清空已连接的套接字m_vecContInfo并清空缓存 35 void CloseServer(); 36 private: 37 //winsock版本类型 38 WSAData m_wsaData; 39 //端口监听套接字 40 SOCKET m_sListen; 41 //等待accept的套接字,这些套接字是没有使用过的,数量为ACCEPT_SOCKET_NUM。同时会有10个套接字等待accept 42 std::vector<SOCKET> m_vecAcps; 43 //已建立连接的信息,每个结构含有一个套接字、发送缓冲和接收缓冲,以及对端地址 44 std::vector<COverlappedIOInfo*> m_vecContInfo; 45 //操作vector的互斥访问锁 46 CThreadLockCs m_lsc; 47 //IOCP封装类 48 CIOCP m_iocp; 49 //AcceptEx函数指针 50 LPFN_ACCEPTEX m_lpfnAcceptEx; 51 //GetAcceptSockAddrs函数指针 52 LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptSockAddrs; 53 }; 54 55 typedef CSingleton<CServer> SERVER;
server.cpp
1 #include "StdAfx.h" 2 #include "Server.h" 3 4 CServer::CServer(void) 5 { 6 m_lpfnAcceptEx = NULL; 7 m_lpfnGetAcceptSockAddrs = NULL; 8 WSAStartup(MAKEWORD(2,2),&m_wsaData); 9 } 10 11 CServer::~CServer(void) 12 { 13 CloseServer(); 14 WSACleanup(); 15 } 16 17 bool CServer::StartListen(unsigned short port,std::string ip) 18 { 19 //listen socket需要将accept操作投递到完成端口,因此,listen socket属性必须有重叠IO 20 m_sListen = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED); 21 if(m_sListen == INVALID_SOCKET) 22 { 23 cout<<"WSASocket create socket error"<<endl; 24 return false; 25 } 26 //创建并设置IOCP并发线程数量 27 if (m_iocp.Create() == FALSE) 28 { 29 cout<<"IOCP create error,error code "<<WSAGetLastError()<<endl; 30 return false; 31 } 32 //将listen socket绑定至iocp 33 if (!m_iocp.AssociateSocket(m_sListen,TYPE_ACP)) 34 { 35 cout<<"iocp Associate listen Socket error"<<endl; 36 return false; 37 } 38 sockaddr_in service; 39 service.sin_family = AF_INET; 40 service.sin_port = htons(port); 41 if (ip.empty()) 42 { 43 service.sin_addr.s_addr = INADDR_ANY; 44 } 45 else 46 { 47 service.sin_addr.s_addr = inet_addr(ip.c_str()); 48 } 49 50 if (bind(m_sListen,(sockaddr*)&service,sizeof(service)) == SOCKET_ERROR) 51 { 52 cout<<"bind() error,error code "<<WSAGetLastError()<<endl; 53 return false; 54 } 55 cout<<"bind ok!"<<endl; 56 57 if (listen(m_sListen,SOMAXCONN) == SOCKET_ERROR) 58 { 59 cout<<"listen() error,error code "<<WSAGetLastError()<<endl; 60 return false; 61 } 62 cout<<"listen ok!"<<endl; 63 //启动工作者线程 64 int threadnum = StartThreadPull(); 65 cout<<"启动工作者线程,num="<<threadnum<<endl; 66 //获取AcceptEx和GetAcceptSockAddrs函数指针 67 if (!GetLPFNAcceptEXAndGetAcceptSockAddrs()) 68 { 69 return false; 70 } 71 //创建10个acceptex 72 for (int i=0;i<ACCEPT_SOCKET_NUM;i++) 73 { 74 //用accept 75 COverlappedIOInfo* ol = new COverlappedIOInfo; 76 if (!PostAccept(ol)) 77 { 78 delete ol; 79 return false; 80 } 81 } 82 } 83 84 bool CServer::GetLPFNAcceptEXAndGetAcceptSockAddrs() 85 { 86 DWORD BytesReturned = 0; 87 //获取AcceptEx函数指针 88 GUID GuidAcceptEx = WSAID_ACCEPTEX; 89 if (SOCKET_ERROR == WSAIoctl( 90 m_sListen, 91 SIO_GET_EXTENSION_FUNCTION_POINTER, 92 &GuidAcceptEx, 93 sizeof(GuidAcceptEx), 94 &m_lpfnAcceptEx, 95 sizeof(m_lpfnAcceptEx), 96 &BytesReturned, 97 NULL,NULL)) 98 { 99 cout<<"WSAIoctl get AcceptEx function error,error code "<<WSAGetLastError()<<endl; 100 return false; 101 } 102 103 //获取GetAcceptexSockAddrs函数指针 104 GUID GuidGetAcceptexSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; 105 if (SOCKET_ERROR == WSAIoctl( 106 m_sListen, 107 SIO_GET_EXTENSION_FUNCTION_POINTER, 108 &GuidGetAcceptexSockAddrs, 109 sizeof(GuidGetAcceptexSockAddrs), 110 &m_lpfnGetAcceptSockAddrs, 111 sizeof(m_lpfnGetAcceptSockAddrs), 112 &BytesReturned, 113 NULL,NULL)) 114 { 115 cout<<"WSAIoctl get GetAcceptexSockAddrs function error,error code "<<WSAGetLastError()<<endl; 116 return false; 117 } 118 return true; 119 } 120 121 bool CServer::PostAccept(COverlappedIOInfo* ol) 122 { 123 if (m_lpfnAcceptEx == NULL) 124 { 125 cout << "m_lpfnAcceptEx is NULL"<<endl; 126 return false; 127 } 128 SOCKET s = ol->m_sSock; 129 ol->ResetRecvBuffer(); 130 ol->ResetOverlapped(); 131 ol->ResetSendBuffer(); 132 ol->m_sSock = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED); 133 if (ol->m_sSock == INVALID_SOCKET) 134 { 135 cout<<"WSASocket error ,error code "<<WSAGetLastError()<<endl; 136 return false; 137 } 138 //这里建立的socket用来和对端建立连接,终会加入m_vecContInfo列表 139 //调用acceptex将accept socket绑定至完成端口,并开始进行事件监听 140 //这里需要传递Overlapped,new一个COverlappedIOInfo 141 //AcceptEx是m_listen的监听事件,m_listen已经绑定了完成端口;虽然ol->m_sSock已经创建, 142 //但未使用,现在不必为ol->m_sSock绑定完成端口。在AcceptEx事件发生后,再为ol->m_sSock绑定IOCP 143 DWORD byteReceived = 0; 144 if (FALSE == m_lpfnAcceptEx( 145 m_sListen, 146 ol->m_sSock, 147 ol->m_recvBuf.buf, 148 ol->m_recvBuf.len - (sizeof(SOCKADDR_IN)+16)*2, 149 sizeof(SOCKADDR_IN)+16, 150 sizeof(SOCKADDR_IN)+16, 151 &byteReceived, 152 ol)) 153 { 154 DWORD res = WSAGetLastError(); 155 if (ERROR_IO_PENDING != res) 156 { 157 cout<<"AcceptEx error , error code "<<res<<endl; 158 return false; 159 } 160 } 161 std::vector<SOCKET>::iterator iter = m_vecAcps.begin(); 162 for (;iter != m_vecAcps.end(); iter++) 163 { 164 if (*iter == s) 165 { 166 *iter = ol->m_sSock; 167 } 168 } 169 if (iter == m_vecAcps.end()) 170 { 171 m_vecAcps.push_back(ol->m_sSock); 172 } 173 return true; 174 } 175 176 bool CServer::DoAccept(COverlappedIOInfo* ol,DWORD NumberOfBytes) 177 { 178 //分支用于获取远端地址。 179 //如果接收TYPE_ACP同时收到第一帧数据,则第一帧数据内包含远端地址。 180 //如果没有收到第一帧数据,则通过getpeername获取远端地址 181 SOCKADDR_IN* ClientAddr = NULL; 182 int remoteLen = sizeof(SOCKADDR_IN); 183 if (NumberOfBytes > 0) 184 { 185 //接受的数据分成3部分,第1部分是客户端发来的数据,第2部分是本地地址,第3部分是远端地址。 186 if (m_lpfnGetAcceptSockAddrs) 187 { 188 SOCKADDR_IN* LocalAddr = NULL; 189 int localLen = sizeof(SOCKADDR_IN); 190 m_lpfnGetAcceptSockAddrs( 191 ol->m_recvBuf.buf, 192 ol->m_recvBuf.len - (sizeof(SOCKADDR_IN)+16)*2, 193 sizeof(SOCKADDR_IN)+16, 194 sizeof(SOCKADDR_IN)+16, 195 (LPSOCKADDR*)&LocalAddr, 196 &localLen, 197 (LPSOCKADDR*)&ClientAddr, 198 &remoteLen); 199 cout<<"收到新的连接请求,ip="<<inet_ntoa(ClientAdd以上是关于winsock编程IOCP模型实现代码的主要内容,如果未能解决你的问题,请参考以下文章