C++Socket编程—socket网络模型之IOCP
网络模型—IOCP模型
一. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
1) IOCP(I/O Completion Port),常称I/O完成端口。
2) IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术,适用于大型项目,处理高并发问题。
3) 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
4) 或者可以说,就是能异步I/O操作的模型。
二. IOCP 工作机制
尽管select、WSAA、WSAE这些socket通信模型可以让我们不用开
更多的线程来处理每一连接,但它们收发数据时仍然要调用Recv和Send,Recv和Send实际上仍然会与操作系统底层进行交互,仍然会进入内核,所以还是会有效率上的损失。
IOCP怎么解决这个问题呢?IOCP有一个队列,当你要发数据时,收数据和连接时,都交由IOCP队列处理,不会与操作系统底层交互。
发送数据时,先将缓冲区和长度封好,这个请求会发送到IOCP队列,IOCP内部会帮你把请求发出去。
收数据时,收数据的请求丢掉IOCP队列,IOCP会将收到的数据填入指定的缓冲区里边,当数据收好后会通知你来收数据。
建立连接时,IOCP帮你把连接建立好,告诉你新的连接已经来了。
开发者使用IOCP时无需关注数据收、发、连接,只需关注处理数据
三. IOCP的存在理由(IOCP的优点)及技术相关有哪些?
IOCP是用于高效处理很多很多的客户端进行数据交换的一个模型,那么,它具体的优点有些什么呢?它到底用到了哪些技术了呢?在Windows环境下又如何去使用这些技术来编程呢?它主要使用上哪些API函数呢?
1) 使用IOCP模型编程的优点
① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
② 去除删除线程创建/终结负担。
③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
④ 优化线程调度,提高CPU和内存缓冲的命中率。
2) 使用IOCP模型编程汲及到的知识点
① 同步与异步
② 阻塞与非阻塞
③ 重叠I/O技术
④ 多线程
⑤ 栈、队列这两种基本的数据结构
3) 需要使用上的API函数
① 与SOCKET相关
1、链接套接字动态链接库:int WSAStartup(...);
2、创建套接字库: SOCKET socket(...);
3、绑字套接字: int bind(...);
4、套接字设为监听状态: int listen(...);
5、接收套接字: SOCKET accept(...);
6、向指定套接字发送信息:int send(...);
7、从指定套接字接收信息:int recv(...);
② 与线程相关
1、创建线程:HANDLE CreateThread(...);
③ 重叠I/O技术相关
1、向套接字发送数据: int WSASend(...);
2、向套接字发送数据包: int WSASendFrom(...);
3、从套接字接收数据: int WSARecv(...);
4、从套接字接收数据包: int WSARecvFrom(...);
④ IOCP相关
1、创建ICOCP对象: HANDLE WINAPI CreateIoCompletionPort(...);
( 这个对象可以收发对象)
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // 句柄,首次创建时填INVALID_HANDLE_VALUE
HANDLE ExistingCompletionPort, // I/O完成端口句柄 ,首次创建给NULL
ULONG_PTR CompletionKey, // 创建自定义对象
DWORD NumberOfConcurrentThreads //允许应用程序同时执行的线程数量,填0,根据CPU核数自动计算核数
该函数实际用于两个明显有别的目的:
a. 用于创建一个完成端口对象。
b. 将一个句柄同完成端口关联到一起。
2、关联完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
(关联需要通过IOCP收发数据的socket)
3.向IOCP队列投递接受连接的请求:BOOL AcceptEx(...);
通知IOCP,让IOCP建立连接(可以异步操作),
它可以接收连接,还可以在建立连接之后,等客户端发第一次数据(或者在建立连接之后等客户端不收后边的数据)
BOOL AcceptEx(
SOCKET sListenSocket, //监听socket,之前用到的socket
SOCKET sAcceptSocket, //用来接收传入socket,与客户端socket建立连接
PVOID lpOutputBuffer, //用来接收数据的缓冲区
DWORD dwReceiveDataLength, //缓冲区大小,一般填0
DWORD dwLocalAddressLength, //本地地址sockaddr大小,
此值必须至少比正在使用的传输协议的最大地址长度多16个字节
DWORD dwRemoteAddressLength, //远程地址信息保留的字节数,此值必须至少
比正在使用的传输协议的最大地址长度多16个字节(填写同上)
LPDWORD lpdwBytesReceived, //返回数据的大小
LPOVERLAPPED lpOverlapped);
4、.检测队列,从队列中取出完成的请求 BOOL WINAPI GetQueuedCompletionStatus(...);
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // 句柄
LPDWORD lpNumberOfBytes, // 接收字节数
PULONG_PTR lpCompletionKey, // 自定义参数
LPOVERLAPPED *lpOverlapped, //返回的结构体参数
DWORD dwMilliseconds); //等待的时间
5、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);
四. 使用实例:(使用IOCP用来处理收发数据)
服务端:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 #define FD_SETSIZE 128 6 #define WIN32_LEAN_AND_MEAN 7 #include <windows.h> 8 #include <Winsock2.h> 9 #pragma comment(lib, "Ws2_32.lib") 10 11 #include <Mswsock.h> 12 #pragma comment(lib, "Mswsock.lib") 13 14 void InitWs2(); 15 void UninitWs32(); 16 void PostAccept(SOCKET sockListen, HANDLE hIocp); 17 void PostRecv(SOCKET sock); 18 19 enum IO_EVENT 20 { 21 IO_ACCEPT, 22 IO_RECV, 23 IO_SEND 24 }; 25 26 struct MYOV :public OVERLAPPED 27 { 28 MYOV(SOCKET sock, IO_EVENT event) 29 { 30 memset(this, 0, sizeof(MYOV)); 31 m_sockClient = sock; 32 m_buf.buf = m_btBuf; 33 m_buf.len = sizeof(m_btBuf); 34 m_dwBytesRecved = 0; 35 m_dwFlag = 0; 36 m_event = event; 37 } 38 IO_EVENT m_event; 39 SOCKET m_sockClient; 40 WSABUF m_buf; 41 CHAR m_btBuf[MAXBYTE]; 42 DWORD m_dwBytesRecved; 43 DWORD m_dwFlag; 44 }; 45 46 int main() 47 { 48 InitWs2(); 49 50 SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 51 if (sockServer == SOCKET_ERROR) 52 { 53 printf("socket 创建失败\r\n"); 54 return 0; 55 } 56 else 57 { 58 printf("socket 创建成功\r\n"); 59 } 60 61 //2) 62 sockaddr_in si; 63 si.sin_family = AF_INET; 64 si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 65 si.sin_port = htons(9527); 66 int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); 67 if (nRet == SOCKET_ERROR) 68 { 69 printf("绑定端口失败\r\n"); 70 return 0; 71 } 72 else 73 { 74 printf("绑定端口成功\r\n"); 75 } 76 77 //3) 78 nRet = listen(sockServer, SOMAXCONN); 79 if (nRet == SOCKET_ERROR) 80 { 81 printf("监听失败 \r\n"); 82 return 0; 83 } 84 else 85 { 86 printf("监听成功 \r\n"); 87 } 88 89 //1)创建IOCP对象 90 ULONG uKey = 0; 91 HANDLE hIocp = CreateIoCompletionPort( 92 INVALID_HANDLE_VALUE, 93 NULL, 94 NULL, 95 0); 96 97 //2) 关联IOCP和socket对象 98 HANDLE bRet=CreateIoCompletionPort( 99 (HANDLE)sockServer, 100 hIocp, 101 NULL, 102 0); 103 104 //3)投递一个接收连接的请求 105 PostAccept(sockServer,hIocp); 106 107 //遍历队列 108 while (true) 109 { 110 DWORD dwBytesTranfered = 0; 111 ULONG_PTR uKey; 112 LPOVERLAPPED pOv = NULL; 113 GetQueuedCompletionStatus( 114 hIocp, 115 &dwBytesTranfered, 116 &uKey, 117 &pOv, 118 INFINITE 119 ); 120 121 122 MYOV* pov = (MYOV*)pOv; 123 124 switch (pov->m_event) 125 { 126 //接收新的连接 127 case IO_ACCEPT: 128 //连接完成后,再次投递一个连接的请求 129 PostAccept(sockServer, hIocp); 130 cout << " 有新的连接接入" << endl; 131 PostRecv(pov->m_sockClient); 132 break; 133 case IO_RECV: 134 //投递一个接收数据的请求 135 printf("接收到数据%s\r\n", pov->m_btBuf); 136 PostRecv(pov->m_sockClient); 137 break; 138 default: 139 break; 140 } 141 } 142 } 143 144 void PostRecv(SOCKET sock) 145 { 146 //接收数据的请求 147 MYOV* pOv = new MYOV(sock, IO_RECV); 148 int nRet = WSARecv( 149 sock, 150 &pOv->m_buf, 1, 151 &pOv->m_dwBytesRecved, 152 &pOv->m_dwFlag, 153 pOv, 154 NULL); 155 } 156 157 void PostAccept(SOCKET sockListen,HANDLE hIocp) 158 { 159 //接收连接的请求 160 SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 161 HANDLE hRet = CreateIoCompletionPort( 162 (HANDLE)sockClient, 163 hIocp, 164 NULL, 165 0); 166 167 char szBuff[MAXBYTE] = { 0 }; 168 DWORD dwRecved = 0; 169 170 MYOV* pOv = new MYOV(sockClient, IO_ACCEPT); 171 172 AcceptEx( 173 sockListen, 174 sockClient, 175 szBuff, 176 0, 177 sizeof(sockaddr) + 16, 178 sizeof(sockaddr) + 16, 179 &dwRecved, 180 pOv 181 ); 182 } 183 184 void InitWs2() 185 { 186 WORD wVersionRequested; 187 WSADATA wsaData; 188 int err; 189 190 wVersionRequested = MAKEWORD(2, 2); 191 err = WSAStartup(wVersionRequested, &wsaData); 192 if (err != 0) { 193 return; 194 } 195 196 if (LOBYTE(wsaData.wVersion) != 2 || 197 HIBYTE(wsaData.wVersion) != 2) { 198 WSACleanup(); 199 return; 200 } 201 } 202 203 void UninitWs32() 204 { 205 WSACleanup(); 206 }
客户端:
#include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <Winsock2.h> #pragma comment(lib, "Ws2_32.lib") void InitWs2(); void UninitWs32(); int main() { InitWs2(); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockClient == SOCKET_ERROR) { printf("socket 创建失败\r\n"); return 0; } else { printf("socket 创建成功\r\n"); } sockaddr_in si; si.sin_family = AF_INET; si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); si.sin_port = htons(9527); int nRet = connect(sockClient, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { printf("连接服务器失败 \r\n"); return 0; } else { printf("连接服务器成功 \r\n"); } while (true) { char szBuff[MAXBYTE] = { 0 }; std::cin >> szBuff; nRet = send(sockClient, szBuff, sizeof(szBuff), 0); if (nRet == SOCKET_ERROR) { printf("发送失败\r\n"); } else { printf("发送成功 \r\n"); } } } void InitWs2() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return; } } void UninitWs32() { WSACleanup(); }
测试效果: