1. 概念
- IOCP(I/O Completion Port),常称I/O完成端口;
- IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术,适用于大型项目,处理高并发问题;
- 处理异步I/O操作的模型。
2. 工作机制
IOCP有一个队列,当你要发数据时,收数据和连接时,都交由IOCP队列处理,不会与操作系统底层交互。
- 发送数据时,先将缓冲区和长度封好,这个请求会发送到IOCP队列,IOCP内部会帮你把请求发出去;
- 收数据时,收数据的请求丢掉IOCP队列,IOCP会将收到的数据填入指定的缓冲区里边,当数据收好后会通知你来收数据。
- 建立连接时,IOCP帮你把连接建立好,告诉你新的连接已经来了。
3. 优点
- 维持重复使用的内存池。(与重叠I/O技术有关)
- 去除删除线程创建/终结负担
- 利于管理,分配线程,控制并发,最小化的线程上下文切换
- 优化线程调度,提高CPU和内存缓冲的命中率
4. 涉及的技术
- 同步与异步
- 阻塞与非阻塞
- 重叠I/O技术
- 多线程
- 栈、队列这两种基本的数据结构
5. API函数
5.1 与SOCKET相关
- 链接套接字动态链接库:int WSAStartup(...);
- 创建套接字库: SOCKET socket(...);
- 绑字套接字: int bind(...);
- 套接字设为监听状态: int listen(...);
- 接收套接字: SOCKET accept(...);
- 向指定套接字发送信息:int send(...);
- 从指定套接字接收信息:int recv(...);
5.2 与线程相关
- HANDLE CreateThread(...);
5.3 与重叠I/O相关
- 向套接字发送数据: int WSASend(...);
- 向套接字发送数据包: int WSASendFrom(...);
- 从套接字接收数据: int WSARecv(...);
- 从套接字接收数据包: int WSARecvFrom(...);
5.4 与iocp相关
5.4.1 创建ICOCP对象
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // 句柄,首次创建时填INVALID_HANDLE_VALUE
HANDLE ExistingCompletionPort, // I/O完成端口句柄 ,首次创建给NULL
ULONG_PTR CompletionKey, // 创建自定义对象
DWORD NumberOfConcurrentThreads) //允许应用程序同时执行的线程数量,填0,根据CPU核数自动计算核数
该函数实际用于两个明显有别的目的:
- 用于创建一个完成端口对象;
- 将一个句柄同完成端口关联到一起。
5.4.2 关联完成端口
HANDLE WINAPI CreateIoCompletionPort(...);
5.4.3 向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);
5.4.4 检测队列,从队列中取出完成的请求
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // 句柄
LPDWORD lpNumberOfBytes, // 接收字节数
PULONG_PTR lpCompletionKey, // 自定义参数
LPOVERLAPPED *lpOverlapped, //返回的结构体参数
DWORD dwMilliseconds); //等待的时间
5.4.5 投递一个队列完成状态
BOOL WINAPI PostQueuedCompletionStatus(...);
6. 示例
服务端
#include <iostream>
#include <vector>
using namespace std;
#define FD_SETSIZE 128
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
#include <Mswsock.h>
#pragma comment(lib, "Mswsock.lib")
void InitWs2();
void UninitWs32();
void PostAccept(SOCKET sockListen, HANDLE hIocp);
void PostRecv(SOCKET sock);
enum IO_EVENT
{
IO_ACCEPT,
IO_RECV,
IO_SEND
};
struct MYOV :public OVERLAPPED
{
MYOV(SOCKET sock, IO_EVENT event)
{
memset(this, 0, sizeof(MYOV));
m_sockClient = sock;
m_buf.buf = m_btBuf;
m_buf.len = sizeof(m_btBuf);
m_dwBytesRecved = 0;
m_dwFlag = 0;
m_event = event;
}
IO_EVENT m_event;
SOCKET m_sockClient;
WSABUF m_buf;
CHAR m_btBuf[MAXBYTE];
DWORD m_dwBytesRecved;
DWORD m_dwFlag;
};
int main()
{
InitWs2();
SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockServer == SOCKET_ERROR)
{
printf("socket 创建失败\r\n");
return 0;
}
else
{
printf("socket 创建成功\r\n");
}
//2)
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 = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
printf("绑定端口失败\r\n");
return 0;
}
else
{
printf("绑定端口成功\r\n");
}
//3)
nRet = listen(sockServer, SOMAXCONN);
if (nRet == SOCKET_ERROR)
{
printf("监听失败 \r\n");
return 0;
}
else
{
printf("监听成功 \r\n");
}
//1)创建IOCP对象
ULONG uKey = 0;
HANDLE hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
NULL,
0);
//2) 关联IOCP和socket对象
HANDLE bRet=CreateIoCompletionPort(
(HANDLE)sockServer,
hIocp,
NULL,
0);
//3)投递一个接收连接的请求
PostAccept(sockServer,hIocp);
//遍历队列
while (true)
{
DWORD dwBytesTranfered = 0;
ULONG_PTR uKey;
LPOVERLAPPED pOv = NULL;
GetQueuedCompletionStatus(
hIocp,
&dwBytesTranfered,
&uKey,
&pOv,
INFINITE
);
MYOV* pov = (MYOV*)pOv;
switch (pov->m_event)
{
//接收新的连接
case IO_ACCEPT:
//连接完成后,再次投递一个连接的请求
PostAccept(sockServer, hIocp);
cout << " 有新的连接接入" << endl;
PostRecv(pov->m_sockClient);
break;
case IO_RECV:
//投递一个接收数据的请求
printf("接收到数据%s\r\n", pov->m_btBuf);
PostRecv(pov->m_sockClient);
break;
default:
break;
}
}
}
void PostRecv(SOCKET sock)
{
//接收数据的请求
MYOV* pOv = new MYOV(sock, IO_RECV);
int nRet = WSARecv(
sock,
&pOv->m_buf, 1,
&pOv->m_dwBytesRecved,
&pOv->m_dwFlag,
pOv,
NULL);
}
void PostAccept(SOCKET sockListen,HANDLE hIocp)
{
//接收连接的请求
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
HANDLE hRet = CreateIoCompletionPort(
(HANDLE)sockClient,
hIocp,
NULL,
0);
char szBuff[MAXBYTE] = { 0 };
DWORD dwRecved = 0;
MYOV* pOv = new MYOV(sockClient, IO_ACCEPT);
AcceptEx(
sockListen,
sockClient,
szBuff,
0,
sizeof(sockaddr) + 16,
sizeof(sockaddr) + 16,
&dwRecved,
pOv
);
}
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();
}
客户端
#include <iostream>
#include <vector>
using namespace std;
#define FD_SETSIZE 128
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
#include <Mswsock.h>
#pragma comment(lib, "Mswsock.lib")
void InitWs2();
void UninitWs32();
void PostAccept(SOCKET sockListen, HANDLE hIocp);
void PostRecv(SOCKET sock);
enum IO_EVENT
{
IO_ACCEPT,
IO_RECV,
IO_SEND
};
struct MYOV :public OVERLAPPED
{
MYOV(SOCKET sock, IO_EVENT event)
{
memset(this, 0, sizeof(MYOV));
m_sockClient = sock;
m_buf.buf = m_btBuf;
m_buf.len = sizeof(m_btBuf);
m_dwBytesRecved = 0;
m_dwFlag = 0;
m_event = event;
}
IO_EVENT m_event;
SOCKET m_sockClient;
WSABUF m_buf;
CHAR m_btBuf[MAXBYTE];
DWORD m_dwBytesRecved;
DWORD m_dwFlag;
};
int main()
{
InitWs2();
SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockServer == SOCKET_ERROR)
{
printf("socket 创建失败\r\n");
return 0;
}
else
{
printf("socket 创建成功\r\n");
}
//2)
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 = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
printf("绑定端口失败\r\n");
return 0;
}
else
{
printf("绑定端口成功\r\n");
}
//3)
nRet = listen(sockServer, SOMAXCONN);
if (nRet == SOCKET_ERROR)
{
printf("监听失败 \r\n");
return 0;
}
else
{
printf("监听成功 \r\n");
}
//1)创建IOCP对象
ULONG uKey = 0;
HANDLE hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
NULL,
0);
//2) 关联IOCP和socket对象
HANDLE bRet=CreateIoCompletionPort(
(HANDLE)sockServer,
hIocp,
NULL,
0);
//3)投递一个接收连接的请求
PostAccept(sockServer,hIocp);
//遍历队列
while (true)
{
DWORD dwBytesTranfered = 0;
ULONG_PTR uKey;
LPOVERLAPPED pOv = NULL;
GetQueuedCompletionStatus(
hIocp,
&dwBytesTranfered,
&uKey,
&pOv,
INFINITE
);
MYOV* pov = (MYOV*)pOv;
switch (pov->m_event)
{
//接收新的连接
case IO_ACCEPT:
//连接完成后,再次投递一个连接的请求
PostAccept(sockServer, hIocp);
cout << " 有新的连接接入" << endl;
PostRecv(pov->m_sockClient);
break;
case IO_RECV:
//投递一个接收数据的请求
printf("接收到数据%s\r\n", pov->m_btBuf);
PostRecv(pov->m_sockClient);
break;
default:
break;
}
}
}
void PostRecv(SOCKET sock)
{
//接收数据的请求
MYOV* pOv = new MYOV(sock, IO_RECV);
int nRet = WSARecv(
sock,
&pOv->m_buf, 1,
&pOv->m_dwBytesRecved,
&pOv->m_dwFlag,
pOv,
NULL);
}
void PostAccept(SOCKET sockListen,HANDLE hIocp)
{
//接收连接的请求
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
HANDLE hRet = CreateIoCompletionPort(
(HANDLE)sockClient,
hIocp,
NULL,
0);
char szBuff[MAXBYTE] = { 0 };
DWORD dwRecved = 0;
MYOV* pOv = new MYOV(sockClient, IO_ACCEPT);
AcceptEx(
sockListen,
sockClient,
szBuff,
0,
sizeof(sockaddr) + 16,
sizeof(sockaddr) + 16,
&dwRecved,
pOv
);
}
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();
}