TCP/IP网络编程 第二十三章:IOCP

通过重叠I/O理解IOCP

本章的IOCP(Input Output CompletionPort,输入输出完成端口)服务器端模型是很多Windows程序员关注的焦点。各位若急于求成而跳过了第21章的内容,建议大家最好回顾一下。因为第21章和第22章介绍了本章的背景知识,而且,关于IOCP的内容实际上是从第22章开始的。

实现非阻塞模式的套接字

第22章中只介绍了执行重叠I/O的Sender和Receiver,但还未利用该模型实现过服务器端。因此,我们先利用重叠I/O模型实现回声服务器喘。首先介绍创建非阻塞模式套接字的方法。我们曾在第17章创建过非阻塞模式的套接字,与之类似,在Windows中通过如下函数调用将套接字属性改为非阻塞模式。

SOCKET hLisnSock;
int mode=1;
.....
hListSock=WSASocket(PF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);//创建重叠IO
ioctlsocket(hLisnSock,FIONBIO,&mode);//设定套接字为非阻塞属性
.....

上述代码中调用的ioctlsocket函数负责控制套接字I/O方式,其调用具有如下含义:“将hLisnSock句柄引用的套接字I/O模式(FIONBIO)改为变量mode中指定的形式。”

也就是说,FIONBIO是用于更改套接字I/O模式的选项,该函数的第三个参数中传入的变量中若存有0,则说明套接字是阻塞模式的;如果存有非0值,则说明已将套接字模式改为非阻塞模式。改为非阻塞模式后,除了以非阻塞模式进行I/O外,还具有如下特点。

□如果在没有客户端连接请求的状态下调用accept函数,将直接返回INVALID_SOCKET。调用WSAGetLastError函数时返回WSAEWOULDBLOCK。
□调用accept函数时创建的套接字同样具有非阻塞属性

因此,针对非阻塞套接字调用accept函数并返回INVALID_SOCKET时,应该通WSAGetLastError
函数确认返回INVALID_SOCKET的理由,再进行适当处理。

以纯重叠I/O方式实现回声服务器端

要想实现基于重叠I/O的服务器端,必须具备非阻塞套接字,所以先介绍了其创建方法。实
际上,因为有IOCP模型,所以很少有人只用重叠I/O实现服务器端。但我认为:“为了正确理解IOCP,应当尝试用纯重叠I/O方式实现服务器端。”

即使坚持不用IOCP,也应具备仅用重叠I/O方式实现类似IOCP的服务器端的能力。这样就可以在其他操作系统平台实现类似IOCP方式的服务器端,而且不会因IOCP的限制而忽略服务器端
功能的实现。

下面用纯重叠I/O模型实现回声服务器端,由于代码量较大,我们分3个部分学习。

#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
#define BUF_SIZE 1024void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD,DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(char *message);typedef struct{SOCKET hClntSock;char buf[BUF_SIZE];WSABUF wsaBuf;
} PER_IO_DATA, *LPPER IO_DATA;

该结构体中的信息足够进行数据交换,下列代码将介绍该结构体的填充及使用方法。

int main(int argc, char* argv[]){WSADATA wsaData;SOCKET hLisnSock, hRecvSock;SOCKADDR_IN lisnAdr, recvAdr;LPWSAOVERLAPPED lpOvLp;DWORD recvBytes;LPPER_IO_DATA hbInfo;int mode=1, recvAdrsz, flagInfo=0;if(argc!=2) {printf("Usage: %s <port>\n",argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)ErrorHandling("WSAStartup() error!");hLisnSock=WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);ioctlsocket(hLisnSock, FIONBIO, &mode);memset(&lisnAdr,0, sizeof(lisnAdr));lisnAdr.sin_family=AF_INET;lisnAdr.sin_addr.s_addr=htonl(INADDR_ANY);lisnAdr.sin_port=htons(atoi(argv[1]));if(bind(hLisnSock,(SOCKADDR*)&lisnAdr, sizeof(lisnAdr))==SOCKET_ERROR)ErrorHandling("bind() error");if(listen(hLisnSock,5)==SOCKET_ERROR)ErrorHandling("listen() error");recvAdrSz=sizeof(recvAdr);while(1){SleepEx(100,TRUE);hRecvSock=accept(hLisnsock,(SOCKADDR*)&recvAdr,&recvAdrSz);if(hRecvSock==INVALID_SOCKET){if(WSAGetLastError()==WSAEWOULDBLOCK)continue;else ErrorHandling("accept() error");}puts("Client connected.....");lpOvLp=(LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));hbInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));  hbInfo->hClntSock=(DWORD)hRecvSock;(hbInfo->wsaBuf).buf=hbInfo->buf;(hbInfo->wsaBuf).len=BUF_SIZE;lpOvLp->hEvent=(HANDLE)hbInfo;WSARecv(hRecvSock,&(hbInfo->wsaBuf),1,&recvBytes, &flagInfo, lpOvLP, ReadcompRoutine);}closesocket(hRecvSock);closesocket(hLisnSock);WSACleanup();return 0;}

有几点需要注意:

第46、47行:申请重叠I/O中需要使用的结构体变量的内存空间并初始化。之所以在循环
内部申请WSAOVERLAPPED结构体空间,是因为每个客户端都需要独立的WSAOVERLAPPED结构体变量。

第54行:WSAOVERLAPPED结构体变量的hEvent成员中将写入第49行分配过空间的变量
地址值。基于Completion Routine函数的重叠I/O中不需要事件对象,因此,hEvent中可以写入其他信息。

第55行:调用WSARecv函数时将ReadCompRoutine函数指定为Completion Routine。其
中第六个参数WSAOVERLAPPED结构体变量地址值将传递到Completion Routine的第三个参数,因此,Completion Routine函数内可以访问完成I/O的套接字句柄和缓冲。另外,为了运行Completion Routine函数,第35行循环调用SleepEx函数。

最后介绍两个Completion Routine函数。实际的回声服务是通过这两个函数完成的。

void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED ipOverlapped, DWORD flags){LPPER_IO_DATA hbInfo=(LPPER_IO_DATA)(1pOverlapped->hEvent);hSock=hbInfo->hClntsock;LPWSABUF bufInfo=&(hbInfo->wsaBuf);DWORD sentBytes;if(szRecvBytes==0){//如果接收到了EOF那么则关闭套接字以及释放对应的空间closesocket(hSock);free(lpoverlapped->hEvent);free(lpoverlapped); puts("Client disconnected.....");}else{//如果不为零那么一定有需要回声的内容bufInfo->len=szRecvBytes;WSASend(hSock,bufInfo,1, &sentBytes, 0, lpoverlapped, WriteCompRoutine);}
}void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpoverlapped, DWORD flags){LPPER_IO_DATA hbInfO=(LPPER_IO_DATA)(lpOverlapped->hEvent);SOCKET hSock=hbInfo->hClntSock;LPWSABUF bufInfo=&(hbInfo->wsaBuf);DWORD recvBytes;int flagInfo=0;WSARecv(hSock,bufInfo,1,&recvBytes,&flagInfo, lpoverlapped, ReadCompRoutine);//发送后默认等待再次回声
}void ErrorHandling(char *message){fputs(message, stderr);fputc('\n',stderr);exit(1);
}

有几个注意点:

第4~6行:提取完成输入的套接字句柄和缓冲信息,因为WSAOVERLAPPED结构体变量
的hEvent成员中保存了PER_IO_DATA结构体变量地址值。

第9行:变量szRecvBytes的值为0就意味着收到了EOF,因此需要进行相应处理。

第17、18行:将WriteCompRoutine函数指定为Completion Routine,同时调用WSASend
函数。程序通过该语句向客户端发送回声消息。
第22、30行:发送回声消息后调用该函数。但需要再次接收数据,所以执行第30行的函数


上述示例的工作原理整理如下。
□有新的客户端连接时调用WSARecv函数,并以非阻塞模式接收数据,接收完成后调用
ReadCompRoutine函数。

□调用ReadCompRoutine函数后调用WSASend函数,并以非阻塞模式发送数据,发送完成后
调用WriteCompRoutine函数。
□此时调用的WriteCompRoutine雨数将再次调用WSARecv函数,并以非阻塞模式等待接收
数据。

通过交替调用ReadCompRoutine函数和WriteCompRoutine函数,反复执行数据的接收和发送操作。另外,每次增加1个客户端都会定义PERIO_DATA结构体,以便将新创建的套接字句柄和缓冲信息传递给ReadCompRoutine函数和WriteCompRoutine函数。同时将该结构体地址值写人WSAOVERLAPPED结构体成员hEvent,并传递给Completion Routine函数。这非常重要,可概括如下:
使用WSAOVERLAPPED结构体成员hEvent向完成I/O时自动调用的Completion Routine函数内部传递客户端信息(套接字和缓冲)。"
接下来需要验证运行结果,先要编写回声客户端,因为使用第4章的回声客户端会无法得到预想的结果。

重新实现客户端

其实第4章实现并使用至今的回声客户端存在一些问题,关于这些问题及解决方案已在第5章进行了充分讲解。虽然在目前为止的各种模型的服务器端中使用稍有缺陷的回声客户端也不会引起太大问题,但本章的回声服务器端则不同。因此,需要按照第5章的提示解决客户端存在的问题,并结合改进后的客户端运行本章服务器端。之前已介绍过解决方法,故只给出代码。

#include <“头声明与之前示例一致。“>
#define BUF SIZE 1024
void ErrorHandling(char *message);int main(int argc, char *argv[]){WSADATA wsaData;SOCKET hSocket;SOCKADDR_IN servAdr;char message[BUF_SIZE];int strlen, readLen;if(argc!=3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)ErrorHandling("WSAStartup() error!");hSocket=socket(PF_INET,SOCK_STREAM, 0);if(hSocket==INVALID_SOCKET)ErrorHandling("socket() error");memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family=AF_INET;servAdr.sin_addr.s _addr=inet_addr(argv[1]);servAdr.sin_port=htons(atoi(argv[2]));if(connect(hSocket,(SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)ErrorHandling("connect()error!");elseputs("Connected......");while(1){fputs("Input message(Q to quit):", stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))break;strLen=strlen(message);send(hSocket, message, strLen, 0);readLen=0;while(1){readLen+=recv(hSocket,&message[readlen], BUF_SIZE-1-readLen,0);if(readLen>=strLen)break;}message[strLen]=0;printf("Message from server: %s", message);}closesocket(hSocket);WSACleanup();return 0;
}void ErrorHandling(char *message){
//与其他Windows相关示例一致,故省略。
}

上述代码第44行的循环语句考虑到TCP的传输特性而重复调用了recv函数,直至接收完所有数据。将上述客户端结合之前的回声服务器端运行可以得到正确的运行结果,具体结果与一般的回声服务器端/客户端没有区别,故省略。

从重叠I/O模型到IOCP模型

下面分析重叠I/O模型回声服务器端的缺点。
“重复调用非阻塞模式的accept函数和以进入alertable wait状态为目的的SleepEx函数将影响性能!"
如果正确理解了之前的示例,应该不难发现这一点。既不能为了处理连接请求而只调用accept函数,也不能为了Completion Routine而只调用SleepEx函数,因此轮流调用了非阻塞模式的accept函数和SleepEx函数(设置较短的超时时间)。这个恰恰是影响性能的代码结构。
我也不知该如何弥补这一缺点,这属于重叠I/O结构固有的缺陷,但可以考虑如下方法:
“让main线程(在main函数内部)调用accept函数,再单独创建1个线程负责客户端I/O。”
其实这就是IOCP中采用的服务器端模型。换言之,IOCP将创建专用的I/O线程、该线程负责
与所有客户端进行I/O。

分阶段实现IOCP程序

本节我们编写最后一种服务器模型IOCP,比阅读代码更重要的是理解IOCP本身。

创建“完成端口”

IOCP中已完成的I/O信息将注册到完成端口对象(Completion Port,简称CP对象),但这个过
程并非单纯的注册,首先需要经过如下请求过程:“该套接字的I/O完成时,请把状态信息注册到指定CP对象。"
该过程称为“套接字和CP对象之间的连接请求”。因此,为了实现基于IOCP模型的服务器端,
需要做如下2项工作。
创建完成端口对象
建立完成端口对象和套接字之间的联系
此时的套接字必须被赋予重叠属性。上述2项工作可以通过1个函数完成,但为了创建CP对
象,先介绍如下函数。

#include <windows.h>
HANDLE CreateIoCompletionport(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR Completionkey,DWORD NumberofConcurrentThreads);
//成功时返回CP对象句柄,失败时返回NULL。FileHandle             //创建CP对象时传递INVALID_HANDLE_VALUE。ExistingCompletionPort //创建CP对象时传递NULL。CompletionKey          //创建CP对象时传递0。NumberOfConcurrentThreads//分配给CP对象的用于处理I/O的线程数。例如,该参数为2时,说明分配 //给CP对象的可以同时运行的线程数最多为2个;如果该参数为0,系统中 //CPU个数就是可同时运行的最大线程数。

以创建CP对象为目的调用上述函数时,只有最后一个参数才真正具有含义。可以用如下代码段将分配给CP对象的用于处理IO的线程数指定为2。

HANDLE hCpObject;
.....
hCpObject =CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,2);

连接完成端口对象和套接字

既然有了CP对象,接下来就要将该对象连接到套接字,只有这样才能使已完成的套接字I/O信息注册到CP对象。下面以建立连接为目的再次介绍CreateCompletionPort函数。

#include <windows.h>
HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionport, ULONG_PTR Completionkey,DWORD NumberofConcurrentThreads);
//成功时返回CP对象句柄,失败时返回NULL。FileHandle      //要连接到CP对象的套接字句柄。ExistingCompletionPort //要连接套接字的CP对象句柄。CompletionKey   //传递已完成I/O相关信息,关于该参数将在稍后介绍的GetQueued//CompletionStatus函数中共同讨论。NumberOfConcurrentThreads //无论传递何值,只要该函数的第二个参数非NULL就会忽略。

上述函数的第二种功能就是将FileHandle句柄指向的套接字和ExistingCompletionPort指向的
CP对象相连。该函数的调用方式如下。

HANDLE hCpObject;
SOCKET hSock;
......
CreateIoCompletionPort((HANDLE)hSock,hCpObject,(DWORD)ioInfo,0);

调用CreateIoCompletionPort函数后,只要针对hSock的I/O完成,相关信息就将注册到hCpObject指向的CP对象。

确认完成端口已完成的I/O和线程的I/O处理

我们已经掌握了CP对象的创建及其与套接字建立连接的方法,接下来就要学习如何确认CP
中注册的已完成的IO。完成该功能的函数如下。

#include <windows.h>
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytes,PULONG_PTR lpCompletionKey,LPOVERLAPPED * lpoverlapped, DWORD dwMilliseconds);
//成功时返回TRUE,失败时返回FALSE.CompletionPort   //注册有已完成I/O信息的CP对象句柄IpNumberOfBytes  //用于保存I/O过程中传输的数据大小的变量地址值。IpCompletionKey  //用于保存CreateIoCompletionPort函数的第三个参数值的变量地址值。IpOverlapped     //用于保存调用WSASend、WSARecv函数时传递的OVERLAPPED结构体地址的变量地址 //值。dwMilliseconds   //超时信息超过该指定时间后将返回FALSE并跳出函数。传递INFINITE时,程序//将阻塞,直到已完成I/O信息写入CP对象。

虽然只介绍了2个IOCP相关函数,但依然有些复杂,特别是上述函数的第三个和第四个参数更是如此。其实这2个参数主要是为了获取需要的信息而设置的,下面介绍这2种信息的含义。

“通过GetQueuedCompletionStatus函数的第三个参数得到的是以连接套接字和CP对象为目的而调用的CreateloCompletionPort函数的第三个参数值。”


“通过GetQueueCompletionStatus函数的第四个参数得到的是调用WSASend、WSARecv函数时传入的WSAOVERLAPPED结构体变量地址值。”

各位需要通过示例理解这2个参数的使用方法。接下来讨论其调用主体,究竟由谁(何时)调用上述函数比较合理呢?如各位所料,应该由处理IOCP中已完成I/O的线程调用。可能有人又有疑问:“那I/O如何分配给线程呢?"


如前所述,IOCP中将创建全职I/O线程,由该线程针对所有客户端进行I/O。而且CreateloCompletionPort函数中也有参数用于指定分配给CP对象的最大线程数,所以各位或许会有如下疑问:“是否自动创建线程并处理I/O?”


当然不是!应该由程序员自行创建调用WSASend、WSARecv等I/O函数的线程,只是该线程为了确认I/O的完成会调用GetQueuedCompletionStatus函数。虽然任何线程都能调用GetQueuedCompletionStatus函数,但实际得到I/O完成信息的线程数不会超过调用CreateIoCompletionPort函数时指定的最大线程数。
以上就是IOCP服务器端实现时需要的全部函数及其理论说明,下面通过源代码理解程序的整体结构。

实现基于IOCP的回声服务器端

虽然介绍了IOCP相关的理论知识,但离开示例很难真正掌握IOCP的使用方法。因此,我将介绍便于理解和运用的(极为普通的)基于IOCP的回声服务器端。首先给出IOCP回声服务器端的main函数之前的部分。

#include<stdio.h>
#include<stdlib.h>
#include<process.h>
#include<winsock2.h>
#include<windows.h>#define BUF SIZE 100
#define READ 3
#define WRITE 5typedef struct{SOCKET hClntsock;SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;typedef struct{OVERLAPPED overlapped;WSABUF wsaBuf;char buffer[BUF_SIZE];int rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIo);
void ErrorHandling(char *message);

第11行:保存与客户端相连套接字的结构体。

第17行:将I/O中使用的缓冲和重叠I/O中需要的OVERLAPPED结构体变量封装到同一结构
体中进行定义。

接下来介绍main函数部分。

int main(int argc, char* argv[]){WSADATA wsaData;HANDLE hComPort;SYSTEM_INFO sysInfo;LPPER_IO_DATA ioInfo; LPPER_HANDLE_DATA handleInfo;SOCKET hServSock;SOCKADDR_IN servAdr;int recvBytes, i, flags=0;if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)ErrorHandling("WSAStartup() error!");hComPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);//创建CP对象GetSystemInfo(&sysInfo);//获得当前系统的信息for(i=0; i<sysInfo.dwNumberofprocessors;i++)_beginthreadex(NULL,0,EchoThreadMain,(LPVOID)hComport,0,NULL);hServSock=WSASocket(AF_INET,SOCK_STREAM,0,NULL, 0, WSA_FLAG_OVERLAPPED);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family=AF_INET;servAdr.sin_addr.s_addr=htonl(INADDR_ANY);servAdr.sin_port=htons(atoi(argv[1]));bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr));listen(hServSock,5);while(1){SOCKET hClntSock;SOCKADDR_IN clntAdr;int addrLen=sizeof(clntAdr);hClntSock=accept(hServSock,(SOCKADDR*)&clntAdr,&addrLen);handleInfo=(LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));handleInfo->hClntSock=hClntSock;memcpy(&(handleInfo->clntAdr),&clntAdr, addrLen);CreateIoCompletionPort((HANDLE)hCIntSock,hComport,(DWORD)handleInfo,0)//建立连接ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));ioInfo->wsaBuf.len=BUF_SIZE;ioInfo->wsaBuf.buf=ioInfo->buffer;ioInfo->rwMode=READ;WSARecv(handleInfo->hClntSock,&(ioInfo->wsaBuf),1,&recvBytes,&flags,&(ioInfo- overlapped),NULL);}
return 0;
}

有几个注意点:

第36~38行:动态分配PER_HANOLE_DATA结构体,并写入客户端相连套接字和客户端地
址信息。

第40行:连接第15行创建的CP对象和第35行创建的套接字。针对这套接字的重叠I/O完成
时,已完成信息将写入连接的CP对象,这会引起GetQueue...函数的返回。请注意观察第三个参数的值。该值是第36~38行中声明开初始化的结构体变量地址值,它同样是在GetQueued..函数返回时得到的。


第42行:动态分配PER_IO_DATA结构体变量空间。相当于同时准备了WSARecv函数中需
要的OVERLAPPED结构体变量、WSABUF结构体变量及缓冲。


第46行:IOCP本身不会帮我们区分输入完成和输出完成的状态。无论输入还是输出,只
通知完成I/O的状态,因此需要通过额外的变量区分这2种I/O。PER_IO_DATA结构体中的rwMode就用于完成该功能。


第47行:WSARecv函数的第七个参数为OVERLAPPED结构体变量地址值,该值可以在
GetQueue...函数返回时得到。但结构体变量地址值与第一个成员的地址值相同,也就相当于传入了PER_IO_DATA结构体变量地址值。

最后给出线程main函数,则部分代码需要结合之前的main函数进行分析。

DWORD WINAPI EchoThreadMain(LPVOID pComPort){HANDLE hComPort=(HANDLE)pComPort;SOCKET sock;DWORD bytesTrans;LPPER_HANDLE_DATA handleInfo;LPPER_IO_DATA ioInfo;DWORD flags=0;while(1){GetQueuedCompletionStatus(hComport,&bytesTrans, (LPDWORD)&handletnfo(LPOVERLAPPED*)&ioInfo, INFINITE);sock=handleInfo->hclntsock,if(ioInfo->rwMode==READ){puts("message received!");if(bytesTrans==0){//传输EOF时closesocket(sock);free(handleInfo);free(ioInfo);continue;}memset(&(ioInfo->overlapped), 0,sizeof(OVERLAPPED));ioInfo->wsaBuf.len=bytesTrans;ioInfo->rwMode=WRITE;WSASend(sock,&(ioInfo->wsaBuf),1,NULL, 0, &(ioInfo->overlapped),NULL);ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));memset(&(ioInfo->overlapped),0, sizeof(OVERLAPPED));ioInfo->wsaBuf.len=BUF_SIZE;ioInfo->wsaBuf.buf=ioInfo->buffer;ioInfo->rwMode=READ;WSARecv(sock,&(ioInfo->wsaBuf),1,NULL, &flags,&(ioInfo->overlapped),NULL);}else{puts("message sent!");free(ioInfo);}}return 0;void ErrorHandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}

第12行:GetQueued..函数在I/O完成且己注册相关信息时返回(因为最后一个参数为
INFINITE)。另外,返回时可以通过第三个和第四个参数得到之前提过的2个信
息。
第16行:指针iolnfo中保存的既是OVERLAPPED结构体变量地址值,也是PER_IO_DATA
结构体变量地址值。因此,可以通过检查rwMode成员中的值判断是输入完成还是输出完成。
第26~30行:将服务器端收到的消息发送给客户端。
第32~38行:再次发送消息后接收客户端消息。
第40行:完成的I/O为输出时执行的else区域。

IOCP性能更优的原因

盲目地认为“因为是IOCP,所以性能更好”的想法并不可取。之前已介绍了Linux和Windows下多种服务器端模型,各位应该可以分析出它们在性能上的优势。将其在代码级别与select进行对比,可以发现如下特点。
1.因为是非阻塞模式的I/O,所以不会由I/O引发延迟

2.查找已完成I/O时无需添加循环
3.无需将作为I/O对象的套接字句柄保存到数组进行管理

4.可以调整处理I/O的线程数,所以可在实验数据的基础上选用合适的线程数。

仅凭这些特点也能判断IOCP属于高性能模型,IOCP是Windows特有的功能,所以很大程度上要归功于操作系统。无需怀疑它提供的性能,我认为IOCP和Linux的epoll都是非常优秀的服务器端模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/9818.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++类和对象(拷贝构造、运算符重载、初始化列表、静态成员、友元等)

一、拷贝构造 拷贝构造函数的特征&#xff1a; 1、拷贝构造函数是构造函数的一个重载形式&#xff1b; 2、拷贝构造函数的参数只有一个且必须是同类类型对象的引用&#xff0c;使用传值方式编译器直接报错&#xff0c;因为会引发无穷递归调用。 在c中自定义类型传值传参的时…

微信批量删除好友怎么删除

微信好友太多想要批量删除不知道怎么删除&#xff0c;相信这个问题也困扰了不少人。那么怎样才能批量的删除微信好友&#xff1f;其实不难&#xff0c;可以通过新建标签删除的方式来实现批量删除好友。 怎么批量删除 微信批量删除好友的具体步骤如下&#xff1a; 1、新建标签 首…

CC1310F128系列 超低功耗低于1GHz射频 微控制器芯片

CC1310F128 是一款经济高效型超低功耗低于1GHz射频器件&#xff0c;凭借极低的有源射频和MCU电流消耗以及灵活的低功耗模式&#xff0c;CC1310F128可确保卓越的电池寿命&#xff0c;并能够在小型纽扣电池供电的情况下以及在能量采集应用中实现远距离工作。 改芯片有三个后缀&am…

【ArcGIS Pro微课1000例】0029:绘制全球海洋波纹荡漾效果图

本文讲解ArcGIS Pro3.0中,基于全球航洋面状矢量数据,绘制震撼全球海洋波纹荡漾效果图。 文章目录 一、效果预览二、效果制作三、参数详解一、效果预览 绘制好的海水波纹荡漾效果图如下: 下面我们来学习绘制过程。 二、效果制作 波纹荡漾效果需要在全局或者局部场景中制作…

第2章 逻辑分页、AutoFac注入、工作单元与仓储

1 CoreCms.Net.Model.ViewModels.Basics.IPageList<T> namespace CoreCms.Net.Model.ViewModels.Basics { ///<typeparam name"T">泛型类型实例(1个指定实体的类型实例)。</typeparam> /// <summary> /// 【逻辑分页列表--接口】 /// <…

阿里云部署 ChatGLM2-6B 与 langchain+ChatGLM

1.ChatGLM2-6B 部署 更新系统 apt-get update 安装git apt-get install git-lfs git init git lfs install 克隆 ChatGLM2-6B 源码 git clone https://github.com/THUDM/ChatGLM2-6B.git 克隆 chatglm2-6b 模型 #进入目录 cd ChatGLM2-6B #创建目录 mkdir model #进入目录 cd m…

python机器学习(五)逻辑回归、决策边界、代价函数、梯度下降法实现线性和非线性逻辑回归

线性回归所解决的问题是把数据集的特征传入到模型中&#xff0c;预测一个值使得误差最小&#xff0c;预测值无限接近于真实值。比如把房子的其他特征传入到模型中&#xff0c;预测出房价&#xff0c; 房价是一系列连续的数值&#xff0c;线性回归解决的是有监督的学习。有很多场…

opencv-23 图像几何变换02-翻转-cv2.flip()

在 OpenCV 中&#xff0c;图像的翻转采用函数 cv2.flip()实现 &#xff0c;该函数能够实现图像在水平方向翻转、垂直方向翻转、两个方向同时翻转&#xff0c;其语法结构为&#xff1a; dst cv2.flip( src, flipCode )式中&#xff1a;  dst 代表和原始图像具有同样大小、类…

vite+vue3 css scss PC移动布局自适应

1. 安装 postcss-pxtorem 和 autoprefixer npm install postcss-pxtorem autoprefixer --save2. vite.config.js引入并配置 import postCssPxToRem from postcss-pxtorem import autoprefixer from autoprefixerexport default defineConfig({base: ./,resolve: {alias},plug…

前端面试题 —— React (三)

目录 一、对componentWillReceiveProps 的理解 二、React.forwardRef是什么&#xff1f;它有什么作用&#xff1f; 三、可以使用TypeScript写React应用吗&#xff1f;怎么操作&#xff1f; &#xff08;1&#xff09;如果还未创建 Create React App 项目 &#xff08;2&am…

Linux搭建Promtail + Loki + Grafana 轻量日志监控系统

一、简介 日志监控告警系统&#xff0c;较为主流的是ELK&#xff08;Elasticsearch 、 Logstash和Kibana核心套件构成&#xff09;&#xff0c;虽然优点是功能丰富&#xff0c;允许复杂的操作。但是&#xff0c;这些方案往往规模复杂&#xff0c;资源占用高&#xff0c;操作苦…

用Python合并多个文件为一个文本文件的方法代码

用Python合并多个文件为一个文本文件的方法代码 Python文件处理操作方便快捷&#xff0c;本文为大家提供的是如何用Python合并多个文本文件的代码示例。要把多个txt或是其它类型文件合并成一个&#xff0c;手动操作费时费力&#xff0c;不如自己动手写一个python代码来完成&…

UE4/5C++多线程插件制作(十六、Coroutines协程封装)

目录 准备 MTPCoroutines.h MTPCoroutines.cpp 我们要对协程继续封装制作: 协程是一种计算机程序组件,它允许在某个位置暂停执行,然后在稍后的时间点恢复执行。与传统的函数调用不同,协程可以被多次调用并且能够保留其内部状态,从而允许程序在执行到一定点时暂停,执行…

java中判断list是否为空

java中判断list是否为空是日常代码中经常遇到的问题。最近发现一个Utils提供的方法可以一步判断。 废话不多说&#xff0c;直接上代码&#xff01; ArrayList<String> arrayList new ArrayList<>(); System.out.println("集合1&#xff1a;" Collecti…

关于Android系统休眠跟串口读写的联系

问题描述&#xff1a;设备在进行rtk定位时&#xff0c;模块会通过串口同时进行读写操作。串口在读写时&#xff0c;如果息屏系统就会进入休眠&#xff0c;休眠的话CPU进入kill cpu状态。但是此时串口还在读写&#xff0c;这就导致出现一个意料外的问题&#xff0c;息屏只十几秒…

2-vi和vim的使用

vi和vim的区别 vi 是linux系统中内置的文本编辑器vim具有程序编辑能力 vi和vim常用的三种模式 正常模式 使用vim打开一个文件&#xff0c;就默认进入正常模式可以使用方向键【上下左右】来移动光标可以使用【删除字符/删除整行】来处理文件内容也可以使用【复制/粘贴】快捷键…

php 年月日 分组分页

//年月日 //分组 分页$type $this->request->type;$dateType "%Y-%m";//月$dateType1 "CONCAT(tmp.date,-01 00:00:00)";$dateType2 "CONCAT(LAST_DAY(CONCAT(tmp.date, -15)), 23:59:59)";if ($type day) {//日$dateType "%Y-…

Pytorch个人学习记录总结 03

目录 Transeforms的使用 常见的transforms Transeforms的使用 torchvision中的transeforms&#xff0c;主要是对图像进行变换&#xff08;预处理&#xff09;。from torchvision import transforms transeforms中常用的就是以下几种方法&#xff1a;&#xff08;Alt7可唤出…

小程序商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

pb:数据类型检查和转换函数

数据类型检查和转换函数 1、Asc() 功 能:得到字符串第一个字符的ASCII码整数值。 语 法:Asc ( string ) 参 数:string:要得到第一个字符ASCII值的字符串。 返回值:Integer。函数执行成功时返回string参数第一个字符的ASCII值,如果string参数的值为NULL,则Asc()函…