Windows_iocp一般编程方法
在Linux上,我们可以使用epoll,而在Windows上我们可以使用iocp的模型;
同样的,我们都是为了实现高性能的网络IO处理模型;
下面演示一般编程方法;即时复习总结,也是学习,希望给大家有帮助。
前言
windows的IOCP模型,需要有对应的理论知识:例如socket编程一般方法、重叠IO的基础知识、内核对象管理等等;
一、代码
/*
File: main.cpp
Function: 演示iocp编程的一般方法
Writer: syq
Time: 2022-08-22
*/
/*
* 一般流程:
* 1. CreateIoCompletionPort() 创建一个完成端口;
* 2. 创建多个工作者线程;
* 3. 单独起个线程accept、或者使用Acceptex函数接收;(使用AcceptEx函数的优点和产生的高效)
* 4. 得到客户端连接的时候,使用CreateIoCompletionPort()绑定;
* 5. 工作线程中,使用GetQueuedCompletionStatus()来扫描端口完成的队列中;
*
*
*
*
* WSASocket是Windows专用,支持异步操作;
socket是unix标准,只能同步操作;
*/
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#define ServerPort 10086
#define MAX_BUFFER_LEN 1024
enum OPERATION_TYPE {
READ,
WRITE
};
typedef struct _SOCKET_DATA // 保存与客户端相连套接字
{
SOCKET m_clientSock;
SOCKADDR_IN m_clientAddr;
}SOCKET_DATA, * LPSOCKET_DATA;
typedef struct _IO_CONTEXT{
OVERLAPPED m_Overlapped; // 每一个重叠I/O网络操作都要有一个
SOCKET m_sockAccept; // 这个I/O操作所使用的Socket,每个连接的都是一样的
WSABUF m_wsaBuf; // 存储数据的缓冲区,用来给重叠操作传递参数的(WSAbuff内部是一个char*指针)
OPERATION_TYPE m_OpType; // 标志这个重叠I/O操作是做什么的,例如Accept/Recv等
char m_szBuffer[MAX_BUFFER_LEN]; // 对应WSABUF里的缓冲区
} IO_CONTEXT, * LPIO_CONTEXT;
/*
* 完成基本初始化
*/
int InitServer()
{
//1. 初始化环境
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//2 .创建socket(WSASocket与socket的区别)
struct sockaddr_in ServerAddress;
int m_sockListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
//3. 设置SO_REUSEADDR
int on = 1;
setsockopt(m_sockListen, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
//4. bind
ZeroMemory((char*)&ServerAddress, sizeof(ServerAddress));
ServerAddress.sin_family = AF_INET;
ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
ServerAddress.sin_port = htons(ServerPort);
bind(m_sockListen, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress));
//4. listen (注意OMAXCONN)
listen(m_sockListen, SOMAXCONN);
return m_sockListen;
}
/*
* 工作者线程
*/
int WINAPI _WorkerThread(LPVOID param)
{
HANDLE CompletionPort = (HANDLE)param;
DWORD dwBytesTransferred = 0;
SOCKET_DATA* pSocketData = NULL;
IO_CONTEXT* pIoData = NULL;
while (true) {
//让出CPU睡眠
//直到完成端口上出现了需要处理的网络操作或者超出了等待的时间限制为止。
//注意一下第4个参数
//第3个参数强转为SOCKET_DATA
//第4个参数强转为IO_CONTEXT(强制转换)
BOOL bReturn = GetQueuedCompletionStatus(CompletionPort,&dwBytesTransferred,(LPDWORD)&pSocketData,(LPOVERLAPPED*)&pIoData,INFINITE);
if (bReturn == FALSE) {
if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED)){
std::cout << "closingsocket" << pSocketData->m_clientSock << std::endl;
closesocket(pSocketData->m_clientSock);
delete pSocketData;
delete pIoData;
continue;
}
else
{
std::cout<<("GetQueuedCompletionStatus failed!")<<std::endl;
continue;
}
}
// 说明客户端已经退出 ,未连接
if (dwBytesTransferred == 0)
{
std::cout << "closing socket" << pSocketData->m_clientSock << std::endl;
closesocket(pSocketData->m_clientSock);
delete pSocketData;
delete pIoData;
continue;
}
// 收到 PostQueuedCompletionStatus 发出的退出指令
if (dwBytesTransferred == -1) break;
// 取得数据并处理
std::cout << pSocketData->m_clientSock <<"长度:"<< dwBytesTransferred<<"from"<< inet_ntoa(pSocketData->m_clientAddr.sin_addr) <<",发送过来的消息:" << pIoData->m_szBuffer << std::endl;
// 继续向 socket 投递WSARecv操作
DWORD Flags = 0;
DWORD dwRecv = 0;
memset(&(pIoData->m_Overlapped), 0, sizeof(OVERLAPPED));
pIoData->m_OpType = READ;
pIoData->m_sockAccept = pSocketData->m_clientSock;
memset(pIoData->m_szBuffer, 0, MAX_BUFFER_LEN);
pIoData->m_wsaBuf.len = MAX_BUFFER_LEN;
pIoData->m_wsaBuf.buf = pIoData->m_szBuffer;
WSARecv(pSocketData->m_clientSock, &pIoData->m_wsaBuf, 1, &dwRecv, &Flags, &pIoData->m_Overlapped, NULL);
}
return 0;
}
/*
* main主线程函数
* HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle, // 这里当然是连入的这个套接字句柄了
__in_opt HANDLE ExistingCompletionPort, // 这个就是前面创建的那个完成端口
__in ULONG_PTR CompletionKey, // 这个参数就是类似于线程参数一样,在
// 绑定的时候把自己定义的结构体指针传递
// 这样到了Worker线程中,也可以使用这个
// 结构体的数据了,相当于参数的传递
__in DWORD NumberOfConcurrentThreads // 这里同样置0
);
*/
int main()
{
DWORD recvBytes, i, flags = 0;
//1. 初始化服务
int m_listensocket = InitServer();
//2.创建一个完成端口,最后参数:NumberOfConcurrentThreads,设置为0,处理器数量=线程运行的数量
//建立一个完成端口
HANDLE m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (m_hIOCompletionPort == NULL) {
std::cout << "error" << __LINE__ << std::endl;
return 0;
}
//3. 创建对应数量的工作者线程,一般是CPU核心数量*2
SYSTEM_INFO si;
GetSystemInfo(&si);
int m_nThreads = si.dwNumberOfProcessors * 2;
HANDLE* m_phWorkerThreads = new HANDLE[m_nThreads];
for (int i = 0; i < m_nThreads; i++){
//将IOCP对象作为线程参数传递
m_phWorkerThreads[i] = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)_WorkerThread, m_hIOCompletionPort, 0, 0);;
}
//4. 将监听端口与iocp绑定
if (CreateIoCompletionPort((HANDLE)m_listensocket, m_hIOCompletionPort, 0, 0) == NULL) {
std::cout << "CreateIoCompletionPort failed" << WSAGetLastError() << std::endl;
closesocket(m_listensocket);
return 0;
}
//5. 进入accept
while (true) {
SOCKADDR_IN clntAdr;
int addrLen = sizeof(clntAdr);
int hClntSock = accept(m_listensocket, (SOCKADDR*)&clntAdr, &addrLen);
SOCKET_DATA* pSocketData = new SOCKET_DATA;
pSocketData->m_clientSock = hClntSock;
//获取客户端信息
memcpy(&(pSocketData->m_clientAddr), &clntAdr, addrLen);
//6. 将这个对象绑定到已经创建的完成端口对象(m_hIOCompletionPort),并且关联客户端的信息(端口和aadr) epoll_ctl
//传递的第三个参数,与GetQueuedCompletionStatus函数有关,完成绑定
if (CreateIoCompletionPort((HANDLE)hClntSock, m_hIOCompletionPort, (DWORD)pSocketData, 0) == NULL){
std::cout << "CreateIoCompletionPort error" << GetLastError() << std::endl;
closesocket(hClntSock);
delete pSocketData;
}
else {
//7. 初始化IO-context
IO_CONTEXT* pIO_CONTEXT = new IO_CONTEXT; // 相当于同时准备了WSARecv函数中需要的OVERLAPPED结构体变量、WSABUF结构体变量和缓冲
memset(&(pIO_CONTEXT->m_Overlapped), 0, sizeof(OVERLAPPED));
pIO_CONTEXT->m_OpType = READ;
pIO_CONTEXT->m_sockAccept = hClntSock;
memset(pIO_CONTEXT->m_szBuffer, 0, MAX_BUFFER_LEN);
pIO_CONTEXT->m_wsaBuf.len = MAX_BUFFER_LEN;
pIO_CONTEXT->m_wsaBuf.buf = pIO_CONTEXT->m_szBuffer;
//8. 调用WSARECV(注意:涉及到一个强制类型转换)
int nRet = WSARecv(hClntSock, &(pIO_CONTEXT->m_wsaBuf), 1, &recvBytes, &flags, &(pIO_CONTEXT->m_Overlapped), NULL); // 第六个参数相当于传入了pIO_CONTEXT结构体变量地址值
if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError())) {
std::cout << "WSARecv failed" << WSAGetLastError() << std::endl;
closesocket(hClntSock);
delete pSocketData;
delete pIO_CONTEXT;
}
}
}
closesocket(m_listensocket);
WSACleanup();
return 0;
}
/*
如何退出线程?
isShutdown = true;
for (size_t i = 0; i < NumberOfThreads; i++) {
PostQueuedCompletionStatus(hIOCP, -1, (ULONG_PTR) lpCompletionKey, nullptr);
}
*/