【网络编程】重叠I/O模子

[复制链接]
发表于 2025-10-18 13:25:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
十、基于I/O模子的网络开发

10.10  重叠I/O 模子

10.10.1  根本概念

在 Winsock   中,重叠 I/O(Overlapped  I/O) 模子能到达更佳的体系性能,高于 select  模 型、异步选择和变乱选择3种。重叠模子的根本计划原理便是让应用步伐利用一个重叠的数据 结 构(WSAOVERLAPPED),     一次投递一个或多个 Winsock   I/O 哀求。针对这些提交的哀求, 在它们完成之后,我们的应用步伐会收到关照,于是我们就可以对数据举行处置惩罚了。
重叠I/O 这个概念来自文件I/O 利用。在Win32 文件I/O 利用中,当调用ReadFile 和 WriteFile时,假如末了一个参数lpOverlapped设置为NULL,  那么线程就壅闭在这里,直到读 写完指定的数据后才返回。如许在读写大文件的时间,许多时间都浪费在等待 ReadFile 和WriteFile的返回上面。假如ReadFile 和 WriteFile 是往管道里读写数据,那么有大概壅闭得更 久,导致步伐性能降落。
为了办理这个题目,Windows 引进了重叠I/O的概念,它可以或许同时以多个线程处置惩罚多个I/O。 着实你自己开多个线程也可以处置惩罚多个I/O, 但是体系内部对I/O 的处置惩罚在性能上有很大的优 化。重叠I/O 是 Windows 下实现异步I/O 常用的方式。
Windows 为险些全部范例的文件提供这个工具:磁盘文件、通讯端口、定名管道和套接 字。通常,利用ReadFile 和 WriteFile 就可以很好地实行重叠I/O。
重叠模子的焦点是一个重叠数据布局。若想以重叠方式利用文件,必须用 FILE_FLAG_OVERLAPPED  标记打开它,比方:
  1. HANDLE hFile = CreateFile(lpFileName, GENERIC READ I GENERIC WRITE,
  2.                           FILE_SHARE_READ I FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
复制代码
假如没有指定该标记,那么针对这个文件(句柄)而言,重叠I/O 是不可用的。假如设置 了该标记,当调用ReadFile 和 WriteFile 利用这个文件(句柄)时,必须为末了一个参数提供 OVERLAPPED 布局:
  1. // WINBASE.H
  2. typedef struct _OVERLAPPED
  3. {
  4.     ULONG_PTR Internal;
  5.     ULONG_PTR InternalHigh;
  6.     union
  7.     {
  8.         struct
  9.         {
  10.             DWORD Offset;
  11.             DWORD OffsetHigh;
  12.         } DUMMYSTRUCTNAME;
  13.         PVOID Pointer;
  14.     } DUMMYUNIONNAME;
  15.     HANDLE hEvent;
  16. } OVERLAPPED, *LPOVERLAPPED;
复制代码

  • Internal:I/O    哀求的状态代码。发出哀求时,体系将此成员设置为状态“挂起”,以 指示利用尚未启动。哀求完成后,体系将此成员设置为已完成哀求的状态代码。该字 段由体系内部利用。
  • InternalHigh:被传输数据的长度。
  • DUMMYUNIONNAME.DUMMYSTRUCTNAME.Offset:  启动输入输出哀求的文件位 置的低阶部门,由用户指定。只有在支持偏移(也称为文件指针机制)概念的查找设 备(如文件)上实行I/O 哀求时此成员才为非零;否则,此成员必须为零。
  • DUMMYUNIONNAME.DUMMYSTRUCTNAME.OffsetHigh:  启动输入输出哀求的文 件位置的高阶部门,由用户指定。只有在支持偏移(也称为文件指针机制)概念的查 找装备(如文件)上实行I/O 哀求时此成员才为非零;否则,此成员必须为零。
  • DUMMYUNIONNAME.Pointer:   保存供体系利用。初始化为零后不要利用。
  • hEvent: 当利用完成时,由体系设置为信号状态的变乱句柄。在将此布局通报给任何重叠函数之前,用户必须利用CreateEvent函数将此成员初始化为零或有效的变乱句  柄。然后可以利用此变乱同步装备的同时I/O 哀求。函数(如ReadFile和 WriteFile)    在开始I/O利用之前将此句柄设置为非署名状态。利用完成后,句柄将设置为信号状  态。诸如GetOverlappedResult   和同步等待函数等函数将主动重置变乱重置为非信号 状态。因此,应该利用手动重置变乱。假如利用主动重置变乱,等待利用完成,然后 使 用bWait 参数设置为TRUE 调 用GetOverlappedResult,    则应用步伐可以制止相应。
  • 在函数调用中利用该布局之前,应始终将该布局的任何未利用成员初始化为零;否则,函 数大概会失败并返回ERROR  INVALID  PARAMETER 。Offset 和 OffsetHigh 成员一起表现64 位文件位置。它是从文件或雷同文件的装备开始的字节偏移量,由用户指定,体系不会修改这 些值。调用进程必须在将重叠布局通报给利用偏移量的函数(如ReadFile 、WriteFile 或其他相 关函数)之前设置此成员。
  • 因 为I/O 异步发生,就不能确定利用是否按次序完成。因此,这里没有当前位置的概念。 对于文件的利用,总是规定该偏移量。在数据流下(如COM 端口或socket),    没有探求正确 偏移量的方法,以是在这些环境中体系忽略偏移量。这4个字段不应由应用步伐直接举行处置惩罚 或利用,OVERLAPPED  布局的末了一个参数是可选的变乱句柄。稍后会提到怎样利用这个参 数来设定变乱关照完成 I/O 。如今,假定该句柄是NULL 。设 置 了OVERLAPPED   参数后, ReadFile/WriteFile 的调用会立刻返回,这时你可以去做其他的事(所谓异步),体系会主动替 你完成ReadFile/WriteFile  相干的I/O 利用。你也可以同时发出几个ReadFile/WriteFile  的调用         (所谓重叠)。当体系完成I/O利用时,会将OVERLAPPED.hEvent   置信(置有信号状态), 我们可以通过调用WaitForSingleObject/WaitForMultipleObjects来等待这个I/O 完成关照,在得 到关照信号后,就可以调用GetOverlappedResult来查询I/O 利用的结果,并举行相干处置惩罚。由 此可以看出,OVERLAPPED  布局在一个重叠I/O 哀求的初始化及厥后续的完成之间提供了一 种沟通或通讯机制。
  • 以Win32 重 叠I/O 机制为根本,自Winsock2发布开始,重叠I/O 便已集成到新的Winsock  函数中,比如WSARecv/WSASend。如许一来,重叠I/O 模子便能实用于安装了Winsock  2的 所 有Windows 平台。可以一次投递一个或多个Winsock  I/O哀求。针对那些提交的哀求,在它 们完成之后,应用步伐可为它们提供服务(对I/O 的数据举行处置惩罚)。
  • 比起壅闭、select 、WSAAsyncSelect   以 及WSAEventSelect   等 模 型 ,Winsock  的 重 叠I/O          (Overlapped    I/O) 模子使应用步伐能到达更佳的体系性能。由于它和这4种模子差异的是, 利用重叠模子的应用步伐关照缓冲区收发体系直接利用数据。也就是说,假如应用步伐投递了 一个10KB巨细的缓冲区来吸收数据,且数据已经到达套接字,那么该数据将直接被复制到投递的缓冲区。而其他4种模子中,数据到达先复制到套接字自己的吸收缓冲区中,此时应用步伐会被体系关照有数据可读。当应用步伐调用吸收函数之后,数据才从套接字自己的缓冲区复 制到应用步伐的缓冲区,如许就多了一次从套接字缓冲区到应用步伐缓冲区的复制,性能差异 就在于此。
10.10.2 创建重叠I/O模子下的套接字

要想在一个套接字上利用重叠 I/O 模子来处置惩罚网络数据通讯,起首必须利用 WSA_FLAG_OVERLAPPED标记来创建一个套接字:
  1. sOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
复制代码
创建套接字的时间,假如利用的是socket 函数,而非WSASocket 函数,那么会默认设置 WSA_FLAG_OVERLAPPED 标记。乐成创建好了一个套接字,将其与一个本地接口绑定到一 起后,便可开始举行这个套接字上的重叠I/O 利用。
为了要利用重叠布局,我们常用的 send、 recv 等收发数据的函数也都要被WSASend 、WSARecv 更换掉了。方法是调用以下Winsock  2函数,同时为它们指定一个WSAOVERLAPPED     布局参数(可选): WSASend、WSASendTo、
WSARecv、WSARecvFrom、WSAloctl、AcceptEx、TransmitFile。   若 随 WSAOVERLAPPED
布局一起调用这些函数,则函数会立刻返回,无论套接字是否设为锁定模式。它们依赖于 WSAOVERLAPPED 布局来返回一个I/O 哀求利用的结果。
在Windows  NT和Windows  2000中,重叠I/O 模子也答应应用步伐以一种重叠方式实现对套接字毗连的处置惩罚。详细的做法是在监听套接字上调用AcceptEx 函数。AcceptEx  是一个特 殊的Winsock 1.1扩展函数,该函数最初的计划宗旨是在Windows   NT与 Windows    2000利用 体系上利用Win32 的重叠I/O 机制。究竟上,它也实用于Winsock   2中的重叠I/O 。AcceptEx  的声明如下:
  1. BOOL AcceptEx(
  2.     SOCKET SListenSocket,
  3.     SOCKET  SAcceptSocket,
  4.     PVOID SAcceptSocket,
  5.     DWORD dwReceiveDataLength,
  6.     DWORD dwLocalAddressLength,
  7.     DWORD dwRemoteAddressLength,
  8.     LPOVERLAPPED lpOverlapped
  9. )        
复制代码

  • sListenSocket: 标识已用listen  函数调用过的套接字的形貌符。服务器步伐在此套接 字上等待毗连。
  • sAcceptSocket: 一种形貌符,用于标识继承传入毗连的套接字。
  • lpOutputBuffer:指向缓冲区的指针。该缓冲区是一个特殊的缓冲区,由于它要负责3 种数据的吸收:服务器的本地地点,客户机的远程地点,以及在新建毗连上发送的第 一个数据块。
  • dwReceiveDataLength:以字节为单位,指定了在lpOutputBuffer  缓冲区中,保存多大 的空间来吸收数据。假如将这个参数设为0,那么在继承毗连的过程中不会再一起接 收任何数据。
  • dwLocalAddressLength: 为本地地点信息保存的字节数。此值必须至少比正在利用的 传输协议的最大地点长度多16字节。举个例子,假定正在利用的是TCP/IP  协议,那么这里的巨细应设为“SOCKADDR  IN  布局的长度+16字节”。
  • dwRemoteAddressLength:   为远程地点信息保存的字节数。此值必须至少比正在利用 的传输协议的最大地点长度多16字节,不能为零。
  • lpdwBytesReceived:用于返回吸收到的实际数据量,以字节为单位。只有在利用以同 步方式完成的条件下才会设置这个参数。假如 AcceptEx 函数返回 ERROR_IO_PENDING,  那么这个参数永世都不会设置,我们必须利用完成变乱关照 机制来获知实际读取的字节量。
  • lpOverlapped:  它对应的是一个OVERLAPPED 布局,答应AcceptEx 以一种异步方式 工作。如我们早先所述,只有在一个重叠I/O应用中该函数才必要利用变乱对象关照 机制(hEvent 字段),这是由于此时没有一个完成例程参数可供利用。
假如函数乐成,就返回 TRUE 。 假如函数失败,就返回 FALSE,   此时可以调用 WSAGetLastError 函数返回错误码。若WSAGetLastError 返 回ERROR_IO_PENDING,    则利用 已乐成启动,而且仍在举行中。若错误码为WSAECONNRESET,     则表现有一个传入毗连,但 随后在继承呼唤之前被远程对等端制止。
10.10.3  获取重叠I/O利用完成结果

异步I/O 哀求挂起后,终极要知道I/O利用是否完成。 一个重叠I/O 哀求终极完成后,应 用步伐要负责取回重叠I/O 利用的结果。对于读,直到I/O 完成,吸收缓冲区才有效。对于写, 要知道写是否乐成。有几种方法可以做到这一点,最直接的方法是调用 WSAGetOverlappedResult,  其函数原型如下:
  1. BOOL WSAAPI WSAGetOverlappedResult(
  2.   SOCKET          s,
  3.   LPWSAOVERLAPPED lpOverlapped,
  4.   LPDWORD         lpcbTransfer,
  5.   BOOL            fWait,
  6.   LPDWORD         lpdwFlags
  7. );
复制代码

  • s:   套接字句柄。
  • lpOverlapped: 关联的WSAOVERLAPPED 布局,在调用CreateFile 、WSASocket  或 AcceptEx 时指定。
  • lpcbTransfer: 指向字节计数指针,负责吸收一次重叠发送或吸收利用实际传输的字 节 数 。
  • fWait:   确定下令是否等待的标记,用于决定函数是否应该等待一次重叠利用完成。 若将该参数设为TRUE, 则直到利用完成函数才返回;若设为FALSE,   而且利用仍 然处于未完成状态,那么WSAGetOverlappedResult 函数会返回FALSE 值。
  • lpdwFlags: 指向32位变量的指针,该变量将吸收一个或多个增补完成状态的标记。 假如重叠利用是通过WSARecv  或 WSARecvFrom 启动的,那么此参数将包罗lpFlags
  • 参数的结果值。此参数不能是空指针。
假如函数乐成,那么返回值为TRUE 。这意味偏重叠利用已乐成完成,而且lpcbTransfer 指向的值已更新。
假如函数回FALSE,   就意味偏重叠利用尚未完成,大概重叠利用已完成但有错误,大概 由 于WSAGetOverlappedResult    的一个或多个参数中的错误而无法确定重叠利用的完成状态。 失败时,lpcbTransfer 指向的值将不会更新。利用WSAGetLastError  可以确定失败的缘故原由。
下面先容两种常用重叠I/O 完成关照的方法。
10.10.4     基于变乱关照(有64个socket  的限定)

套接字重叠I/O 的变乱关照方法要求变乱对象与WSAOVERLAPPED  布局关联在一起。 当 I/O   利用完成后,该变乱对象从未触发状态变为触发状态。在应用步伐中先调用 WSAWaitForMultipleEvents   函数等待该变乱的发生。得到该变乱对象对应的 WSAOVERLAPPED  布局后可以根据 Internal  和 InternalHigh  字段(也可以调用 WSAGetOverlappedResult 函 数 ) 判 断I/O 完成的环境。
详细步调如下:

  • (1)创建具有WSAOVERLAPPED     标记的套接字。假如调用socket()函数,那么默认创 建具有 WSAOVERLAPPED   标记的套接字。假如调用WSASocket   函数,就必要明白指定 WSAOVERLAPPED 标记。
  • (2)为套接字界说WSAOVERLAPPED  布局,并清零。
  • (3)调用WSACreateEvent 函数创建变乱对象,并将该变乱句柄分配给WSAOVERLAPPED 布局的hEvent 字段。
  • (4)调用吸收大概发送函数。
  • (5)调用WSAWaitForMultipleEvents  函数等待与重叠I/O 关联的变乱变为已触发状态。
  • (6)WSAWaitForMultipleEvents  返回后,调用WSAResetEvent 函数,将该变乱对象规复 为未触发态。
  • (7) 调 用WSAGetOverlappedResult  函数判定重叠I/O 的完成状态。
下面的实例演示了利用socket  重 叠I/O 模子开发服务步伐的步调。该步伐涉及两个线程: 吸收线程用于继承客户端毗连哀求,初始化重叠I/O 利用;服务线程用于重叠I/O 处置惩罚。
【例10.5】利用变乱关照实现重叠I/O模子
  1. #define _WINSOCK_DEPRECATED_NO_WARNINGS
  2. #include <winsock2.h>
  3. #include <Windows.h>
  4. #include <iostream>
  5. #pragma comment(lib,"Ws2_32.lib")
  6. using std::cout;
  7. using std::cin;
  8. using std::endl;
  9. using std::ends;
  10. void WSAEventServerSocket()
  11. {
  12.         SOCKET server = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  13.         if (server == INVALID_SOCKET) {
  14.                 cout << "创建SOCKET失败!,错误代码:" << WSAGetLastError() << endl;
  15.                 return;
  16.         }
  17.         int error = 0;
  18.         sockaddr_in addr_in;
  19.         addr_in.sin_family = AF_INET;
  20.         addr_in.sin_port = htons(6000);
  21.         addr_in.sin_addr.s_addr = INADDR_ANY;
  22.         error = ::bind(server, (sockaddr*)&addr_in, sizeof(sockaddr_in));
  23.         if (error == SOCKET_ERROR) {
  24.                 cout << "绑定端口失败!,错误代码:" << WSAGetLastError() << endl;
  25.                 return;
  26.         }
  27.         listen(server, 5);
  28.         if (error == SOCKET_ERROR) {
  29.                 cout << "监听失败!,错误代码:" << WSAGetLastError() << endl;
  30.                 return;
  31.         }
  32.         cout << "成功监听端口 :" << ntohs(addr_in.sin_port) << endl;
  33.         WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];        // 事件对象数组
  34.         SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];            // 事件对象数组对应的SOCKET句柄
  35.         int nEvent = 0;                    // 事件对象数组的数量
  36.         WSAEVENT event0 = ::WSACreateEvent();
  37.         ::WSAEventSelect(server, event0, FD_ACCEPT | FD_CLOSE);
  38.         eventArray[nEvent] = event0;
  39.         sockArray[nEvent] = server;
  40.         nEvent++;
  41.         while (true) {
  42.                 int nIndex = ::WSAWaitForMultipleEvents(nEvent, eventArray, false, WSA_INFINITE, false);
  43.                 if (nIndex == WSA_WAIT_IO_COMPLETION || nIndex == WSA_WAIT_TIMEOUT) {
  44.                         cout << "等待时发生错误!错误代码:" << WSAGetLastError() << endl;
  45.                         break;
  46.                 }
  47.                 nIndex = nIndex - WSA_WAIT_EVENT_0;
  48.                 WSANETWORKEVENTS event;
  49.                 SOCKET sock = sockArray[nIndex];
  50.                 ::WSAEnumNetworkEvents(sock, eventArray[nIndex], &event);
  51.                 if (event.lNetworkEvents & FD_ACCEPT) {
  52.                         if (event.iErrorCode[FD_ACCEPT_BIT] == 0) {
  53.                                 if (nEvent >= WSA_MAXIMUM_WAIT_EVENTS) {
  54.                                         cout << "事件对象太多,拒绝连接" << endl;
  55.                                         continue;
  56.                                 }
  57.                                 sockaddr_in addr;
  58.                                 int len = sizeof(sockaddr_in);
  59.                                 SOCKET client = ::accept(sock, (sockaddr*)&addr, &len);
  60.                                 if (client != INVALID_SOCKET) {
  61.                                         cout << "接受了一个客户端连接 " << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port) << endl;
  62.                                         WSAEVENT eventNew = ::WSACreateEvent();
  63.                                         ::WSAEventSelect(client, eventNew, FD_READ | FD_CLOSE | FD_WRITE);
  64.                                         eventArray[nEvent] = eventNew;
  65.                                         sockArray[nEvent] = client;
  66.                                         nEvent++;
  67.                                 }
  68.                         }
  69.                 }
  70.                 else if (event.lNetworkEvents & FD_READ) {
  71.                         if (event.iErrorCode[FD_READ_BIT] == 0) {
  72.                                 char buf[2500];
  73.                                 ZeroMemory(buf, 2500);
  74.                                 int nRecv = ::recv(sock, buf, 2500, 0);
  75.                                 if (nRecv > 0) {
  76.                                         cout << "收到一个消息 :" << buf << endl;
  77.                                         char strSend[] = "hi,client,I am server, I recvived your message.";
  78.                                         ::send(sock, strSend, strlen(strSend), 0);
  79.                                 }
  80.                         }
  81.                 }
  82.                 else if (event.lNetworkEvents & FD_CLOSE) {
  83.                         ::WSACloseEvent(eventArray[nIndex]);
  84.                         ::closesocket(sockArray[nIndex]);
  85.                         cout << "一个客户端连接已经断开了连接" << endl;
  86.                         for (int j = nIndex; j < nEvent - 1; j++) {
  87.                                 eventArray[j] = eventArray[j + 1];
  88.                                 sockArray[j] = sockArray[j + 1];
  89.                         }
  90.                         nEvent--;
  91.                 }
  92.                 else if (event.lNetworkEvents & FD_WRITE) {
  93.                         cout << "一个客户端连接允许写入数据" << endl;
  94.                 }
  95.         } // end while
  96.         ::closesocket(server);
  97. }
  98. int main(){
  99.         WSADATA wsaData;
  100.         int error;
  101.         WORD wVersionRequested;
  102.         wVersionRequested = WINSOCK_VERSION;
  103.         error = WSAStartup(wVersionRequested, &wsaData);
  104.         if (error != 0) {
  105.                 WSACleanup();
  106.                 return 0;
  107.         }
  108.         WSAEventServerSocket();
  109.         WSACleanup();
  110.         return 0;
  111. }
复制代码
这个模子与其他模子差异的是它利用Winsock2 提供的异步I/O 函 数WSARecv。 在调用 WSARecv 时,指定一个WSAOVERLAPPED 布局,这个调用不是壅闭的,也就是说,它会立 刻返回。一旦有数据到达,被指定的WSAOVERLAPPED     布局中的hEvent被 Signaled。在 取 得吸收的数据后,把数据原封不动地打印出来,大概同道们也可以调用一下 send 函数,把数 据发送回客户端(客户端那里有吸收线程),然后重新激活一个WSARecv 异步利用。有一个 函数值得留意,那就是用于吸收数据的函数WSARecv。该函数声明如下:
  1. int WSAAPI WSARecv(
  2.   SOCKET                             s,
  3.   LPWSABUF                           lpBuffers,
  4.   DWORD                              dwBufferCount,
  5.   LPDWORD                            lpNumberOfBytesRecvd,
  6.   LPDWORD                            lpFlags,
  7.   LPWSAOVERLAPPED                    lpOverlapped,
  8.   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  9. );
复制代码

  • s:   标识一个已毗连的套接字的形貌符。
  • lpBuffers: 指 向WSABUF  布局数组的指针。每个WSABUF 布局都包罗一个指向缓 冲区的指针和缓冲区的长度(以字节为单位)。
  • dwBufferCount:lpBuffers    数组中WSABUF 布局的个数。
  • lpNumberOfBytesRecvd: 假如吸收利用立刻完成,就指向吸收数据的字节数指针。
假如 lpOverlapped  参数不是空值,就对此参数利用空值,以制止潜伏的错误结果。只有 lpOverlapped参数不为空时,此参数才气为空。

  • lpFlags:   指向用于修改WSARecv 函数调用活动的标记的指针。
  • lpOverlapped: 指 向 W SAOVERLAPPED 布局的指针(对于非重叠的套接字忽略)。
  • lpCompletionRoutine: 当吸收利用完成时调用的完成例程的指针(对于非重叠的套接 字忽略)。
假如没有发生错误,而且吸收利用立刻完成,则函数返回零。在这种环境下, 一旦调用线 程处于可警报状态,就已经操持调用完成例程。否则,将返回SOCKET_ERROR,   此时可以通 过调用WSAGetLastError   来检索特定的错误代码。错误代码WSA _IO_PENDING 表现重叠操 作已乐成启动,稍后将指示完成。任何其他错误代码都表现重叠利用未乐成启动,不会出现完 成指示 。
10.10.5 基于完成例程

10.10.5.1 根本概念

假如你想要利用重叠I/O 机制带来的高性能模子,又懊恼于基于变乱关照的重叠模子要收到64个等待变乱的限定,尚有点畏惧完成端口稍显复杂的初始化过程,那么“完成例程”无 疑是最好的选择!由于完成例程摆脱了变乱关照的限定,可以连入恣意数目客户端而不消另开 线程,也就是说只用很简单的一些代码就可以利用Windows 内部的I/O 机制来得到网络服务 器的高性能。
在基于变乱关照的重叠I/O 模子中,在你投递了一个哀求(比如WSARecv)   以后,体系 在完成以后是用变瞎搅关照你的,而在完成例程中,体系在网络利用完成以后会主动调用你提 供的回调函数,区别仅此而已。接纳完成例程的服务器端,通讯流程如图10-12所示。

从图10-12中可以看到,服务器端存在一个显着的异步过程,也就是说我们把客户端连入的 SOCKET  与一个重叠布局绑定之后,便可以将通讯过程全权交给体系内部去帮我们调治处置惩罚,我 们在主线程中可以边做其他的变乱边等待体系完成的关照。这就是完成例程高性能的缘故原由所在。
假如还没有看明白,我们打个普通易懂的比方:完成例程的处置惩罚过程,也就像我们告诉系 统,“我想要在网络上吸收网络数据,你去帮我办一下”(投递WSARecv 利用),“不外我 并不知道网络数据何时到达,总之在吸收到网络数据之后,你就直接调用我给你的这个函数(比 如  CompletionProess),把它们生存到内存大概体现到界面中等,全权交给你处置惩罚了”。于是 乎,体系在吸收到网络数据之后, 一方面会给我们一个关照,另一方面体系也会主动调用我们 事先准备好的回调函数,就不必要我们自己费心了。
10.10.5.2 完成例程的优点

基于完成例程的重叠I/O 模子相对于变乱选择I/O 模子的精良之处在于,重叠I/O 模子完 全办理了recv  的壅闭题目。在从前的模子当中,recv 只是吸收数据到来的关照,之后仍然要己去内核复制数据到用户空间中,如今recv 过程全部交由利用体系完成,减去了数据等待 与将数据从内核复制到步伐缓冲区的时间,而且其间不占用步伐自身的时间片。
基于完成例程的重叠I/O 异步模子如图10-13所示。

变乱选择网络模子的数据复制如图10-14所示。

别的,完成例程相比基于变乱相应的重叠I/O 模子的精良之处在于,完成例程并没有64 个变乱的上限,而是利用体系调用完成例程(也就是一个由利用体系调用的回调函数)对吸收 到的数据举行处置惩罚。固然都是基于重叠I/O,但是由于前两种模子都必要自己来管理任务的分 派,以是性能上没有区别。
10.10.5.3 完成例程的关键函数

完成例程方式和前面的变乱关照方式最大的差异之处在于,我们必要提供一个回调函数供 体系收到网络数据后主动调用,回调函数的参数界说应该依照如下函数原型:
  1. void CALLBACK_CompletionRoutineFunc(DWORD dwError,
  2.                                     DWORD cbTransferred,
  3.                                     LPWSAOVERLAPPED lpOverlapped,
  4.                                     DWORD dwFlags);
复制代码

  • 此中,参数dwError 标记咱们投递的重叠利用,比如WSARecv, 完成的状态是什么;
  • 参 数 cbTransferred 指明白在重叠利用期间,实际传输的字节量是多大;
  • 参数lpOverlapped参数 指明通报到最初的I/O 调用内的一个重叠布局;
  • 参数dwFlags 返回利用竣事时大概用的标记(一 般没用)。
留意:函数名字任意起,但是参数范例不能错。尚有一点必要重点提一下,由于我们必要 给体系提供一个如上面界说的那样的回调函数,以便体系在完成了网络利用后主动调用,这里 就必要提一下究竟是怎样把这个函数与体系内部绑定的。比如,在WSARecv 函数中是如许绑 定 的 :
  1. int WSARecv(
  2.     SOCKET s,
  3.     LPWSABUF lpBuffers, DWORD dwBufferCount,
  4.     LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags,
  5.     LPWSAOVERLAPPED lpOverlapped,
  6.     LPWSAOVERLAPPED COMPLETION ROUTINE lpCompletionRoutine);
复制代码
这个函数前面着实先容过,由于比力告急,我们再次简单地报告一下这个函数。

  • 参数 s  投递这个利用的套接字;
  • 参数IpBuffers表现吸收缓冲区,与recv函数差异,这里必要一个由 WSABUF布局构成的数组;
  • 参数dwBufferCount 表现数组中WSABUF 布局的数目,设置为1 即可;
  • 参数lpNumberOfBytesRecvd 表现当吸收利用完成时,会返回函数调用所吸收到的字节 数 ;
  • lpOverlapped 表现“绑定”的重叠布局;
  • 末了一个参数lpCompletionRoutine 就是完成例程 函数的指针。
我们的回调函数 CompletionRoutineFunc和 WSARecv 利用关联起来了,体系一 完成吸收数据,就回调我们的函数,之后我们就可以在内里处置惩罚数据了。
假如以为有些抽象,我们可以看一段代码:
  1. SOCKET s;
  2. WSABUF DataBuf;           //   定义WSABUF结构的缓冲区
  3. #define DATA BUFSIZE 4096 //   初始化一下DataBuf
  4. char buffer[DATA BUFSIZE];
  5. ZeroMemory(buffer, DATA BUFSIZE);
  6. DataBuf.len = DATA BUFSIZE;
  7. DataBuf.buf = buffer;
  8. DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
  9. // 建立需要的重叠结构,每个连入的sOCKET上的每一个重叠操作都得绑定一个
  10. WSAOVERLAPPED Acceptoverlapped; // 如果要处理多个操作,这里当然需要一个WSAOVERLAPPED  数组
  11. ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
  12. //   做了这么多工作,终于可以使用WSARecv  来把我们的完成例程函数绑定上了
  13. // 当然,假设我们的_CompletionRoutine 函数已经定义好了
  14. WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes, &Flags, &AcceptOverlapped, _CompletionRoutine);
复制代码
其他参数我们可以先不消细看,重点关注末了一个。
10.10.5.4 完成例程的实现步调

理论知识方面必要知道的就是这么多。下面我们共同代码, 一步步地教学怎样亲手实现一 个基于完成例程的重叠I/O 模子。
详细步调如下:

  • 第一步,创建一个套接字,开始在指定的端口上监听毗连哀求。
  • 第一步很简单,和其他的SOCKET 初始化并无多大区别。必要留意的是,为了突出重点, 笔者去掉了错误处置惩罚,详细开发时可不能如许,只管这里堕落的概率比力小:
  1. WSADATA wsaData;
  2. WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建TCP 套接字
  3. ListenSocket = socket(AF INET, SOCK STREAM, IPPROTO TCP);
  4. SOCKADDR_IN ServerAddr;                              // 分配端口及协议簇并绑定 ServerAddr.sin   family=AF   INET;
  5. ServerAddr.sin addr.S un.S addr = htonl(INADDR ANY); // 在8888端口监听,端口号可以随意更改,但最好不要少于1024
  6. ServerAddr.sin port = htons(8888);
  7. bind(ListenSocket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)); //       绑定套接字
  8. listen(ListenSocket, 5);                                         //     开始监听
复制代码
第二步,继承一个入站的毗连哀求。调用accept 函数即可:
  1. AcceptSocket = accept(ListenSocket, NULL, NULL);
复制代码
假如想要得到连入客户端的信息,则accept 的后两个参数不要用NULL,   而是如许:
  1. SOCKADDR IN ClientAddr; // 定义一个客户端的地址结构作为参数
  2. int addr length = sizeof(ClientAddr);
  3. AcceptSocket = accept(ListenSocket, (SOCKADDR *)&ClientAddr, &addr length);
  4. // 于是乎,我们就可以轻松得知连入客户端的信息了
  5. LPCTSTR lpIP = inet ntoa(ClientAddr.sin addr); // 连入客户端的 IP
  6. UINT nPort = ClientAddr.sin port;              //    连入客户端的Port
复制代码

  • 第三步,准备好我们的重叠布局。有新的套接字连入以后,新建一个WSAOVERLAPPED  重叠布局(固然也可以提前创建 好),准备绑定到我们的重叠利用上去。这里也可以看到和基于变乱的显着区别,就是不消再 为WSAOVERLAPPED 布局绑定一个hEvent 了 。这里只界说一个,实际上是每一个SOCKET 的每一个利用都必要绑定一个重叠布局,所 以在实际利用面对多个客户端的时间要界说为数组,详见示例代码:
  1. WSAOVERLAPPED AcceptOverlapped;
  2. ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 置 零
复制代码

  • 第四步,开始在套接字上投递WSARecv 哀求。这一步必要将第三步准备的WSAOVERLAPPED 布局和我们界说的完成例程函数为参数。 各个变量都已经初始化完成以后,我们就可以开始举行详细的Socket  通讯函数调用了,然后 让体系内部的重叠布局来替我们管理I/O 哀求,我们只用等待网络通讯完成后调用回调函数就 可 以 了 。这个步调的重点是绑定一个Overlapped 变量和一个完成例程函数:
  1. // 将WSAOVERLAPPED   结构指定为一个参数,在套接字上投递一个异步WSARecv() 请求 //   并提供下面的作为完成例程的_CompletionRoutine   回调函数(函数名字)
  2. if (WSARecv(
  3.         AcceptSocket, &DataBuf, 1,
  4.         &dwRecvBytes, &Flags,
  5.         &AcceptOverlapped,
  6.         _CompletionRoutine) == SOCKET_ERR OR) //    注意我们传入的回调函数指针
  7. {
  8.     if (WSAGetLastError() != WSA_IO_PENDING)
  9.     {
  10.         ReleaseSocket(nSockIndex);
  11.         continue;
  12.     }
  13. }
复制代码

  • 第五步,调用WSAWaitForMultipleEvents  函数大概SleepEx  函数等待重叠利用返回的结 果。我们在前面提到过,投递完WSARecv 利用,并绑定了Overlapped 布局和完成例程函数之 后,根本就完事大吉了。比及体系自己去完成网络通讯,并在吸收到数据的时间,会主动调用 我们的完成例程函数。
我们在主线程中必要做的变乱只有做别的变乱,而且等待体系完成了完成例程调用后的返 回结果。就是说在WSARecv  调用发起完毕之后,我们不得不在背面紧跟上一些等待完成结果 的代码。有两种办法可以实现:

  • (1)和上一节重叠I/O 中讲到的一样,我们可以利用WSAWaitForMultipleEvent 来等待 重叠利用的变乱关照,演示如下:
  1. /*因为WSAWaitForMultipleEvents()API    要求在一个或多个事件对象上等待,但是这个事件
  2. 数组已经不是和SOCKET 相关联的了,因此不得不创建一个伪事件对象*/
  3. WSAEVENT EventArray[1];
  4. EventArray[0] = WSACreateEvent(); //    建立一个事件
  5. //    然后等待重叠请求完成就可以了。注意保存返回值,这个很重要
  6. DWORD dwIndex = WSAWaitForMultipleEvents(1, EventArray, FALSE, WSA_INFINITE, TRUE);
复制代码
WSAWaitForMultipleEvents参数的寄义上一节中已经先容过了。调用这个函数以后,线程 就会置于一个警觉的等待状态。留意,fAlertable 参数肯定要设置为TRUE。

  • (2)可以直接利用SleepEx 函数来完成等待,结果是一样的。SleepEx 函数调用起来就简 单得多了,它的函数原型界说是如许的:
  1. DWORD SleepEx(
  2.     DWORD dwMilliseconds, BOOL bAlertable);
复制代码
此中,参数dwMilliseconds 为等待的超时时间,假如设置为INFINITE 就会不停等待下去; 参数bAlertable 表现是否置于警觉状态,假如为FALSE,   则肯定要等待超时时间完毕之后才 会返回。假如指定的时间隔断已逾期,则函数返回值为零。假如函数由于一个或多个I/O 完成 回调函数而返回,则返回值为WAIT_IO_COMPLETION,    只有当bAlertable为 TRUE,  而且调 用SleepEx函数的线程与调用扩展I/O函数的线程雷同时才会发生这种环境。
这里我们盼望重叠利用一完成绩能返回,以是肯定要设置为TRUE。调用这个函数的时间, 同样留意用一个DWORD   范例变量来生存它的返回值,背面会派上用场。

  • 第六步,通逾等待函数的返回值取得重叠利用的完成结果。这是我们最关心的变乱,费了那么大劲投递的这个重叠利用究竟是什么结果呢?就是通过 上一步中我们调用的等待函数的DWORD  范例的返回值。正常环境下,在利用完成之后,应 该是返回 WAIT_IO_COMPLETION,      假如返回的是WAIT_TIMEOUT,     则表现等待设置的超时时间到了,但是重叠利用仍然没有完成,应该通过循环再继续等待。假如是其他返回值,就 坏事了,阐明网络通讯出现了其他非常,步伐就可以报错退出了。
判定返回值的代码大抵如下:
  1. // 返 回WAIT IO COMPLETION 表示一个重叠请求完成例程代码的结束,继续为更多的完成例程服务
  2. if (dwIndex == WAIT IO COMPLETION)
  3. {
  4.     TRACE(" 重叠操作完成...\n");
  5. }
  6. else if (dwIndex == WAIT TIMEOUT)
  7. {
  8.     TRACE("  超时了,继续调用等待函数\n");
  9. }
  10. else
  11. {
  12.     TRACE(" 出错了.…\n");
  13. }
复制代码
利用完成了之后,就阐明我们上一个利用已经乐成了。乐成了之后做什么?固然是继续投 递下一个重叠利用了。继续上面的循环。

  • 第七步,继续回到第四步,在套接字上继续投递WSARecv  哀求,重复第4~7步。
  • 第八步,处置惩罚吸收到的数据。
忙活了这么久,客户端传来的数据到底在那里吸收啊?怎么一点都没有提到呢?这个题目 提得好,我们写了这么多代码图什么呢?着实想要读取客户端的数据很简单,由于我们在 WSARecv 调用的时间通报了一个WSABUF  变量,用于生存网络数据,而在我们写的完成例 程回调函数内里就可以取到客户端传送来的网络数据了。体系在调用我们完成例程函数的时间 网络利用已经完成了,WSABUF   内里已经有我们必要的数据了,只是通过完成例程来举行后 期的处置惩罚。详细可以参考示例代码。此中,DataBuf.buf就是一个char*字符串指针。
下面我们来看两个例子,分别利用SleepEx 和 WSAWaitForMultipleEvents 函数。
【例10.6】基于完成例程的重叠I/O例 子(SleepEx 版 )
  1. # define _WINSOCK_DEPRECATED_NO_WARNINGS
  2. #include <WINSOCK2.H>
  3. #include <stdio.h>
  4. #define PORT    6000
  5. #define MSGSIZE 1024
  6. #pragma comment(lib, "ws2_32.lib")
  7. typedef struct
  8. {
  9.         WSAOVERLAPPED overlap;
  10.         WSABUF        Buffer;
  11.         char          szMessage[MSGSIZE];
  12.         DWORD         NumberOfBytesRecvd;
  13.         DWORD         Flags;
  14.         SOCKET        sClient;
  15. }PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
  16. DWORD WINAPI WorkerThread(LPVOID);
  17. void CALLBACK CompletionROUTINE(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
  18. SOCKET g_sNewClientConnection;
  19. BOOL   g_bNewConnectionArrived = FALSE;
  20. int main()
  21. {
  22.         WSADATA     wsaData;
  23.         SOCKET      sListen;
  24.         SOCKADDR_IN local, client;
  25.         DWORD       dwThreadId;
  26.         int         iaddrSize = sizeof(SOCKADDR_IN);
  27.         // Initialize Windows Socket library
  28.         WSAStartup(0x0202, &wsaData);
  29.         // Create listening socket
  30.         sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  31.         // Bind
  32.         local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  33.         local.sin_family = AF_INET;
  34.         local.sin_port = htons(PORT);
  35.         bind(sListen, (struct sockaddr*)&local, sizeof(SOCKADDR_IN));
  36.         // Listen
  37.         listen(sListen, 3);
  38.         puts("服务器已经启动。。。\n");
  39.         // Create worker thread
  40.         CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
  41.         while (TRUE)
  42.         {
  43.                 // Accept a connection
  44.                 g_sNewClientConnection = accept(sListen, (struct sockaddr*)&client, &iaddrSize);
  45.                 g_bNewConnectionArrived = TRUE;
  46.                 printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
  47.         }
  48. }
  49. DWORD WINAPI WorkerThread(LPVOID lpParam)
  50. {
  51.         LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
  52.         while (TRUE)
  53.         {
  54.                 if (g_bNewConnectionArrived)
  55.                 {
  56.                         // Launch an asynchronous operation for new arrived connection
  57.                         lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
  58.                                 GetProcessHeap(),
  59.                                 HEAP_ZERO_MEMORY,
  60.                                 sizeof(PER_IO_OPERATION_DATA));
  61.                         lpPerIOData->Buffer.len = MSGSIZE;
  62.                         lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
  63.                         lpPerIOData->sClient = g_sNewClientConnection;
  64.                         WSARecv(lpPerIOData->sClient,
  65.                                 &lpPerIOData->Buffer,
  66.                                 1,
  67.                                 &lpPerIOData->NumberOfBytesRecvd,
  68.                                 &lpPerIOData->Flags,
  69.                                 &lpPerIOData->overlap,
  70.                                 CompletionROUTINE);
  71.                         g_bNewConnectionArrived = FALSE;
  72.                 }
  73.                 SleepEx(1000, TRUE);
  74.         }
  75.         return 0;
  76. }
  77. void CALLBACK CompletionROUTINE(DWORD dwError,
  78.         DWORD cbTransferred,
  79.         LPWSAOVERLAPPED lpOverlapped,
  80.         DWORD dwFlags)
  81. {
  82.         LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;
  83.         if (dwError != 0 || cbTransferred == 0)
  84.         {
  85.                 // Connection was closed by client
  86.                 closesocket(lpPerIOData->sClient);
  87.                 HeapFree(GetProcessHeap(), 0, lpPerIOData);
  88.         }
  89.         else
  90.         {
  91.                 lpPerIOData->szMessage[cbTransferred] = '\0';
  92.                 puts(lpPerIOData->szMessage);//打印下收到的客户端信息
  93.                 send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0);//再重新发回给客户端
  94.                 // Launch another asynchronous operation
  95.                 memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
  96.                 lpPerIOData->Buffer.len = MSGSIZE;
  97.                 lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
  98.                 WSARecv(lpPerIOData->sClient,
  99.                         &lpPerIOData->Buffer,
  100.                         1,
  101.                         &lpPerIOData->NumberOfBytesRecvd,
  102.                         &lpPerIOData->Flags,
  103.                         &lpPerIOData->overlap,
  104.                         CompletionROUTINE);
  105.         }
  106. }
复制代码
用完成例程来实现重叠I/O 比用变乱关照简单得多。在这个模子中,主线程只用不绝地接 受毗连即可;辅助线程判定有没有新的客户端毗连被创建,假如有,就为谁人客户端套接字激 活一个异步的WSARecv  利用,然后调用SleepEx使线程处于一种可告诫的等待状态,以使得 I/O完成后CompletionROUTINE 可以被内核调用。假如辅助线程不调用SleepEx,  则内核在完 成一次I/O 利用后,无法调用完成例程(由于完成例程的运行应该和当初激活WSARecv  异步 利用的代码在同一个线程之内)。
完成例程内的实当代码比力简单,它取出吸收到的数据,然后将数据原封不动地发送给客 户端,末了重新激活另一个WSARecv 异步利用。留意,在这里用到了“尾随数据”。我们在 调用 WSARecv  的时间,参数 lpOverlapped  实际上指向一个比它大得多的布局 PER_IO_OPERATION_DATA,     这个布局除了WSAOVERLAPPED   以外,还被我们附加了缓 冲区的布局信息,别的还包罗客户端套接字等告急的信息。如许,在完成例程中通过参数 lpOverlapped 拿到的不但仅是WSAOVERLAPPED 布局,尚有后边尾随的包罗客户端套接字和 吸收数据缓冲区等告急信息。如许的C 语言本事在背面先容完成端口的时间还会利用到。
【例10.7】基于完成例程的重叠I/O例 子(WSAWaitForMultipleEvents 版 )
  1. #include <WinSock2.h>
  2. #include <process.h>
  3. #include <stdio.h>
  4. #pragma comment(lib,"ws2_32.lib")
  5. #define PORT 6000
  6. #define MAXBUF 128
  7. //自定义一个存放socket信息的结构体,用于完成例程中对OVERLAPPED的转换
  8. typedef struct _SOCKET_INFORMATION {
  9.         OVERLAPPED Overlapped;        //这个字段一定要放在第一个,否则转换的时候,数据的赋值会出错              
  10.         SOCKET Socket;                //后面的字段顺序可打乱并且不限制字段数,也就是说你还可以多定义几个字段
  11.         CHAR Buffer[MAXBUF];
  12.         WSABUF wsaBuf;
  13. } SOCKET_INFORMATION, *LPSOCKET_INFORMATION;
  14. SOCKET g_sClient;                 //不断新加进来的client
  15.                                                                   //打开服务器
  16. BOOL OpenServer(SOCKET* sServer)
  17. {
  18.         BOOL bRet = FALSE;
  19.         WSADATA wsaData = { 0 };
  20.         SOCKADDR_IN addrServer = { 0 };
  21.         addrServer.sin_family = AF_INET;
  22.         addrServer.sin_port = htons(PORT);
  23.         addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  24.         do
  25.         {
  26.                 if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
  27.                 {
  28.                         if (LOBYTE(wsaData.wVersion) == 2 || HIBYTE(wsaData.wVersion) == 2)
  29.                         {
  30.                                 //在套接字上使用重叠I/O模型,必须使用WSA_FLAG_OVERLAPPED标志创建套接字
  31.                                 //g_sServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
  32.                                 *sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  33.                                 if (*sServer != INVALID_SOCKET)
  34.                                 {
  35.                                         if (SOCKET_ERROR != bind(*sServer, (SOCKADDR*)&addrServer, sizeof(addrServer)))
  36.                                         {
  37.                                                 if (SOCKET_ERROR != listen(*sServer, SOMAXCONN))
  38.                                                 {
  39.                                                         bRet = TRUE;
  40.                                                         break;
  41.                                                 }
  42.                                                 closesocket(*sServer);
  43.                                         }
  44.                                         closesocket(*sServer);
  45.                                 }
  46.                         }
  47.                 }
  48.         } while (FALSE);
  49.         return bRet;
  50. }
  51. //完成例程
  52. void CALLBACK CompeletRoutine(DWORD dwError, DWORD dwBytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD dwFlags)
  53. {
  54.         DWORD  dwRecvBytes;
  55.         DWORD dwFlag;
  56.         //强制转换为我们自定义的结构,这里就解释了为什么第一个字段要是OVERLAPPED
  57.         //因为转换后首地址肯定会相同,读取的数据一定会是Overlapped的数据
  58.         //所以要先把Overlapped的数据保存下来,接下来内存中的数据再由系统分配到各个字段中
  59.         LPSOCKET_INFORMATION pSi = (LPSOCKET_INFORMATION)Overlapped;
  60.         if (dwError != 0)  //错误显示
  61.                 printf("I/O operation failed with error %d\n", dwError);
  62.         if (dwBytesTransferred == 0)
  63.                 printf("Closing socket %d\n\n", pSi->Socket);
  64.         if (dwError != 0 || dwBytesTransferred == 0) //错误处理
  65.         {
  66.                 closesocket(pSi->Socket);
  67.                 GlobalFree(pSi);
  68.                 return;
  69.         }
  70.         //如果已经发送完成了,接着投递下一个WSARecv
  71.         printf("Recv%d:%s\n", pSi->Socket, pSi->wsaBuf.buf);
  72.         dwFlag = 0;
  73.         ZeroMemory(&(pSi->Overlapped), sizeof(WSAOVERLAPPED));
  74.         pSi->wsaBuf.len = MAXBUF;
  75.         pSi->wsaBuf.buf = pSi->Buffer;
  76.         if (WSARecv(pSi->Socket, &(pSi->wsaBuf), 1, &dwRecvBytes, &dwFlag, &(pSi->Overlapped), CompeletRoutine) == SOCKET_ERROR)
  77.         {
  78.                 if (WSAGetLastError() != WSA_IO_PENDING)
  79.                 {
  80.                         printf("WSARecv() failed with error %d\n", WSAGetLastError());
  81.                         return;
  82.                 }
  83.         }
  84. }
  85. //把client和完成例程绑定起来
  86. unsigned int __stdcall ThreadBind(void* lparam)
  87. {
  88.         DWORD dwFlags;
  89.         LPSOCKET_INFORMATION pSi;
  90.         DWORD dwIndex;
  91.         DWORD dwRecvBytes;
  92.         WSAEVENT eventArry[1];
  93.         eventArry[0] = (WSAEVENT)lparam;
  94.         while (1)
  95.         {
  96.                 //等待一个完成例程返回
  97.                 while (TRUE)
  98.                 {
  99.                         dwIndex = WSAWaitForMultipleEvents(1, eventArry, FALSE, WSA_INFINITE, TRUE);
  100.                         if (dwIndex == WSA_WAIT_FAILED)
  101.                         {
  102.                                 printf("WSAWaitForMultipleEvents() failed with error %d\n", WSAGetLastError());
  103.                                 return FALSE;
  104.                         }
  105.                         if (dwIndex != WAIT_IO_COMPLETION)
  106.                                 break;
  107.                 }
  108.                 //重设事件
  109.                 WSAResetEvent(eventArry[0]);
  110.                 //为SOCKET_INFORMATION分配一个全局内存空间,相当于全局变量了
  111.                 //这里为什么要分配全局的呢?因为我们要在完成例程中引用socket的数据
  112.                 if ((pSi = (LPSOCKET_INFORMATION)GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL)
  113.                 {
  114.                         printf("GlobalAlloc() failed with error %d\n", GetLastError());
  115.                         return 1;
  116.                 }
  117.                 //填充各个字段
  118.                 pSi->Socket = g_sClient;
  119.                 ZeroMemory(&(pSi->Overlapped), sizeof(WSAOVERLAPPED));
  120.                 pSi->wsaBuf.len = MAXBUF;
  121.                 pSi->wsaBuf.buf = pSi->Buffer;
  122.                 dwFlags = 0;
  123.                 //投递一个WSARecv
  124.                 if (WSARecv(pSi->Socket, &(pSi->wsaBuf), 1, &dwRecvBytes, &dwFlags,
  125.                         &(pSi->Overlapped), CompeletRoutine) == SOCKET_ERROR)
  126.                 {
  127.                         if (WSAGetLastError() != WSA_IO_PENDING)
  128.                         {
  129.                                 printf("WSARecv() failed with error %d\n", WSAGetLastError());
  130.                                 return 1;
  131.                         }
  132.                 }
  133.                 printf("Socket %d got connected...\n", g_sClient);
  134.         }
  135.         return 0;
  136. }
  137. //接受client请求线程
  138. unsigned int __stdcall ThreadAccept(void* lparam)
  139. {
  140.         SOCKET sServer = *(SOCKET*)lparam;
  141.         WSAEVENT event = WSACreateEvent();
  142.         _beginthreadex(NULL, 0, ThreadBind, event, 0, NULL);
  143.         while (TRUE)
  144.         {
  145.                 g_sClient = accept(sServer, NULL, NULL);
  146.                 if (g_sClient != INVALID_SOCKET)
  147.                         WSASetEvent(event);
  148.         }
  149.         return 0;
  150. }
  151. int main(int argc, char **argv)
  152. {
  153.         SOCKET sServer = INVALID_SOCKET;
  154.         puts("服务器已经启动...\n");
  155.         if (OpenServer(&sServer))
  156.                 _beginthreadex(NULL, 0, ThreadAccept, &sServer, 0, NULL);
  157.         Sleep(10000000);
  158.         return 0;
  159. }
复制代码
参考册本《Visual C++2017网络编程实战》
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表