网络模型—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;
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")
11 #include <Mswsock.h>
12 #pragma comment(lib, "Mswsock.lib")
14 void InitWs2();
15 void UninitWs32();
16 void PostAccept(SOCKET sockListen, HANDLE hIocp);
17 void PostRecv(SOCKET sock);
19 enum IO_EVENT
20 {
21 IO_ACCEPT,
22 IO_RECV,
23 IO_SEND
24 };
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 };
46 int main()
47 {
48 InitWs2();
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 }
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 }
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 }
89 //1)创建IOCP对象
90 ULONG uKey = 0;
91 HANDLE hIocp = CreateIoCompletionPort(
92 INVALID_HANDLE_VALUE,
93 NULL,
94 NULL,
95 0);
97 //2) 关联IOCP和socket对象
98 HANDLE bRet=CreateIoCompletionPort(
99 (HANDLE)sockServer,
100 hIocp,
101 NULL,
102 0);
104 //3)投递一个接收连接的请求
105 PostAccept(sockServer,hIocp);
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 );
122 MYOV* pov = (MYOV*)pOv;
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 }
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 }
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);
167 char szBuff[MAXBYTE] = { 0 };
168 DWORD dwRecved = 0;
170 MYOV* pOv = new MYOV(sockClient, IO_ACCEPT);
172 AcceptEx(
173 sockListen,
174 sockClient,
175 szBuff,
176 0,
177 sizeof(sockaddr) + 16,
178 sizeof(sockaddr) + 16,
179 &dwRecved,
180 pOv
181 );
182 }
184 void InitWs2()
185 {
186 WORD wVersionRequested;
187 WSADATA wsaData;
188 int err;
190 wVersionRequested = MAKEWORD(2, 2);
191 err = WSAStartup(wVersionRequested, &wsaData);
192 if (err != 0) {
193 return;
194 }
196 if (LOBYTE(wsaData.wVersion) != 2 ||
197 HIBYTE(wsaData.wVersion) != 2) {
198 WSACleanup();
199 return;
200 }
201 }
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;
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;
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");
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();
测试效果: