• windows网络编程 --网络聊天室(2)


    IOCP模型

    IOCP :输入输出完成端口。

    支持多个同时发生的异步I/O操作的应用程序编程接口,IOCP特别适合C/S模式网络服务器端模型。

    因为,让每一个socket有一个线程负责同步(阻塞)数据处理,one-thread-per-client的缺点是:一是如果连入的客户多了,就需要同样多的线程;二是不同的socket的数据处理都要线程切换的代价。
    最佳线程数量=cpu内核数量*2

    一个IOCP对象,在操作系统中可关联着多个Socket和(或)文件控制端。 IOCP对象内部有一个先进先出(FIFO)队列,用于存放IOCP所关联的输入输出端的服务请求完成消息。请求输入输出服务的进程不接收IO服务完成通知,而是检查IOCP的消息队列以确定IO请求的状态。

    • 队列有消息: (线程池中的)多个线程负责从IOCP消息队列中取走完成通知并执行数据处理
    • 如果队列中没有消息,那么线程阻塞挂起在该队列。这些线程从而实现了负载均衡。

    IOCP是一个内核对象,但是他是一个不需要安全属性的Windows内核对象


    常用IOCP函数

    使用IOCP模型,首先必须创建(或者关联)一个IOCP对象(与文件句柄):

    HANDLE CreateIoCompletionPort(
      [in]           HANDLE    FileHandle,//要关联的文件句柄
      [in, optional] HANDLE    ExistingCompletionPort,//完成端口句柄
      [in]           ULONG_PTR CompletionKey,//完成键
      [in]           DWORD     NumberOfConcurrentThreads//允许的最大线程数
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    CreateIoCompletionPort 函数创建 IOCP完成端口并将其与指定的文件句柄相关联,或者创建一个新的未被关联的IOCP对象。

    • 参数一:接受一个文件句柄或者为NULL。 文件句柄:将其与一个已存在的IOCP对象相关联; NULL:创建一个新的IOCP对象。
    • 参数二:接受一个现有IOCP对象或 NULL 的句柄。现有IOCP对象: 将其与一个文件句柄相关联;NULL:创建一个新的IOCP对象。
    • 参数三:指定文件句柄的每个 I/O 完成数据包中包含的每句柄用户定义完成密钥。如果是关联已存在文件句柄,则此参数为此文件句柄,否则为空。
    • 参数四:一个IOCP允许处理的最大线程数,NULL为系统默认。

    获取IOCP的消息队列:

    BOOL GetQueuedCompletionStatus(
      [in]  HANDLE       CompletionPort,//IOCP对象句柄
            LPDWORD      lpNumberOfBytesTransferred,//IO操作传输的字节数
      [out] PULONG_PTR   lpCompletionKey,//发送消息方
      [out] LPOVERLAPPED *lpOverlapped,//接受消息,并且存储消息的结构体指针
      [in]  DWORD        dwMilliseconds//接受等待时间
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    GetQueuedCompletionStatus 函数尝试从指定的 I/O 完成端口取消 I/O 完成数据包的排队。
    如果没有排队的完成数据包,该函数将等待与完成端口关联的挂起 I/O 操作完成。

    说白了就是 接收从指定的发送方发送的消息,并且取消排队。

    • 参数一:正在操作的IOCP对象。
    • 参数二 :准备从发送方接收的数据的字节数量,接收成功返回接收的字节数,失败返回NULL,表示读取失败。
    • 参数三:指定的消息发送方,即客户端。
    • 参数四:接收消息的一个结构体,消息信息都存储在这个结构体中。
    • 参数五:等待的时间,相当于WaitForSingleoObect的函数等待。

    基于IOCP的网络聊天室

    服务器端

    #include 
    #include 
    #include 
    #include 
    #include 
    #pragma comment(lib,"ws2_32.lib")
    
    std::vector<SOCKET> Vec_Socket;
    
    typedef struct My_Overlapped
    {
    	OVERLAPPED wsaoverlapped;
    	WSABUF wsabuf;
    }My_Overlapped,*PMy_Overlapped;
    
    DWORD WINAPI DoMessage(
    	LPVOID lpParameter
    )
    {
    	HANDLE SocketHandle = (HANDLE)lpParameter;
    	BOOL Result = FALSE;
    	PMy_Overlapped myOverlapped = nullptr;
    	DWORD NumOfReadISze = 0;
    	ULONG_PTR ComplixKey = 0;
    	DWORD Flags = 0;
    	//指向接收与 I / O 操作已完成的文件句柄关联的完成键值的变量的指针
    	ULONG_PTR client{ 0 };
    	while (true)
    	{
    		//等待消息的读入
    		Result =  GetQueuedCompletionStatus(
    			SocketHandle,
    			&NumOfReadISze,
    			&client,		//从每一个客户句柄里读取信息
    			(LPOVERLAPPED*)&myOverlapped,
    			INFINITE
    		);
    		if (Result && NumOfReadISze > 0)
    		{
    			for (UINT i = 0; i < Vec_Socket.size(); i++)
    			{
    				//发送消息
    				if (Vec_Socket[i] != client)
    				{//句柄不是你自己,往其他人客户端发送消息
    					send(Vec_Socket[i], myOverlapped->wsabuf.buf, myOverlapped->wsabuf.len, NULL);
    				}
    			}
    			//清空缓冲区
    			memset(myOverlapped->wsabuf.buf, 0, 0x100);
    			memset(&myOverlapped->wsaoverlapped, 0, sizeof(OVERLAPPED));
    			WSARecv(
    				client,
    				&myOverlapped->wsabuf,
    				1,
    				&NumOfReadISze,
    				&Flags,
    				(LPWSAOVERLAPPED)myOverlapped,
    				NULL
    			);
    		}
    		else
    		{
    			//卸载客户端
    			for (UINT i = 0; i < Vec_Socket.size(); i++)
    			{
    				if (Vec_Socket[i] == client)
    				{
    					closesocket(client);
    					printf("客户端%u断开了连接\n", Vec_Socket[i]);
    					Vec_Socket.erase(Vec_Socket.begin() + i);
    				}
    			}
    		}
    	}
    	return 1;
    }
    
    
    int main()
    {
    	// 创建IOCP对象
    	HANDLE IOCP =  CreateIoCompletionPort(
    		INVALID_HANDLE_VALUE,
    		NULL,
    		NULL,
    		NULL
    	);
    	if (!IOCP)
    	{
    		printf("IOCP创建失败!\n");
    		return 0;
    	}
    	//获取CPU的内核处理器数量
    	SYSTEM_INFO system_info{ 0 };
    	GetNativeSystemInfo(&system_info);
    	for (UINT i = 0; i < system_info.dwNumberOfProcessors*2; i++)
    	{
    		CreateThread(
    			NULL,
    			NULL,
    			DoMessage,
    			(LPVOID)IOCP,	//IOCP传入
    			NULL,
    			NULL
    		);
    	}
    
    	//1. 初始化网络环境
    	WSADATA WsaData{ 0 };
    	WSAStartup(
    		MAKEWORD(2, 2),	//套接字
    		&WsaData
    	);
    
    	//2. 创建socket套接字
    	//创建绑定到特定传输服务提供者的套接字。
    	SOCKET serve = socket(
    		AF_INET,
    		SOCK_STREAM,
    		IPPROTO_TCP
    	);
    
    	//3. 绑定端口号IP地址
    	sockaddr_in serverAddr{ 0 };
    	serverAddr.sin_family = AF_INET;			//地址族
    	serverAddr.sin_port = htons(666);	//端口号
    	//inet_pton:标准文本呈现形式中将 IPv4 或 IPv6 Internet 网络地址转换为其数字二进制形式
    	inet_pton(AF_INET, "172.20.230.126", &serverAddr.sin_addr);
    	//bind:绑定函数将本地地址与套接字相关联。
    	bind(serve, (sockaddr*)&serverAddr, sizeof(serverAddr));
    
    	//4. 监听
    	//侦听函数将套接字置于侦听传入连接的状态。
    	listen(serve, SOMAXCONN);
    
    	sockaddr_in ClientAddr{ 0 };
    	int SockSize = sizeof(ClientAddr);
    	//5. 接收会话
    	while (1)
    	{
    		//连接客户端与服务器,返回客户端句柄
    		SOCKET client = accept(
    			serve,	//标识已使用监听函数处于侦听状态的套接字
    			(sockaddr*)&ClientAddr,
    			&SockSize
    		);
    		//保存所有的客户端句柄于一个容器中
    		Vec_Socket.push_back(client);
    		//将SOCKET关联到IOCP
    		CreateIoCompletionPort(
    			(HANDLE)client,
    			IOCP,
    			client,
    			NULL
    		);
    
    		PMy_Overlapped MyOverlapped = new My_Overlapped{ 0 };
    		MyOverlapped->wsabuf.buf = new char[0x100] {0};
    		MyOverlapped->wsabuf.len = 0x100;	//缓冲区的长度	
    		DWORD RealSize = 0;
    		DWORD Flags = 0;
    		//接收消息
    		WSARecv(
    			client,
    			&MyOverlapped->wsabuf,
    			1,
    			&RealSize,
    			&Flags,
    			(LPWSAOVERLAPPED)MyOverlapped,
    			NULL
    		);
    		printf("服务器%u连接到了客户端\n", client);
    	}
    	closesocket(serve);
    	system("pause");
    	return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179

    客户端

    #include 
    #include 
    #include 
    #include 
    #pragma comment(lib,"ws2_32.lib")
    
    DWORD WINAPI GetMessageAAA(
    	LPVOID lpParameter
    )
    {
    	SOCKET Socket = (SOCKET)lpParameter;
    	CHAR DstBuff[0x100]{ 0 };
    	while (recv(Socket, DstBuff, 0x100, NULL) > 0)
    	{
    		printf("接收:%s\n", DstBuff);
    	}
    	return 1;
    }
    
    int main()
    {
    	//1. 初始化网络连接
    	WSADATA WsaData{ 0 };
    	WSAStartup(MAKEWORD(2, 2), &WsaData);
    	//2. 创建SOCKET套接字
    	SOCKET serve = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	//3. 绑定IP地址
    	sockaddr_in serveAddr{ 0 };
    	serveAddr.sin_family = AF_INET;				//地址族
    	serveAddr.sin_port = htons(666);	//端口号
    	inet_pton(AF_INET, "172.20.230.126", &serveAddr.sin_addr);
    	//连接服务器
    	//connect函数建立与指定套接字的连接。
    	int result = connect(serve, (sockaddr*)&serveAddr, sizeof(serveAddr));
    	if (result != 0)	//未发生错误,返回零
    	{
    		printf("连接失败!\n");
    		system("pause");
    		return 0;
    	}
    	//创建线程
    	HANDLE Thread = CreateThread(
    		NULL,
    		NULL,
    		GetMessageAAA,
    		(LPVOID)serve,	//传入当前的客户端句柄
    		NULL,
    		NULL
    	);
    	CHAR lpBuff[0x100]{ 0 };
    	while (scanf("%s", lpBuff) && strcmp(lpBuff, "exit"))
    	{
    		send(serve, lpBuff, strlen(lpBuff)+1, NULL);
    	}
    
    	system("pause");
    	closesocket(serve);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    运行效果:

  • 相关阅读:
    Connor学Android - OkHttp基本使用与源码解析
    【Java高级技术】动态代理
    渗透测试(1)
    Web前端:JavaScript编程语言有哪些优势?
    laravel框架的优缺点是什么?
    【注释和反射】获取class类实例的方法
    剑指 Offer II 091. 粉刷房子
    云计算 3月11号 (NFS远程共享存储及vsftpd配置)
    csrf防护机制
    Vue向pdf文件中添加二维码
  • 原文地址:https://blog.csdn.net/jj6666djdbbd/article/details/127549865