• Windows io完成端口


    Windows 提供一种称为I/O完成端口(I/O Completion Port)机制,能够让I/O的完成处理交由一个专门的线程池来完成,而线程池的线程数量是一个可配置的参数。这种做法将I/O请求的发起动作与完成处理分离到了不同的线程中。

    I/O完成端口是内核对象。个人的感觉应该叫它“完成队列”似乎更合适一些,总之这个“端口”和我们平常所说的用于网络通信的“端口”完全不是一个东西。

    之所以叫“完成”端口,就是说系统会在网络I/O操作“完成”之后才会通知我们,也就是说,我们在接到系统的通知的时候,其实网络操作已经完成了,就是比如说在系统通知我们的时候,并非是有数据从网络上到来,而是来自于网络上的数据已经接收完毕了;或者是客户端的连入请求已经被系统接入完毕了等等,我们只需要处理后面的事情就好了。
     

    重叠结构

    typedef struct _OVERLAPPED {
    DWORD Internal;
    DWORD InternalHigh;
    DWORD Offset;
    DWORD OffsetHigh;
    HANDLE hEvent;
    } OVERLAPPED

    CreateIoCompletionPort


    HANDLE  CreateIoCompletionPort(
    __in HANDLE FileHandle,   //有效的文件句柄或INVALID_HANDLE_VALUE
    __in_opt HANDLE ExistingCompletionPort, //是已经存在的完成端口。如果为NULL,则为新建一                                                                                       个IOCP
    __in ULONG_PTR CompletionKey,  //传送给处理函数的参数
    __in DWORD NumberOfConcurrentThreads   //是有多少个线程在访问这个消息队列。当参数ExistingCompletionPort不为0的时候,系统忽略该参数,当该参数为0表示允许同时相等数目于处理器个数的线程访问该消息队列。
    );

    NumberOfConcurrentThreads   :操作系统可以允许同时处理I / O完成端口的I / O完成数据包的最大线程数.也就是并发量。

    返回值:返回一个IOCP的句柄。若为NULL则创建失败,不为NULL则创建成功。

    CreateIoCompletionPort函数会创建一个I/O完成端口,并使其与一个或多个文件句柄发生关联。完成端口的并发量可以在创建该完成端口时指定同时系统内核实际上会创建5个不同的数据结构。

    在一般情况下,我们需要且只需要建立这一个完成端口。

    1.设备列表

    2.io完成队列(先进先出)

    3.等待线程队列(先进后出)

    4.已释放线程列表

    5.已暂停线程列表

     GetQueuedCompletionStatus

    BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,      
    LPDWORD lpNumberOfBytes,   
    PULONG_PTR lpCompletionKey,
    LPOVERLAPPED *lpOverlapped,
    DWORD dwMilliseconds
    );

    CompletionPort:指定的IOCP,该值由CreateIoCompletionPort函数创建。
    lpnumberofbytes:一次完成后的I/O操作所传送数据的字节数。
    lpcompletionkey:当文件I/O操作完成后,用于存放与之关联的CK。就是CreateIoCompletionPort                                             函数的CompletionKey参数
    lpoverlapped:为调用IOCP机制所引用的OVERLAPPED结构。就是异步操作api的对应的                                          OVERLAPPED结构
    dwmilliseconds:用于指定调用者等待CP的时间。

    返回值:调用成功,则返回非零数值,相关数据存于lpNumberOfBytes、lpCompletionKey、lpoverlapped变量中。失败则返回零值。
    具体表现为:

    1.如果函数从完成端口取出一个成功I/O操作的完成包,返回值为非0。函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数中存储相关信息。

    2.如果 *lpOverlapped为空并且函数没有从完成端口取出完成包,返回值则为0。函数则不会在lpNumberOfBytes and lpCompletionKey所指向的参数中存储信息。调用GetLastError可以得到一个扩展错误信息。如果函数由于等待超时而未能出列完成包,GetLastError返回WAIT_TIMEOUT.
    3.如果 *lpOverlapped不为空并且函数从完成端口出列一个失败I/O操作的完成包,返回值为0。函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数指针中存储相关信息。调用GetLastError可以得到扩展错误信息 。

    4.如果关联到一个完成端口的一个socket句柄被关闭了,则GetQueuedCompletionStatus返回ERROR_SUCCESS(也是0),并且lpNumberOfBytes等于0

    PostQueuedCompletionStatus

    BOOL PostQueuedCompletionStatus(
    HANDLE CompletlonPort,
    DW0RD dwNumberOfBytesTrlansferred,
    DWORD dwCompletlonKey,
    LPOVERLAPPED lpoverlapped,
    );

    CompletionPort:指定想向其发送一个完成数据包的完成端口对象。
    dwNumberOfBytesTrlansferred:指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数
    dwCompletlonKey:指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数
    lpoverlapped:指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数

    提供了一种方式来与线程池中的所有线程进行通信。如,当用户终止服务应用程序时,我们想要所有线程都完全利索地退出。但是如果各线程还在等待完成端口而又没有已完成的I/O 请求,那么它们将无法被唤醒。 通过为线程池中的每个线程都调用一次PostQueuedCompletionStatus,我们可以将它们都唤醒。每个线程会对GetQueuedCompletionStatus的返回值进行检查,如果发现应用程序正在终止,那么它们就可以进行清理工作并正常地退出。

    说白了就是模拟一个io完成请求,这样io完成队列就得到一个模拟项,GetQueuedCompletionStatus会返回true.线程会被唤醒,可进行清理工作并正常地退出。

    重点理解

    IOCP本质上就是Windows内核提供的一种请求队列+通知队列,我们把各种耗时的网络操作请求投递到请求队列,IOCP具体怎么去完成这些网络操作我们不管,IOCP完成后会把结果放到通知队列里,我们就去通知队列里获取结果然后处理。

    1.创建普通内核对象(如套接字),并设置异步模式WSA_FLAG_OVERLAPPED

    SOCKET socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    

    2.创建io完成端口并绑定内核对象

    1. HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    2. CreateIoCompletionPort((HANDLE)socket, hIOCP, socket, 0);

    3.投递到请求队列

    使用带有重叠结构的api函数,同时可以自定义结构体(目的是区分各个不同api函数)。目的就是是异步操作api和io完成端口保持关联。因为异步操作aip把重叠结构overlap的指针一起投递到IOCP中。调用GetQueuedCompletionStatus函数获取IO通知时,获取到的变量就是之前overlap的指针。甚至可以继承自定义结构体派生出不一样的结构体。如GetQueuedCompletionStatus函数返回时,接收的数据就在其第四个参数lpoverlapped里。

    异步操作的投递参数可以通过CreateIoCompletionPort的第三个参数lpCompletionKey、第四个参数lpoverlapped。获取对应GetQueuedCompletionStatus函数的lpCompletionKey和lpOverlapped。

    1. typedef struct Context
    2. {
    3. OVERLAPPED overlap;
    4. int iOperateType;
    5. }SContext;
    6. 以后我们就不向IOCP投递重叠结构的指针了,而是改成投递上下文的指针。
    7. 因为我们把重叠结构放在了上下文的第一位,这样重叠结构的首地址和上下文的首地址就是相等的。
    8. 所以在投递上下文的指针时,其实就相当于投递了重叠结构的指针,
    9. 而获取IO通知时获取到的重叠结构的指针,也相当于获取到了上下文的指针。
    10. typedef struct Context
    11. {
    12. OVERLAPPED overlap;
    13. int iOperateType;
    14. }SContext;
    15. typedef struct RecvContext
    16. : SContext
    17. {
    18. WSABUF buf;
    19. PFNRecv pfnRecv;
    20. }SRecvContext;
    1. SContext contextConn = {0};
    2. contextConn.iOperateType = 1;
    3. pfnConnectEx(socket, (sockaddr*)&addrConn, sizeof(addrConn), NULL, 0, NULL, (OVERLAPPED*)&contextConn);
    4. SContext contextRecv = {0};
    5. contextRecv.iOperateType = 2;
    6. WSARecv(socket, &buf, 1, NULL, &dwFlags, (OVERLAPPED*)&contextRecv, NULL);

    4.从通知队列获取io通知。

    首先创建线程池,一般线程的个数为cpu的个数的2倍。然后在工作线程(属于回调函数)中获取IO通知和处理网络操作的结果。通过GetQueuedCompletionStatus函数获取io通知,这个函数是阻塞函数,会一直等待io通知。收到io通知才返回。通过GetQueuedCompletionStatus的第四个参数LPOVERLAPPED指针来区分不同的处理。完成端口非常厚道,因为它是先把用户数据接收回来之后再通知用户直接来取就好了。

    1. SYSTEM_INFO si;
    2. GetSystemInfo(&si);
    3. for (DWORD i = 0; i < 2 * si.dwNumberOfProcessors; i++)
    4. {
    5. CreateThread(NULL, 0, _threadIOCPWork, NULL, 0, NULL);
    6. }
    1. typedef void (*PFNConn)(BOOL bSucc);
    2. typedef void (*PFNRecv)(void* pData, unsigned int uiDataSize);
    3. PFNConn pfnConn;
    4. PFNRecv pfnRecv;
    5. DWORD WINAPI _threadIOCPWork(LPVOID param)
    6. {
    7. while (true)
    8. {
    9. DWORD dwBytes;
    10. SOCKET socketGet;
    11. SContext* contextGet;
    12. BOOL bIOSucc = GetQueuedCompletionStatus(hIOCP, &dwBytes, (PULONG_PTR)&socketGet, (LPOVERLAPPED*)&contextGet, INFINITE);
    13. if (contextGet->iOperateType == 1)
    14. {
    15. pfnConn(bIOSucc);
    16. }
    17. if (contextGet->iOperateType == 2)
    18. {
    19. pfnRecv(buf.buf, dwBytes);
    20. }
    21. }
    22. return 0;
    23. }
    24. SRecvContext contextRecv = {0};
    25. contextRecv.iOperateType = 2;
    26. contextRecv.buf.buf = (char*)malloc(1024);
    27. contextRecv.buf.len = 1024;
    28. contextRecv.pfnRecv = pfnRecv;
    29. WSARecv(socket, &contextRecv.buf, 1, NULL, &dwFlags, (OVERLAPPED*)&contextRecv, NULL);
    30. DWORD dwBytes;
    31. SContext* contextGet;
    32. BOOL bIOSucc = GetQueuedCompletionStatus(hIOCP, &dwBytes, (PULONG_PTR)&socketGet, (LPOVERLAPPED*)&contextGet, INFINITE);
    33. if (contextGet->iOperateType == 2)
    34. {
    35. SRecvContext* contextRecv = (SRecvContext*)contextGet;
    36. contextRecv->pfnRecv(contextRecv->buf.buf, dwBytes);
    37. }

    实操例子

    1、在网络通讯套接字的应用上,都会用WSASocket、AcceptEx、WSASend()和WSARecv()代替。因为参数里面都会附带一个重叠结构,这是为什么呢?因为重叠结构我们就可以理解成为是一个网络操作的ID号,也就是说我们要利用重叠I/O提供的异步机制的话,每一个网络操作都要有一个唯一的ID号,因为进了系统内核,里面黑灯瞎火的,也不了解上面出了什么状况,一看到有重叠I/O的调用进来了,就会使用其异步机制,并且操作系统就只能靠这个重叠结构带有的ID号来区分是哪一个网络操作了,然后内核里面处理完毕之后,根据这个ID号,把对应的数据传上去。所以重叠结构OVERLAPPED)包含每个异步操作的区分、及信息(数据缓存、数据大小、数据下一步处理)及。

    2.之所以叫“完成”端口,就是说系统会在网络I/O操作“完成”之后才会通知我们,也就是说,我们在接到系统的通知的时候,其实网络操作已经完成了,就是比如说在系统通知我们的时候,并非是有数据从网络上到来,而是来自于网络上的数据已经接收完毕了;或者是客户端的连入请求已经被系统接入完毕了等等,我们只需要处理后面的事情就好了。

    函数解析:

    1.WSASocket

    需要使用重叠IO,必须得使用WSASocket来建立监听Socket,才可以支持重叠IO操作.

    WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

    想要使用重叠I/O的话,初始化Socket的时候一定要使用WSASocket并带上WSA_FLAG_OVERLAPPED参数才可以(只有在服务器端需要这么做,在客户端是不需要的);

    SOCKET WSASocket (
    int af,
    int type,
    int protocol,
    LPWSAPROTOCOL_INFO lpProtocolInfo,
    GROUP g,
    DWORD dwFlags
    );

    若无错误发生,WSASocket()返回新套接口的描述字。否则的话,返回 INVALID_SOCKET,应用程序可定调用WSAGetLastError()来获取相应的错误代码。

    2.CreateIoCompletionPort 

    建立一个完成端口,整个程序共用一个完成端口即可。

    CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );

    函数原型见上面.

    3.WSAIoctl

    如使用AcceptEx函数,因为这个是属于WinSock2规范之外的微软另外提供的扩展函数。所以需要额外获取一下函数的指针。

    因为我们在未取得函数指针的情况下就调用AcceptEx的开销是很大的,因为AcceptEx 实际上是存在于Winsock2结构体系之外的(因为是微软另外提供的),所以如果我们直接调用AcceptEx的话,首先我们的代码就只能在微软的平台上用了,没有办法在其他平台上调用到该平台提供的AcceptEx的版本(如果有的话), 而且更糟糕的是,我们每次调用AcceptEx时,Service Provider都得要通过WSAIoctl()获取一次该函数指针,效率太低了,所以还不如我们自己直接在代码中直接去这么获取一下指针好了。要避免这种性能损失,需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。

    1. m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    2. GUID GuidAcceptEx = WSAID_ACCEPTEX;
    3. LPFN_ACCEPTEX m_lpfnAcceptEx; //函数指针
    4. GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
    5. LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockAddrs;
    6. if(SOCKET_ERROR == WSAIoctl(
    7. m_pListenContext->m_Socket,
    8. SIO_GET_EXTENSION_FUNCTION_POINTER,
    9. &GuidAcceptEx,
    10. sizeof(GuidAcceptEx),
    11. &m_lpfnAcceptEx,
    12. sizeof(m_lpfnAcceptEx),
    13. &dwBytes,
    14. NULL,
    15. NULL))
    16. if(SOCKET_ERROR == WSAIoctl(
    17. m_pListenContext->m_Socket,
    18. SIO_GET_EXTENSION_FUNCTION_POINTER,
    19. &GuidGetAcceptExSockAddrs,
    20. sizeof(GuidGetAcceptExSockAddrs),
    21. &m_lpfnGetAcceptExSockAddrs,
    22. sizeof(m_lpfnGetAcceptExSockAddrs),
    23. &dwBytes,
    24. NULL,
    25. NULL))

    在vs2017中的 MSWSock.h 文件中 LPFN_ACCEPTEX 就是AcceptEx 函数指针。 LPFN_GETACCEPTEXSOCKADDRS就是GetAcceptExSockAddrs 函数指针。

    WSAIoctl只是获取到函数指针,真正的应用在调用函数LPFN_ACCEPTEX LPFN_GETACCEPTEXSOCKADDRS的使用

    int WSAAPI WSAIoctl(SOCKET s,
    DWORD dwIoControlCode,
    LPVOID lpvInBuffer,
    DWORD cbInBuffer,
    LPVOID lpvOutBuffer,
    DWORD cbOutBuffer,
    LPDWORD lpcbBytesReturned,
    LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE
    lpCompletionRoutine);

         dwIoControlCode:将进行的操作的控制代码。
      lpvInBuffer:输入缓冲区的地址。
      cbInBuffer:输入缓冲区的大小。
      lpvOutBuffer:输出缓冲区的地址。
      cbOutBuffer:输出缓冲区的大小。
      lpcbBytesReturned:输出实际字节数的地址。
      lpOverlapped:WSAOVERLAPPED结构的地址。
      lpCompletionRoutine:一个指向操作结束后调用的例程指针。

    4.AcceptEx

    Windows套接字AcceptEx函数接受一个新的连接,返回本地和远程地址,并接收由客户端应用程序发送的第一块数据。

    1. typedef struct _PER_IO_CONTEXT
    2. {
    3. OVERLAPPED m_Overlapped; // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)
    4. SOCKET m_sockAccept; // 这个网络操作所使用的Socket
    5. WSABUF m_wsaBuf; // WSA类型的缓冲区,用于给重叠操作传参数的
    6. char m_szBuffer[MAX_BUFFER_LEN]; // 这个是WSABUF里具体存字符的缓冲区
    7. OPERATION_TYPE m_OpType; // 标识网络操作的类型(对应上面的枚举)
    8. }PER_IO_CONTEXT, *PPER_IO_CONTEXT;
    9. PER_IO_CONTEXT* pAcceptIoContext;
    10. // 为以后新连入的客户端先准备好Socket( 这个是与传统accept最大的区别 )
    11. pAcceptIoContext->m_sockAccept = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    12. WSABUF *p_wbuf = &pAcceptIoContext->m_wsaBuf;
    13. OVERLAPPED *p_ol = &pAcceptIoContext->m_Overlapped;
    14. if(FALSE == m_lpfnAcceptEx( m_pListenContext->m_Socket, pAcceptIoContext->m_sockAccept, p_wbuf->buf, p_wbuf->len - ((sizeof(SOCKADDR_IN)+16)*2),   
    15.                                 sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, &dwBytes, p_ol))  

    BOOL 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
    [in]侦听套接字。服务器应用程序在这个套接字上等待连接。
    sAcceptSocket
    [in]将用于连接的套接字。此套接字必须不能已经绑定或者已经连接。
    lpOutputBuffer
    [in]指向一个缓冲区,该缓冲区用于接收新建连接的
    所发送数据的第一个块、该服务器的本地地址和客户端的远程地址。接收到的数据将被写入到缓冲区0偏移处,而地址随后写入。 该参数必须指定,如果此参数设置为NULL,将不会得到执行,也无法通过GetAcceptExSockaddrs函数获得本地或远程的地址。
    dwReceiveDataLength
    [in]lpOutputBuffer字节数,指定接收数据缓冲区lpOutputBuffer的大小。这一大小应不包括服务器的本地地址的大小或客户端的远程地址,他们被追加到输出缓冲区。如果dwReceiveDataLength是零,AcceptEx将不等待接收任何数据,而是尽快建立连接。
    dwLocalAddressLength
    [in]为本地地址信息保留的字节数。此值必须比所用传输协议的最大地址大小长16个字节。
    dwRemoteAddressLength
    [in]为远程地址的信息保留的字节数。此值必须比所用传输协议的最大地址大小长16个字节。 该值不能为0。
    dwBytesReceived
    [out]指向一个DWORD用于标识接收到的字节数。此参数只有在同步模式下有意义。如果函数返回ERROR_IO_PENDING并在迟些时候完成操作,那么这个DWORD没有意义,这时你必须获得从完成通知机制中读取操作字节数。
    lpOverlapped
    [in]一个OVERLAPPED结构,用于处理请求。此参数必须指定,它不能为空。
    返回值
    如果没有错误发生,AcceptEx函数成功完成并返回TRUE。 [1] 
    如果函数失败,AcceptEx返回FALSE。可以调用WSAGetLastError函数获得扩展的错误信息。如果WSAGetLastError返回ERROR_IO_PENDING,那么这次行动成功启动并仍在进行中。

    accept、WSAAccept是同步操作,AcceptEx是异步操作

    而AcceptEx比Accept又强大在哪里呢?是有三点:

             1. 这个好处是最关键的,是因为AcceptEx是在客户端连入之前,就把客户端的Socket建立好了,也就是说,AcceptEx是先建立的Socket,然后才发出的AcceptEx调用,也就是说,在进行客户端的通信之前,无论是否有客户端连入,Socket都是提前建立好了;而不需要像accept是在客户端连入了之后,再现场去花费时间建立Socket。

           2.可以同时在完成端口上投递多个请求。

           3.在我们收到AcceptEx完成的通知的时候,我们就已经把这第一组数据接完毕了

    5.GetAcceptExSockaddrs

    GetAcceptExSockaddrs是专门为AcceptEx函数准备的,它将AcceptEx接受的第一块数据中的本地和远程机器的地址返回给用户。即可以获取客户端发来的第一组数据。

    1. PER_IO_CONTEXT* pIoContext;
    2. SOCKADDR_IN* ClientAddr = NULL;
    3. SOCKADDR_IN* LocalAddr = NULL;
    4. int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN);
    5. m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf, pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),
    6. sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen);

    void GetAcceptExSockaddrs(
    _In_ PVOID lpOutputBuffer,
    _In_ DWORD dwReceiveDataLength,
    _In_ DWORD dwLocalAddressLength,
    _In_ DWORD dwRemoteAddressLength,
    _Out_ LPSOCKADDR *LocalSockaddr,
    _Out_ LPINT LocalSockaddrLength,
    _Out_ LPSOCKADDR *RemoteSockaddr,
    _Out_ LPINT RemoteSockaddrLength
    );

    lpOutputBuffer [in]
    指向传递给AcceptEx函数接收客户第一块数据的缓冲区
    dwReceiveDataLength [in]
    lpoutputBuffer缓冲区的大小,必须和传递给AccpetEx函数的一致
    dwLocalAddressLength [in]
    为本地地址预留的空间大小,必须和传递给AccpetEx函数一致
    dwRemoteAddressLength [in]
    为远程地址预留的空间大小,必须和传递给AccpetEx函数一致
    LocalSockaddr [out]
    用来返回连接的本地地址
    LocalSockaddrLength [out]
    用来返回本地地址的长度
    RemoteSockaddr [out]
    用来返回远程地址
    RemoteSockaddrLength [out]
    用来返回远程地址的长度

    6.CONTAINING_RECORD

    它的功能为已知结构体或类的某一成员、对象中该成员的地址以及这一结构体名或类名,从而得到该对象的基地址。具体为:成员变量的地址-成员变量和结构体首地址间的偏移量,就是结构体的首地址了。

    1. typedef struct _PER_IO_CONTEXT
    2. {
    3. OVERLAPPED m_Overlapped; // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)
    4. SOCKET m_sockAccept; // 这个网络操作所使用的Socket
    5. WSABUF m_wsaBuf; // WSA类型的缓冲区,用于给重叠操作传参数的
    6. char m_szBuffer[MAX_BUFFER_LEN]; // 这个是WSABUF里具体存字符的缓冲区
    7. OPERATION_TYPE m_OpType;
    8. }PER_IO_CONTEXT, *PPER_IO_CONTEXT;
    9. OVERLAPPED *pOverlapped = NULL;
    10. PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(pOverlapped, PER_IO_CONTEXT, m_Overlapped);

    #define CONTAINING_RECORD(address, type, field) ((type *)( \
    (PCHAR)(address) - \
    (ULONG_PTR)(&((type *)0)->field)))

    7.WSARecv

    从一个套接口接收数据的程序。主要用于在重叠模型中接收数据。

    1. DWORD dwFlags = 0;
    2. DWORD dwBytes = 0;
    3. WSABUF *p_wbuf = &pIoContext->m_wsaBuf;
    4. OVERLAPPED *p_ol = &pIoContext->m_Overlapped;
    5. pIoContext->ResetBuffer();
    6. pIoContext->m_OpType = RECV_POSTED;
    7. //pIoContext->m_sockAccept 为接收数据套接字
    8. int nBytesRecv = WSARecv( pIoContext->m_sockAccept, p_wbuf, 1, &dwBytes, &dwFlags,
    9. p_ol, NULL );
    10. // 如果返回值错误,并且错误的代码并非是Pending的话,那就说明这个重叠请求失败了
    11. if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
    12. {
    13. this->_ShowMessage("投递第一个WSARecv失败!");
    14. return false;
    15. }

    int WSAAPI WSARecv (
    SOCKET s,
    LPWSABUF lpBuffers,
    DWORD dwBufferCount,
    LPDWORD lpNumberOfBytesRecvd,
    LPINT lpFlags,
    LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );

    s:一个标识已连接套接口的描述字。
    lpBuffers:一个指向WSABUF结构数组的指针。每一个WSABUF结构包含一个缓冲区的指针和缓冲区的长度。
    dwBufferCount:lpBuffers数组中WSABUF结构的数目。
    lpNumberOfBytesRecvd:如果接收操作立即结束,一个指向本调用所接收的字节数的指针。
    lpFlags:一个指向标志位的指针。
    lpOverlapped:一个指向WSAOVERLAPPED结构的指针(对于非重叠套接口则忽略)。
    lpCompletionRoutine:一个指向接收操作结束后调用的例程的指针(对于非重叠套接口则忽略)。

    8.WSASend

    在一个已连接的套接口上发送数据。
    int WSASend (
    SOCKET s,
    LPWSABUF lpBuffers
    DWORD dwBufferCount,
    LPDWORD lpNumberOfBytesSent,
    DWORD dwFlags,
    LPWSAOVERLAPPED lpOverlapped,
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );


    s:标识一个已连接套接口的描述字。
    lpBuffers:一个指向WSABUF结构数组的指针。每个WSABUF结构包含缓冲区的指针和缓冲区的大小。
    dwBufferCount:lpBuffers数组中WSABUF结构的数目。
    lpNumberOfBytesSent:如果发送操作立即完成,则为一个指向所发送数据字节数的指针。
    dwFlags:标志位。
    lpOverlapped:指向WSAOVERLAPPED结构的指针(对于非重叠套接口则忽略)。
    lpCompletionRoutine:一个指向发送操作完成后调用的完成例程的指针。(对于非重叠套接口则忽略)。

  • 相关阅读:
    Python爬虫解析器BeautifulSoup4
    ruoyi权限设置的坑
    【前端面试必知】对vue中mixin的理解
    区块链baas平台告警方案
    【前沿技术RPA】 一文了解UiPath的项目活动设置
    为什么要选择AWS?AWS的优势有哪些?
    机器学习泛化误差
    洛谷 P3834 【模板】可持久化线段树 2(主席树)
    【JS笔记】JS中的DOM对象以及通过JS获取DOM结点,操作DOM属性
    java uniapp旅游微信小程序的开发hbuilderx
  • 原文地址:https://blog.csdn.net/baidu_16370559/article/details/127427348