• 重叠I/O模型


    一. 重叠I/O的概念
    当调用ReadFile和WriteFile时,如果最后一个参数lpOverlapped设置为NULL,那么线程就阻塞在这里,直到读写完指定的数据后,它们才返回。这样在读写大文件的时候,很多时间都浪费在等待ReadFile和WriteFile的返回上面。如果ReadFile和WriteFile是往管道里读写数据,那么有可能阻塞得更久,导致程序性能下降。
    为了解决这个问题,windows引进了重叠I/O的概念,它能够同时以多个线程处理多个I/O。其实你自己开多个线程也可以处理多个I/O,但是系统内部对I/O的处理在性能上有很大的优化。它是Windows下实现异步I/O最常用的方式。
    Windows为几乎全部类型的文件提供这个工具:磁盘文件、通信端口、命名管道和套接字。通常,使用ReadFile和WriteFile就可以很好地执行重叠I/O。
    重叠模型的核心是一个重叠数据结构。若想以重叠方式使用文件,必须用 FILE_FLAG_OVERLAPPED 标志打开它,例如:
    HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    如果没有规定该标志,则针对这个文件(句柄),重叠I/O是不可用的。如果设置了该标志,当调用ReadFile和WriteFile操作这个文件(句柄)时,必须为最后一个参数提供OVERLAPPED结构:
    // WINBASE.H
    typedef struct _OVERLAPPED{
    DWORD Internal;
    DWORD InternalHigh;
    DWORD Offset;
    DWORD OffsetHigh;
    HANDLE hEvent; //关键的一个参数
    }OVERLAPPED, *LPOVERLAPPED;
    头两个32位的结构字Internal和InternalHigh由系统内部使用;其次两个32位结构字Offset和OffsetHigh使得可以设置 64位的偏移量,该偏移量是要文件中读或写的地方。
    因为I/O异步发生,就不能确定操作是否按顺序完成。因此,这里没有当前位置的概念。对于文件的操作,总是规定该偏移量。在数据流下(如COM端口或socket),没有寻找精确偏移量的方法,所以在这些情况中,系统忽略偏移量。这四个字段不应由应用程序直接进行处理或使用,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机制为基础,自Winsock 2发布开始,重叠I/O便已集成到新的Winsock函数中,比如WSARecv/WSASend。这样一来,重叠I/O模型便能适用于安装了Winsock 2的所有Windows平台。可以一次投递一个或多个Winsock I/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务(对I/O的数据进行处理)。
    相应的,要想在一个套接字上使用重叠I/O模型来处理网络数据通信,首先必须使用 WSA_FLAG_OVERLAPPED这个标志来创建一个套接字。如下所示:
    SOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    创建套接字的时候,假如使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。成功创建好了一个套接字,将其与一个本地接口绑定到一起后,便可开始进行这个套接字上的重叠I/O操作,方法是调用下述的Winsock 2函数,同时为它们指定一个WSAOVERLAPPED结构参数(#define WSAOVERLAPPED OVERLAPPED// WINSOCK2.H):
    n WSASend
    n WSASendTo
    n WSARecv
    n WSARecvFrom
    n WSAIoctl
    n AcceptEx
    n TransmitFile
    若随一个WSAOVERLAPPED结构一起调用这些函数,函数会立即返回,无论套接字是否设为锁定模式。它们依赖于WSAOVERLAPPED结构来返回一个I/O请求操作的结果。
    比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,Winsock的重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据。也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。而这4种模型中,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被系统通知可以读入的字节数。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区。这样就减少了一次从I/O缓冲区到应用程序缓冲区的拷贝,差别就在于此。
    在Windows NT和Windows 2000中,重叠I/O模型也允许应用程序以一种重叠方式实现对套接字连接的处理。具体的做法是在监听套接字上调用AcceptEx函数。AcceptEx是一个特殊的Winsock 1.1扩展函数,位于Mswsock.h头文件以及Mswsock.lib库文件内。该函数最初的设计宗旨是在Windows NT与Windows 2000操作系统上使用Win 32的重叠I/O机制。但事实上,它也适用于Winsock 2中的重叠I/O。AcceptEx的定义如下:
    // MSWSOCK.H
    AcceptEx(
    IN SOCKET sListenSocket,
    IN SOCKET sAcceptSocket,
    IN PVOID lpOutputBuffer,
    IN DWORD dwReceiveDataLength,
    IN DWORD dwLocalAddressLength,
    IN DWORD dwRemoteAddressLength,
    OUT LPDWORD lpdwBytesReceived,
    IN LPOVERLAPPED lpOverlapped);
    参数一sListenSocket参数指定的是一个监听套接字。
    参数二sAcceptSocket参数指定的是另一个套接字,负责对进入连接请求的“接受”。AcceptEx函数和accept函数的区别在于,我们必须提供接受的套接字,而不是让函数自动为我们创建。正是由于要提供套接字,所以要求我们事先调用socket或WSASocket函数,创建一个套接字,以便通过sAcceptSocket参数,将其传递给AcceptEx。
    参数三lpOutputBuffer参数指定的是一个特殊的缓冲区,因为它要负责三种数据的接收:服务器的本地地址,客户机的远程地址,以及在新建连接上发送的第一个数据块。
    参数四dwReceiveDataLength参数以字节为单位,指定了在lpOutputBuffer缓冲区中,保留多大的空间,用于数据的接收。如这个参数设为0,那么在接受连接的过程中,不会再一道接收任何数据。
    参数五dwLocalAddressLength和参数六dwRemoteAddressLength也是以字节为单位,指定在lpOutputBuffer缓冲区中,保留多大的空间,在一个套接字被接受的时候,用于本地和远程地址信息的保存。要注意的是,和当前采用的传送协议允许的最大地址长度比较起来,这里指定的缓冲区大小至少应多出16字节。举个例子来说,假定正在使用的是TCP/IP协议,那么这里的大小应设为“SOCKADDR_IN结构的长度+16字节”。
    参数七lpdwBytesReceived参数用于返回接收到的实际数据量,以字节为单位。只有在操作以同步方式完成的前提下,才会设置这个参数。假如AcceptEx函数返回ERROR_IO_PENDING,那么这个参数永远都不会设置,我们必须利用完成事件通知机制,获知实际读取的字节量。
    最后一个参数是lpOverlapped,它对应的是一个OVERLAPPED结构,允许AcceptEx以一种异步方式工作。如我们早先所述,只有在一个重叠I/O应用中,该函数才需要使用事件对象通知机制(hEvent字段),这是由于此时没有一个完成例程参数可供使用。
    二.获取重叠I/O操作完成结果
    当异步I/O请求挂起后,最终要知道I/O操作是否完成。一个重叠I/O请求最终完成后,应用程序要负责取回重叠I/O操作的结果。对于读,直到I/O完成,输入缓冲器才有效(参考IRP缓冲区管理)。对于写,要知道写是否成功。有几种方法可以做到这点,最直接的方法是调用(WSA)GetOverlappedResult,其函数原型如下。
    WINBASEAPI BOOL WINAPI
    GetOverlappedResult(
    HANDLE hFile,
    LPOVERLAPPED lpOverlapped,
    LPDWORD lpNumberOfBytesTransferred,
    BOOL bWait);
    WINSOCK_API_LINKAGE BOOL WSAAPI
    WSAGetOverlappedResult(
    SOCKET s,
    LPWSAOVERLAPPED lpOverlapped,
    LPDWORD lpcbTransfer,
    BOOL fWait,
    LPDWORD lpdwFlags);
    l 参数一为的文件/套接字句柄。
    l 参数二为参数一关联的(WSA)OVERLAPPED结构,在调用CreateFile、WSASocket或AcceptEx时指定。
    l 参数三指向字节计数指针,负责接收一次重叠发送或接收操作实际传输的字节数。
    l 参数四是确定命令是否等待的标志。Wait参数用于决定函数是否应该等待一次重叠操作完成。若将Wait设为TRUE,那么直到操作完成函数才返回;若设为FALSE,而且操作仍然处于未完成状态,那么(WSA)GetOverlappedResult函数会返回FALSE值。

    如(WSA)GetOverlappedResult函数调用成功,返回值就是TRUE。这意味着我们的重叠I/O操作已成功完成,而且由参数三BytesTransfered参数指向的值已进行了更新。若返回值是FALSE,那么可能是由下述任何一种原因造成的:
    ■ 重叠I/O操作仍处在“待决”状态。
    ■ 重叠操作已经完成,但含有错误。
    ■ 重叠操作的完成状态不可判决,因为在提供给 WSAGetOverlappedResult函数的一个或多个参数中,存在着错误。
    失败后,由BytesTransfered参数指向的值不会进行更新,而且我们的应用程序应调用(WSA)GetLastError函数,检查到底是何种原因造成了调用失败以使用相应容错处理。如果错误码为
    ERROR/WSA_IO_INCOMPLETE(Overlapped I/O event is not in a signaled state)或
    ERROR/WSA_IO_PENDING(Overlapped I/O operation is in progress),则表明I/O仍在进行。当然,这不是真正错误,任何其他错误码则真正表明一个实际错误。
    下面介绍两种常用重叠I/O完成通知的方法。

    1.使用事件通知
    基于事件通知,就要求将Windows事件对象与WSAOVERLAPPED结构关联在一起(WSAOVERLAPPED结构中专门有对应的参数),通俗一点讲,就是。。。。对了,忘了说了,既然要使用重叠结构,我们常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替换掉了, 它们的用法我后面会讲到,这里只需要注意一点,它们的参数中都有一个Overlapped参数,我们可以假设是把我们的WSARecv这样的操作操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交给重叠结构去操心,而其中重叠结构又要与Windows的事件对象“绑定”在一起,这样我们调用完WSARecv以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,然后我们就可以来根据重叠操作的结果取得我们想要德数据了。
    使用(WSA)GetOverlappedResult是直截了当的,它吻合重叠I/O的概念。毕竟,如果要等待I/O,也许使用常规I/O命令更好。对于大多数程序,反复检查I/O是否完成,并非最佳。解决方案之一是使用(WSA)OVERLAPPED结构中的hEvent字段,使应用程序将一个事件对象句柄同一个文件/套接字关联起来。
    当指定OVERLAPPED参数给ReadFile/WriteFile或WSARecv/WSASend后,可以再为(WSA)OVERLAPPED最后一个参数提供自定义的事件对象(通过(WSA)CreateEvent创建)。
    当I/O完成时,系统更改(WSA)OVERLAPPED结构对应的事件对象的传信状态,使其从“未传信”(unsignaled)变成“已传信”(signaled)。由于我们之前将事件对象分配给了(WSA)OVERLAPPED结构,所以只需简单地调用WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函数,从而判断出一个(一些)重叠I/O在什么时候完成。通过WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函数返回的索引可以知道这个重叠I/O完成事件是在哪个HANDLE(File或Socket)上发生的。
    然后调用(WSA)GetOverlappedResult函数,将发生事件的HANDLE(File或Socket)传给参数一,将这个HANDLE对应的(WSA)OVERLAPPED结构传给参数二,这样判断重叠调用到底是成功还是失败。如果返回FALSE值,则重叠操作已经完成但含有错误。或者重叠操作的完成状态不可判决,因为在提供给 WSAGetOverlappedResult函数的一个或多个参数中存在着错误。失败后,由BytesTransfered参数指向的值不会进行更新,应用程序应调用(WSA)GetLastError函数,调查到底是何种原因造成了调用失败。
    若(WSA)GetOverlappedResult函数返回TRUE,则根据先前调用异步I/O函数时设置的缓冲区(ReadFile/WriteFile.lpBuffer,WSARecv/WSASend.lpBuffers)和BytesTransfered,使用指针偏移定位就可以准确操作接受到的数据了。
    利用事件对象来完成同步通知的方法比重复调用(WSA)GetOverlappedResult浪费处理器时间的方案要高效得多。

    2.使用完成例程
    对于文件重叠I/O操作,等待I/O操作结束的另外方法是使用ReadFileEx和WriteFileEx。这些命令只用于重叠I/O,当为它们的最后一个参数lpCompletionRoutine传递了一个完成例程(自定义函数)指针时,I/O操作结束时将调用此函数进行处理。
    完成例程指针LPOVERLAPPED_COMPLETION_ROUTINE定义如下:
    // WINBASE.H
    typedef VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransfered,
    LPOVERLAPPED lpOverlapped );
    相应在Winsock 2中,WSARecv/WSASend最后一个参数lpCompletionROUTINE是一个可选的指针,它指向一个完成例程。若指定此参数(自定义函数地址),在重叠请求完成后,将调用完成例程处理。
    Winsock 2中完成例程指针LPWSAOVERLAPPED_COMPLETION_ROUTINE定义略有不同:
    // WINSOCK2.H
    typedef void (CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
    DWORD dwError,
    DWORD cbTransferred,
    LPWSAOVERLAPPED lpOverlapped,
    DWORD dwFlags );
    前三个参数同LPOVERLAPPED_COMPLETION_ROUTINE,参数四一般不用,置0。用完成例程完成一个重叠I/O请求之后,参数中会包含下述信息:
    参数一dwError表明了一个重叠操作(由lpOverlapped指定)的完成状态是什么。
    参数二BytesTransferred参数指定了在重叠操作实际传输的字节量是多大。
    参数三lpOverlapped参数指定的是调用这个完成例程的异步I/O操作函数(ReadFileEx/WriteFileEx或WSARecv/WSASend)的(WSA)OVERLAPPED结构参数。
    用一个完成例程提交的重叠I/O请求时,(WSA)OVERLAPPED结构的事件字段hEvent并未使用。也就是说,我们不可将一个事件对象同重叠I/O请求关联到一起。使用一个含有完成例程指针参数的异步I/O函数发出一个重叠I/O请求之后,一旦重叠I/O操作完成,作为我们的调用线程,必须能够通知完成例程指针所指向的自定义函数开始执行,提供数据处理服务。这样一来,便要求将调用线程置于一种“可警告的等待状态”,在I/O操作完成后,自动调用完成例程加以处理。WSAWaitForMultipleEvents函数可用来将线程置于一种可警告的等待状态。这样做的代价是必须创建一个事件对象可用于WSAWaitForMultipleEvents函数。假定应用程序只用完成例程对重叠请求进行处理,便不可能有任何事件对象需要处理。作为一种变通方法,我们的应用程序可用Win32的SleepEx函数将自己的线程置为一种可警告等待状态。当然,亦可创建一个伪事件对象,不将它与任何东西关联在一起。假如调用线程经常处于繁忙状态,而且并不处在一种可警告的等待状态,那么完成例程根本不会得到调用。
    如前面所述,WSAWaitForMultipleEvents通常会等待同WSAOVERLAPPED结构关联在一起的事件对象。该函数也可用于将我们的线程设计成一种可警告等待状态,并可为已经完成的重叠I/O请求调用完成例程进行处理(前提是将fAlertable参数设为TRUE)。使用一个含有完成例程指针的异步I/O函数提交了重叠I/O请求之后,WSAWaitForMultipleEvents的返回值是WAIT_IO_COMPLETION(One or more I/O completion routines are queued for execution),而不是事件数组中的一个事件对象索引。从宏WAIT_IO_COMPLETION的注解可知,它的意思是有完成例程需要执行。SleepEx函数的行为实际上和WSAWaitForMultipleEvents差不多,只是它不需要任何事件对象。对SleepEx函数的定义如下:
    WINBASEAPI DWORD WINAPI
    SleepEx(
    DWORD dwMilliseconds,
    BOOL bAlertable );
    其中,dwMilliseconds参数定义了SleepEx函数的等待时间,以毫秒为单位。假如将dwMilliseconds设为INFINITE,那么SleepEx会无休止地等待下去。bAlertable参数规定了一个完成例程的执行方式,若将它设置为FALSE,则使用一个含有完成例程指针的异步I/O函数提交了重叠I/O请求后,I/O完成例程不会执行,而且SleepEx函数不会返回,除非超过由dwMilliseconds规定的时间;若将它设置为TRUE,则完成例程会得到执行,同时SleepEx函数返回WAIT_IO_COMPLETION。
    利用完成例程处理重叠I/O的Winsock程序的编写步骤如下:
    1) 新建一个监听套接字,在指定端口上监听客户端的连接请求。
    2) 接受一个客户端的连接请求,并返回一个会话套接字负责与客户端通信。
    3) 为会话套接字关联一个WSAOVERLAPPED结构。
    4) 在套接字上投递一个异步WSARecv请求,方法是将WSAOVERLAPPED指定成为参数,同时提供一个完成例程。
    5) 在将fAlertable参数设为TRUE的前提下,调用WSAWaitForMultipleEvents,并等待一个重叠I/O请求完成。重叠请求完成后,完成例程会自动执行,而且WSAWaitForMultipleEvents会返回一个WAIT_IO_COMPLETION。在完成例程内,可随一个完成例程一道投递另一个重叠WSARecv请求。
    6) 检查WSAWaitForMultipleEvents是否返回WAIT_IO_COMPLETION。
    7) 重复步骤5 )和6 )。
    当调用accept处理连接时,一般创建一个AcceptEvent伪事件,当有客户连接时,需要手动SetEvent(AcceptEvent);当调用AcceptEx处理重叠的连接时,一般为ListenSocket创建一个ListenOverlapped结构,并为其指定一个伪事件,当有客户连接时,系统自动将其置信。这些伪事件的作用在于,当含有完成例程指针的异步I/O操作(如WSARecv)完成时,设置了fAlertable的WSAWaitForMultipleEvents返回WAIT_IO_COMPLETION,并调用完成例程指针指向的完成例程对数据进行处理。
    重叠I/O模型的缺点是它为每一个I/O请求都开了一个线程,当同时有成千上万个请求发生时,系统处理线程上下文切换是非常耗时的。所以这也就引出了更为先进的完成端口模型IOCP,用线程池来解决这个问题。

    下面附录源代码:

    编译环境 VS2015!

    //1.Overlapped.h

    #pragma once

    #include <WINSOCK2.h>

    #include <mswsock.h> // 该文件定义了LPFN_ACCEPTEX
    #include <Windows.h>

    #define BUFFER_SIZE 4096

    #pragma comment(lib,"ws2_32.lib")

    typedef struct _SOCKET_OBJECT_
    {
    SOCKET s; // 套接字句柄
    int nOutstandingOps; // 记录此套接字上的重叠I/O数量
    LPFN_ACCEPTEX lpfnAcceptEx; // 扩展函数AcceptEx的指针(仅对监听套接字而言)
    } SOCKET_OBJECT, *PSOCKET_OBJECT;

    typedef struct _BUFFER_OBJECT_ //缓冲区对象
    {
    OVERLAPPED ol; // 重叠结构
    char *buff; // send/recv/AcceptEx所使用的缓冲区
    int nLen; // buff的长度
    PSOCKET_OBJECT pSocket; // 此I/O所属的套接字对象
    int nOperation; // 提交的操作类型
    #define OP_ACCEPT 1
    #define OP_READ 2
    #define OP_WRITE 3
    SOCKET sAccept; // 用来保存AcceptEx接受的客户套接字(仅对监听套接字而言)
    _BUFFER_OBJECT_ *pNext;
    } BUFFER_OBJECT, *PBUFFER_OBJECT;

    PSOCKET_OBJECT GetSocketObject(SOCKET s);
    void FreeSocketObject(PSOCKET_OBJECT pSocket);
    PBUFFER_OBJECT GetBufferObject(PSOCKET_OBJECT pSocket, ULONG nLen);
    void FreeBufferObject(PBUFFER_OBJECT pBuffer);
    PBUFFER_OBJECT FindBufferObject(HANDLE hEvent);
    void RebuildArray();

    BOOL PostAccept(PBUFFER_OBJECT pBuffer);
    BOOL PostRecv(PBUFFER_OBJECT pBuffer);
    BOOL PostSend(PBUFFER_OBJECT pBuffer);

    //2.Overlapped.cpp

    #include "stdafx.h"

    #include "Overlapped.h"

    PSOCKET_OBJECT GetSocketObject(SOCKET s)
    {
    PSOCKET_OBJECT pSocket = (PSOCKET_OBJECT)GlobalAlloc(GPTR, sizeof(SOCKET_OBJECT));
    if(pSocket != NULL)
    {
    pSocket->s = s;
    }
    return pSocket;
    }

    void FreeSocketObject(PSOCKET_OBJECT pSocket)
    {
    if(pSocket->s != INVALID_SOCKET)
    {
    closesocket(pSocket->s);
    }
    GlobalFree(pSocket);
    }

    extern HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS];
    extern int g_nBufferCount;
    extern PBUFFER_OBJECT g_pBufferHead, g_pBufferTail;

    PBUFFER_OBJECT GetBufferObject(PSOCKET_OBJECT pSocket, ULONG nLen)
    {
    if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS -1)
    {
    return NULL;
    }
    PBUFFER_OBJECT pBuffer = (PBUFFER_OBJECT)GlobalAlloc(GPTR, sizeof(BUFFER_OBJECT));
    if(pBuffer != NULL)
    {
    pBuffer->buff = (char*)GlobalAlloc(GPTR, nLen);
    pBuffer->ol.hEvent = WSACreateEvent();
    pBuffer->pSocket = pSocket;
    pBuffer->sAccept = INVALID_SOCKET;

    // 将新的BUFFER_OBJECT添加到列表中
    if(g_pBufferHead == NULL)
    {
    g_pBufferHead = g_pBufferTail = pBuffer;
    }
    else
    {
    g_pBufferTail->pNext = pBuffer;
    g_pBufferTail = pBuffer;
    }
    g_events[++ g_nBufferCount] = pBuffer->ol.hEvent;
    }
    return pBuffer;
    }


    void FreeBufferObject(PBUFFER_OBJECT pBuffer)
    { // 从列表中移除BUFFER_OBJ对象
    PBUFFER_OBJECT pTest = g_pBufferHead;
    BOOL bFind = FALSE;
    if(pTest == pBuffer)
    {
    g_pBufferHead = g_pBufferTail = NULL;
    bFind = TRUE;
    }
    else
    {
    while(pTest != NULL && pTest->pNext != pBuffer)
    pTest = pTest->pNext;
    if(pTest != NULL)
    {
    pTest->pNext = pBuffer->pNext;
    if(pTest->pNext == NULL)
    g_pBufferTail = pTest;
    bFind = TRUE;
    }
    }
    // 释放它占用的内存空间
    if(bFind)
    {
    g_nBufferCount --;
    CloseHandle(pBuffer->ol.hEvent);
    GlobalFree(pBuffer->buff);
    GlobalFree(pBuffer);
    }
    }


    PBUFFER_OBJECT FindBufferObject(HANDLE hEvent)
    {
    PBUFFER_OBJECT pBuffer = g_pBufferHead;
    while(pBuffer != NULL)
    {
    if(pBuffer->ol.hEvent == hEvent)
    {
    break;
    }
    pBuffer = pBuffer->pNext;
    }
    return pBuffer;
    }

    void RebuildArray()
    {
    PBUFFER_OBJECT pBuffer = g_pBufferHead;
    int i = 1;
    while(pBuffer != NULL)
    {
    g_events[i++] = pBuffer->ol.hEvent;
    pBuffer = pBuffer->pNext;
    }
    }


    // 以下三个函数是 提交重叠I/O函数
    // PostAccept
    // PostRecv
    // PostSend

    BOOL PostAccept(PBUFFER_OBJECT pBuffer)
    {
    PSOCKET_OBJECT pSocket = pBuffer->pSocket;
    if(pSocket->lpfnAcceptEx != NULL)
    { // 设置I/O类型,增加套接字上的重叠I/O计数
    pBuffer->nOperation = OP_ACCEPT;
    pSocket->nOutstandingOps++;

    // 投递此重叠I/O
    DWORD dwBytes;
    pBuffer->sAccept = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    BOOL b = pSocket->lpfnAcceptEx(pSocket->s,
    pBuffer->sAccept,
    pBuffer->buff,
    BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) * 2),
    sizeof(sockaddr_in) + 16,
    sizeof(sockaddr_in) + 16,
    &dwBytes,
    &pBuffer->ol);
    if(!b)
    {
    if(WSAGetLastError() != WSA_IO_PENDING)
    {
    return FALSE;
    }
    }
    char szBuffer[0x1000] = {0};
    memcpy(szBuffer, pBuffer->buff, sizeof(sockaddr_in) + 16);
    printf("%s ", pBuffer->buff);
    return TRUE;
    }
    return FALSE;
    };

    BOOL PostRecv(PBUFFER_OBJECT pBuffer)
    { // 设置I/O类型,增加套接字上的重叠I/O计数
    pBuffer->nOperation = OP_READ;
    pBuffer->pSocket->nOutstandingOps++;
    // 投递此重叠I/O
    DWORD dwBytes;
    DWORD dwFlags = 0;
    WSABUF buf;
    buf.buf = pBuffer->buff;
    buf.len = pBuffer->nLen;
    if(WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
    {
    if(WSAGetLastError() != WSA_IO_PENDING)
    {
    return FALSE;

    }
    }
    return TRUE;
    }

    BOOL PostSend(PBUFFER_OBJECT pBuffer)
    { // 设置I/O类型,增加套接字上的重叠I/O计数
    pBuffer->nOperation = OP_WRITE;
    pBuffer->pSocket->nOutstandingOps++;
    // 投递此重叠I/O
    DWORD dwBytes;
    DWORD dwFlags = 0;
    WSABUF buf;
    buf.buf = pBuffer->buff;
    buf.len = pBuffer->nLen;
    if(WSASend(pBuffer->pSocket->s,
    &buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
    {
    if(WSAGetLastError() != WSA_IO_PENDING)
    {
    return FALSE;
    }
    }
    return TRUE;
    }

    //3.Sever_TCP_Overlapped.cpp

    // Server_TCP_Overlapped.cpp : 定义控制台应用程序的入口点。
    //

    #include "stdafx.h"

    #include "Overlapped.h"

    BOOL HandleIO(PBUFFER_OBJECT pBuffer);//I/O请求完成之后,处理它的函数是 HandleIO。

    HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS]; // I/O事件句柄数组
    int g_nBufferCount = 0; // 上数组中有效句柄数量
    PBUFFER_OBJECT g_pBufferHead, g_pBufferTail; // 记录缓冲区对象组成的表的地址

    int _tmain(int argc, _TCHAR* argv[])
    { // 创建监听套接字,绑定到本地端口,进入监听模式
    WSADATA wsaData;
    WORD socketVersion = 0x0202;
    if(WSAStartup(socketVersion, &wsaData) != 0)
    {
    printf("Init socket dll error!");
    exit(1);
    }
    SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2356);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    // 绑定套接字到本地机器
    if (bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
    printf(" Failed bind() ");
    return -1;
    }
    listen(sListen, 200);

    // 为监听套接字创建一个SOCKET_OBJECT对象
    PSOCKET_OBJECT pListen = GetSocketObject(sListen);

    // 加载扩展函数AcceptEx
    GUID GuidAcceptEx = WSAID_ACCEPTEX;
    DWORD dwBytes;
    WSAIoctl(pListen->s,
    SIO_GET_EXTENSION_FUNCTION_POINTER,
    &GuidAcceptEx,
    sizeof(GuidAcceptEx),
    &pListen->lpfnAcceptEx,
    sizeof(pListen->lpfnAcceptEx),
    &dwBytes,
    NULL,
    NULL);

    // 创建用来重新建立g_events数组的事件对象
    g_events[0] = WSACreateEvent();

    // 在此可以投递多个接受I/O请求
    for(int i=0; i<5; i++)
    {
    PostAccept(GetBufferObject(pListen, BUFFER_SIZE));
    }

    WSASetEvent(g_events[0]);

    while(TRUE)
    {
    int nIndex = WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);
    if(nIndex == WSA_WAIT_FAILED)
    {
    printf("WSAWaitForMultipleEvents() failed ");
    break;
    }
    nIndex = nIndex - WSA_WAIT_EVENT_0;
    for(int i=0; i<= 5; i++)
    {
    int nRet = WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);
    if(nRet == WSA_WAIT_TIMEOUT)
    {
    continue;
    }
    else
    {
    WSAResetEvent(g_events[i]);
    // 重新建立g_events数组
    if(i == 0)
    {
    RebuildArray();
    continue;
    }
    // 处理这个I/O
    PBUFFER_OBJECT pBuffer = FindBufferObject(g_events[i]);
    if(pBuffer != NULL)
    {
    if(!HandleIO(pBuffer))
    RebuildArray();
    }
    }
    }
    }
    return 0;
    }


    //I/O请求完成之后,处理它的函数是 HandleIO。
    BOOL HandleIO(PBUFFER_OBJECT pBuffer)
    {
    PSOCKET_OBJECT pSocket = pBuffer->pSocket; // 从BUFFER_OBJECT对象中提取SOCKET_OBJECT对象指针,
    // 为的是方便引用
    pSocket->nOutstandingOps --;
    // 获取重叠操作结果
    DWORD dwTrans;
    DWORD dwFlags;
    BOOL bRet = WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags);
    if(!bRet)
    { // 在此套接字上有错误发生,因此,关闭套接字,移除此缓冲区对象。
    // 如果没有其它抛出的I/O请求了,释放此缓冲区对象,否则,等待此套接字上的其它I/O也完成
    if(pSocket->s != INVALID_SOCKET)
    {
    closesocket(pSocket->s);
    pSocket->s = INVALID_SOCKET;
    }
    if(pSocket->nOutstandingOps == 0)
    {
    FreeSocketObject(pSocket);
    }
    FreeBufferObject(pBuffer);
    return FALSE;
    }
    // 没有错误发生,处理已完成的I/O
    switch(pBuffer->nOperation)
    {
    case OP_ACCEPT: // 接收到一个新的连接,并接收到了对方发来的第一个封包
    {
    // 为新客户创建一个SOCKET_OBJECT对象
    PSOCKET_OBJECT pClient = GetSocketObject(pBuffer->sAccept);
    // 为发送数据创建一个BUFFER_OBJECT对象,这个对象会在套接字出错或者关闭时释放
    PBUFFER_OBJECT pSend = GetBufferObject(pClient, BUFFER_SIZE);
    if(pSend == NULL)
    {
    printf(" Too much connections! ");
    FreeSocketObject(pClient);
    return FALSE;
    }
    RebuildArray();
    // 将数据复制到发送缓冲区
    pSend->nLen = dwTrans;
    memcpy(pSend->buff, pBuffer->buff, dwTrans);
    // 投递此发送I/O(将数据回显给客户)
    if(!PostSend(pSend))
    { // 万一出错的话,释放上面刚申请的两个对象
    FreeSocketObject(pSocket);
    FreeBufferObject(pSend);
    return FALSE;
    }
    // 继续投递接受I/O
    PostAccept(pBuffer);
    }
    break;
    case OP_READ: // 接收数据完成
    {
    if(dwTrans > 0)
    { // 创建一个缓冲区,以发送数据。这里就使用原来的缓冲区
    PBUFFER_OBJECT pSend = pBuffer;
    pSend->nLen = dwTrans;
    // 投递发送I/O(将数据回显给客户)
    PostSend(pSend);
    }
    else // 套接字关闭
    { // 必须先关闭套接字,以便在此套接字上投递的其它I/O也返回
    if(pSocket->s != INVALID_SOCKET)
    {
    closesocket(pSocket->s);
    pSocket->s = INVALID_SOCKET;
    }
    if(pSocket->nOutstandingOps == 0)
    {
    FreeSocketObject(pSocket);
    }
    FreeBufferObject(pBuffer);
    return FALSE;
    }
    }
    break;
    case OP_WRITE: // 发送数据完成
    {
    if(dwTrans > 0)
    { // 继续使用这个缓冲区投递接收数据的请求
    pBuffer->nLen = BUFFER_SIZE;
    PostRecv(pBuffer);
    }
    else // 套接字关闭
    { // 同样,要先关闭套接字
    if(pSocket->s != INVALID_SOCKET)
    {
    closesocket(pSocket->s);
    pSocket->s = INVALID_SOCKET;
    }
    if(pSocket->nOutstandingOps == 0)
    {
    FreeSocketObject(pSocket);
    }
    FreeBufferObject(pBuffer);
    return FALSE;
    }
    }
    break;
    }
    return TRUE;
    }

  • 相关阅读:
    C# 委托/Func() 中 GetInvocationList() 方法的使用 | 接收委托多个返回值
    蒋廷黻著《中国近代史》-中国近代屈辱史读后感
    ASP.NET Core 上传多文件 超简单教程
    Python
    Python
    Python
    Python
    Python
    Python
    Python
  • 原文地址:https://www.cnblogs.com/lsh123/p/5958178.html
Copyright © 2020-2023  润新知