2022-08-23 Windows_iocp一般编程方法

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);
	}
*/

二、编译环境+测试结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShaYQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值