iocp简单例子

下方代码中,没有写注释的地方,说明与icop网络无关也就是它们都不重要,重要的位置全部都有注释,复制下方代码就可以运行看效果

iocp带网络的例子:

客户端:

客户端只有一个main,只有socket相关函数,无iocp

客户端说明:

由于和客户端只用了socket相关函数,所以客户端不重要,重要的是服务端,它使用iocp网络实现,所以用手机开热点让电脑连接热点,然后用手机浏览器访问电脑的ip和服务端开启的端口就可以看iocp网络(iocp网络的意思是,socket 加 iocp配合,实现的网络数据的接收与发送),或者用路由器让手机和电脑在一个网络下也可以,所以如果只关心iocp网络,下方客户端的代码不需要看,用手机调试 或者 用电脑浏览器调试
客户端代码:

cpp 复制代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib, "Ws2_32.lib")
#include <winsock2.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>

class COverlapped {
public:
	/**
	  重叠结构 就是 OVERLAPPED(它的中文意思是重叠,所以称它是重叠结构)
	*/
	OVERLAPPED m_overlapped;
	DWORD m_operator;
	char m_buffer[4096];
	SOCKET client = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	COverlapped() {
		m_operator = 0;
		memset(&m_overlapped, 0, sizeof(m_overlapped));
		memset(m_buffer, 0, sizeof(m_buffer));
	}
};

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("WSAStartup() error");
		return -1;
	}
	SOCKET hSocket;
	hSocket = socket(AF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN servAddr{};
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servAddr.sin_port = htons(9527);
	/*********************************************连接*********************************************************/
	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
		printf("connect error");
		return -1;
	}
	/*********************************************连接完成*********************************************************/




	/*********************************************等待接收服务端发来的数据*********************************************************/
	char message[30];
	int strlen = recv(hSocket, message, 30, 0);
	if (strlen == -1)
	{
		printf("recv error");
		return -1;
	}
	printf("recv message: %s\n", message);
	/*********************************************等待接收服务端发来的数据结束*********************************************************/


	/*********************************************给服务端发数据*********************************************************/
	
	/*
		下方代码(char* str = XXXX这个代码)是写着玩的,现在没有任何作用,所以给注释了
		char* str = (char*)"fkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffkhsklffk";
	*/
	for (int i = 4; i < 100; i++)
	{
		;
		/*
			send函数发送一个数据给socket
			std::to_string(i).c_str() 把一个数字转成字符串
		*/
		send(hSocket, std::to_string(i).c_str(), ::strlen(std::to_string(i).c_str())+1, 0);
		printf("%s\n", std::to_string(i).c_str());
		// Sleep(1000);
	
	// WSASend函数发送数据,这里的WSASend没有绑定iocp(这个代码无iocp,单纯的socket),所以它只能发送,发送成功还是发送不成功,没有iocp没法知道结果
	/*	
		WSABUF m_wsabuffer{};
		DWORD m_received;
		DWORD m_flags;
		m_flags = 0;
		m_received = 0;
		COverlapped overlapped2{};
		overlapped2.m_operator = 2;
		m_wsabuffer.buf = (char*)std::to_string(i).c_str();
		m_wsabuffer.len = ::strlen(std::to_string(i).c_str())+1;
		WSASend(hSocket, &m_wsabuffer, 1, &m_received, m_flags, &overlapped2.m_overlapped, NULL);
		int tt = WSAGetLastError();
		int a = tt;
	*/
	}

	// 下方是用 WSASend函数 发送一个指定的字符串给服务器
	/*
		WSABUF m_wsabuffer{};
		DWORD m_received;
		DWORD m_flags;
		m_flags = 0;
		m_received = 0;
		COverlapped overlapped2{};
		overlapped2.m_operator = 2;
		char* str = (char*)"fkhsklf";
		m_wsabuffer.buf = str;
		m_wsabuffer.len = ::strlen(str) + 1;
		WSASend(hSocket, &m_wsabuffer, 1, &m_received, m_flags, &overlapped2.m_overlapped, NULL);
	*/

	/*********************************************给服务端发数据结束*********************************************************/
	closesocket(hSocket);
	WSACleanup();
	system("pause");
	return 0;
}

服务端

服务端代码观看方式(观看思路):

从main函数开始看,或者搜索 WSARecv、AcceptEx、WSASend、CreateIoCompletionPort、GetQueuedCompletionStatus这些关键字

iocp网络相关函数说明:

WSARecv是注册一个接收数据的iocp,当接收到数据会进入iocp回调函数,也就是 GetQueuedCompletionStatus退出阻塞状态,可以从第二个参数的内存地址得到接收的数据

AcceptEx是注册一个新连接的iocp,当新的连接连接成功后会进入iocp回调函数,也就是 GetQueuedCompletionStatus退出阻塞状态

WSASend是注册一个发送数据的iocp,当发送成功会进入iocp回调函数,也就是 GetQueuedCompletionStatus退出阻塞状态

CreateIoCompletionPort让一个socket绑定一个iocp

GetQueuedCompletionStatus获取一个iocp,如果没有iocp,将进入阻塞

iocp:iocp(完成端口)官方说明

大概意思就是当一个进程创建了iocp之后,操作系统会给创建一个队列,让这个队列与进程进行关联,专门给这个进程处理请求服务,由于它是队列所以支持线程安全,可以把iocp当作一个高性能锁,它比任意多线程安全机制都要快
服务端代码:

cpp 复制代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib,"Ws2_32.lib")
#pragma comment(lib,"Mswsock.lib")
#include <process.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#include <stdio.h>

int i = 0; // 这个不要管,写着玩的
char strarr[999999]{};// 接收数据的缓存,也就是 WSARecv 接收的数据

class COverlapped { // 重叠结构
public:
    /**
      只要有 OVERLAPPED 这个类型并且放在第一个位置,那这个结构体就可以成为重叠结构
    */
    OVERLAPPED m_overlapped;
    DWORD m_operator;
    int clientIndex;
    char m_buffer[4096];
    COverlapped() {
        m_operator = 0;
        memset(&m_overlapped, 0, sizeof(m_overlapped));
        memset(m_buffer, 0, sizeof(m_buffer));
    }
};
HANDLE hIOCP;
SOCKET sock;

/*
    最多只能连接5个客户端,如果要更多的客户端,需要添加心跳包功能,用心跳包检测socket连接状态,也就是服务端给客户端发送一个数据,客户端接收到必须马上给服务端返回一个数据
    否则视为当前sokcet已经断开连接需要释放资源,这样释放资源之后就可以给新的客户端用了,也就可以处理新的连接了
*/
SOCKET clients[5];
int i2 = 1; // socket的索引,也就是 clients 它的索引


void iocp() {
    // SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); // TCP
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed.\n");
        return;
    }

    for (int i = 0; i < 5; i++)
    {
        clients[i] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    }

    // 创建一个套接字用于绑定iocp,让AcceptEx生效
    sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    //client = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sock == INVALID_SOCKET) {
        int ttt = WSAGetLastError();
        int a = ttt;
        return;
    }
    // 重建一个iocp
    hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, sock, 4);
    // SOCKET client = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    // sock与iocp绑定
    CreateIoCompletionPort((HANDLE)sock, hIOCP, 0, 0);

    // 下方是设置套接字ip与端口
    sockaddr_in addr;
    addr.sin_family = PF_INET;// PF_INET 与 AF_INET是一个东西,用哪一个都行,混着用也行
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");// 监听所有ip
    addr.sin_port = htons(9527);
    bind(sock, (sockaddr*)&addr, sizeof(addr)); // ip信息绑定socket
    listen(sock, 5);


    COverlapped _Aoverlapped; {}; // 创建重叠结构
    _Aoverlapped.m_operator = 1; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
    _Aoverlapped.clientIndex = 0;
    DWORD received = 0; // 固定写死

    // AcceptEx是异步的,调用之后很快就会返回,投递给iocp一个新连接事件
    if (AcceptEx(sock, clients[_Aoverlapped.clientIndex], _Aoverlapped.m_buffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, &received, &_Aoverlapped.m_overlapped) == FALSE) {
      
        /*
            WSAGetLastError 获取上一个函数执行结果,只能针对套接字相关使用,它的返回值是上一个函数的执行状态,可以得到执行成功还是执行失败
            它有许多错误类型,可以去 msdn(微软官方文档简称msdn) 搜 WSAGetLastError,然后从 WSAGetLastError函数说明的页面里可以找到返回值都有哪些以及返回值说明
        */
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
            return;
        }
    }
}

unsigned int __stdcall  ThreadFunc(void * a) {
    // 开启线程
    while (true) // 代表一个线程
    {
        LPOVERLAPPED pOverlapped{};
        DWORD transferred = 0;
        DWORD key = 0;
    
        /*
            第二个参数,也就是transferred可以得到有效字节数,也就是 WSARecv 接收数据的长度
            依据tcp或udp协议不同,数据大会造成分包发送,分包是顺序的,不用担心数据会乱,所以需要在发送的数据包里,设置数据包大小
            然后通过 transferred 判断数据包是否接收完毕,比如 transferred 它的数字小于,我们数据包里设置的大小,就说明数据包没有接收完毕,需要拼接数据
            pOverlapped 获取重叠结构,重叠结构是用来在使用 iocp 时进行iocp内部的传参
            hIOCP 绑定了什么socket,它就触发什么,如果socket没有与它绑定,任何操作都不会触发 GetQueuedCompletionStatus 函数,也就是无法让 GetQueuedCompletionStatus 函数退出阻塞状态
        */
        if (GetQueuedCompletionStatus(hIOCP, &transferred, &key, &pOverlapped, WSA_INFINITE)) {
            COverlapped* pO = CONTAINING_RECORD(pOverlapped, COverlapped, m_overlapped);
           
            switch (pO->m_operator)
            {
            case 1: {
                printf("AcceptEx\n");
                
                /*
                  有客户端连接,这里把客户端连接绑定到 IOCP 里,如果不绑定,WSARecv会失效,WSASend可以发送
                  但是发送成功无法触发 GetQueuedCompletionStatus 函数的回调,也就是无法让 GetQueuedCompletionStatus 函数退出阻塞状态
                */
                CreateIoCompletionPort((HANDLE)clients[pO->clientIndex], hIOCP, 0, 0);
     
            /*********************************************************************************************************************************************/
                DWORD Flags = 0; // 固定写死
                DWORD dwRecv = 0; // 固定写死
                COverlapped overlapped3{}; // 创建重叠结构,并初始化内存
                overlapped3.m_operator = 3; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
                overlapped3.clientIndex = pO->clientIndex;
                WSABUF m_wsabuffer2{}; // 接收数据的缓冲区结构
                m_wsabuffer2.buf = strarr; // 设置缓冲区,也就是一块存放数据的内存,当Recv成功之后,会把数据写到这里
                m_wsabuffer2.len = 999999; // 内存大小,buf只是一块内存,只有内存地址没有大小,所以这里告知程序此处内存有多大
                /* 
                    添加Recv监听,告诉iocp只要有Recv操作,也就是有人给我发了消息你就触发 GetQueuedCompletionStatus 函数的回调
                    然后通过 m_operator 就能知道这是什么操作,然后在对应的switch里处理
                */
                WSARecv(clients[overlapped3.clientIndex], &m_wsabuffer2, 1, &dwRecv, &Flags, &overlapped3.m_overlapped, NULL);
             /*********************************************************************************************************************************************/
             
             /*0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000*/
                sockaddr* pLocal = NULL, * pRemote = NULL;
                int nLocal = 0, nRemote = 0;
                /* 
                    GetAcceptExSockaddrs函数它的第一个参数是 AcceptEx 函数的第三个参数,AcceptEx函数会把本地ip和对方ip返回到第三个参数里
                    GetAcceptExSockaddrs函数它只是用来解析远程地址和本地地址的
                 */
                GetAcceptExSockaddrs(&pO->m_buffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, &pLocal, &nLocal, &pRemote, &nRemote);

                sockaddr_in* pP = (sockaddr_in*)pLocal;
                printf("本地IP:%s ", inet_ntoa(pP->sin_addr));
                printf("对方端口号:%d \n", ntohs(pP->sin_port));

                sockaddr_in* pR = (sockaddr_in*)pRemote;
                printf("对方IP:%s ", inet_ntoa(pR->sin_addr));
                printf("对方端口号:%d \n", ntohs(pR->sin_port));
             /*0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000*/
             
             /*============================================================================================================================================*/
                WSABUF m_wsabuffer{}; // 缓冲区,发送的数据内容
                DWORD m_received = 0; // 固定写死
                DWORD m_flags = 0; // 固定写死
                DWORD received = 0; // 固定写死
                COverlapped overlapped2{}; // 创建重叠结构
                overlapped2.m_operator = 2; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
                overlapped2.clientIndex = pO->clientIndex;
                char* str = (char*)"000";
                m_wsabuffer.buf = str; // 发送的数据内容
                m_wsabuffer.len = strlen(str) + 1; // 数据大小,C++里的字符串是以0结尾,strlen(str)计算的长度不包含0,所以这里+1
                // 发送数据
                WSASend(clients[overlapped3.clientIndex], &m_wsabuffer, 1, &m_received, m_flags, &overlapped2.m_overlapped, NULL);
             /*============================================================================================================================================*/


             /*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/
                COverlapped overlapped{}; // 创建重叠结构
                overlapped.m_operator = 1; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
                overlapped.clientIndex = i2++;
                /*
                    当前是在 case 1 里,也就是当有了新的连接,它就会调用 GetQueuedCompletionStatus 函数的回调,然后再得到这是什么类型,如果类型是1就会进入 case 1 里,也就是会来段现在的位置
                    下方的写法,首先AcceptEx是注册一个新连接事件(准确说是投递到iocp的队列里一个连接请求),也就是告诉iocp,当有了新连接就通知 GetQueuedCompletionStatus 函数的回调
                    通知完AcceptEx注册了事件将被删除,被删除了,就说明下次没法触发了,所以这里再次注册 AcceptEx
                */
                AcceptEx(sock, clients[overlapped.clientIndex], overlapped.m_buffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, &received, &overlapped.m_overlapped);
             /*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/
                i++;
                break;
            }

            case 2: { // 这里是 WSASend 成功的处理
                //printf("send %d--\n", i);
                printf("send %d -- %s\n", i, strarr);
                WSABUF m_wsabuffer{}; // 缓冲区,发送的数据内容
                DWORD m_received = 0; // 固定写死
                DWORD m_flags = 0; // 固定写死
                DWORD received = 0; // 固定写死
                COverlapped overlapped2{}; // 创建重叠结构
                overlapped2.m_operator = 2; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
                overlapped2.clientIndex = pO->clientIndex;
                char* str = (char*)"000";
                m_wsabuffer.buf = str; // 发送的数据内容
                m_wsabuffer.len = strlen(str) + 1; // 数据大小,C++里的字符串是以0结尾,strlen(str)计算的长度不包含0,所以这里+1
                // 发送数据
                // WSASend(clients[overlapped2.clientIndex], &m_wsabuffer, 1, &m_received, m_flags, &overlapped2.m_overlapped, NULL);
                break;
            }
            case 3: { // 这里是 WSARecv 成功的处理
                printf("recv %d -- %s\n", i, strarr);// 客户端循环发送数据,这里 strarr 里的值只是当前接收的数据,接收过的数据会被覆盖,所以要创建一个缓冲区,做数据拼接,在这一行下断点就可以看出这个问题
                DWORD Flags = 0; // 固定写死
                DWORD dwRecv = 0; // 固定写死
                COverlapped overlapped3{}; // 创建重叠结构,并初始化内存
                overlapped3.m_operator = 3; // 设置当前操作类型,用于在 GetQueuedCompletionStatus 函数的回调中使用
                overlapped3.clientIndex = pO->clientIndex;
                WSABUF m_wsabuffer2{}; // 接收数据的缓冲区结构
                m_wsabuffer2.buf = strarr; // 设置缓冲区,也就是一块存放数据的内存,当Recv成功之后,会把数据写到这里
                m_wsabuffer2.len = 999999; // 内存大小,buf只是一块内存,只有内存地址没有大小,所以这里告知程序此处内存有多大
                /*
                    添加Recv监听,告诉iocp只要有Recv操作,也就是有人给我发了消息你就触发 GetQueuedCompletionStatus 函数的回调
                    然后通过 m_operator 就能知道这是什么操作,然后在对应的switch里处理
                */
                WSARecv(clients[overlapped3.clientIndex], &m_wsabuffer2, 1, &dwRecv, &Flags, &overlapped3.m_overlapped, NULL);
                break;
            }
            default:
                break;
            }
        }
    }
    return 0;
}
bool 控制台事件处理(DWORD type) {
    switch (type)
    {
    case CTRL_CLOSE_EVENT: { // 点击控制台关闭时,会来到这里
        for (int i = 0; i < clientLen; i++)
        {
            closesocket(clients[i]);
            clients[i] = INVALID_SOCKET;
        }
        closesocket(sock);
        CloseHandle(hIOCP);
        WSACleanup();
        printf("关闭套接字完成");
        return false;
    }
    default:
        break;
    }
}

int main() {
    
    // 给控制台添加一个监听,为了实现控制台关闭时关闭socket、iocp相关句柄资源
    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)控制台事件处理, true))
        return 0;
    // 初始化 iocp 环境
    iocp();

    // iocp处理事件线程,主要看 ThreadFunc函数 里的case 1里的代码,看懂了它,就懂iocp网络了
    _beginthreadex(NULL, 0, ThreadFunc, (LPVOID)hIOCP, 0, NULL);


    /*=================================下方支持控制台输入同时避免主线程结束导致iocp网络结束=================================*/
    HANDLE ConsoleWin;
    INPUT_RECORD eventMsg;
    DWORD Pointer;
    ConsoleWin = GetStdHandle(STD_INPUT_HANDLE); // 获取控制台句柄,没有句柄没法获取它的输入
    while (1) { // 
        ReadConsoleInput(ConsoleWin, &eventMsg, 1, &Pointer);//Read input msg
        if (eventMsg.EventType == MOUSE_EVENT && eventMsg.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED) {
            printf("Right button clicked.");
        }
        if (eventMsg.EventType == MOUSE_EVENT && eventMsg.Event.MouseEvent.dwEventFlags == RIGHTMOST_BUTTON_PRESSED) {
            printf("Left button double clicked.");
        }

        if (eventMsg.EventType == KEY_EVENT) {

            /* 
                eventMsg.Event.KeyEvent.uChar.AsciiChar只能获取键盘左边英文字母、数字、标点符号
                其它的要根据键盘的虚拟键码来得到(eventMsg.Event.KeyEvent.wVirtualKeyCode)
            */
            printf("uChar:%c\t", eventMsg.Event.KeyEvent.uChar.AsciiChar);
          
            /*
                eventMsg.Event.KeyEvent.dwControlKeyState是用来控制键的状态
                也就是Ctrl、Alt、大写键的状态
                下方是 eventMsg.Event.KeyEvent.dwControlKeyState 的使用方式
            */ 
            int daxie = eventMsg.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON;
            int zCtrl = eventMsg.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED ;
            int nu = eventMsg.Event.KeyEvent.dwControlKeyState & NUMLOCK_ON;
            if (nu == NUMLOCK_ON) {
                printf("按下了nu\t");
            }
            if (daxie == CAPSLOCK_ON) {
                printf("开启大写\t");
            }
            if (zCtrl == LEFT_CTRL_PRESSED) {
                printf("按下了做Ctrl键\t");
            }
            printf("%x\t", eventMsg.Event.KeyEvent.dwControlKeyState);

            // 虚拟键码
            printf("当前:%X", eventMsg.Event.KeyEvent.wVirtualKeyCode);
            printf("\n");
        }
    }

    printf("\n退出\n");
    return 0;
}

首先说明:纯iocp使用的例子看:纯iocp例子(里面的代码可能无法运行,但是下面的代码一定可以运行,可以看看它里面的 PostQueuedCompletionStatus函数 的使用,参考参考然后拿出来放到下面的代码里测试,搞几下就能懂了),主要涉及api:PostQueuedCompletionStatus(它可以触发一次iocp回调,也就是可以手动触发一次iocp回调,可以用来做多线程环境的锁)


控制台界面:控制台版坦克大战

相关推荐
麻瓜也要学魔法1 小时前
链路状态路由协议-OSPF
网络
Estar.Lee1 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
傻啦嘿哟2 小时前
代理IP在后端开发中的应用与后端工程师的角色
网络·网络协议·tcp/ip
Red Red2 小时前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
亚远景aspice4 小时前
ISO 21434标准:汽车网络安全管理的利与弊
网络·web安全·汽车
Estar.Lee4 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
友友马5 小时前
『 Linux 』网络层 - IP协议(一)
linux·网络·tcp/ip
码老白6 小时前
【老白学 Java】Warshipv2.0(二)
java·网络
HackKong6 小时前
小白怎样入门网络安全?
网络·学习·安全·web安全·网络安全·黑客