Windows io完成端口

article/2025/9/26 19:13:38

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完成端口并绑定内核对象

HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
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。

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

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

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

SYSTEM_INFO si;
GetSystemInfo(&si);
for (DWORD i = 0; i < 2 * si.dwNumberOfProcessors; i++)
{CreateThread(NULL, 0, _threadIOCPWork, NULL, 0, NULL);
}
typedef void (*PFNConn)(BOOL bSucc);
typedef void (*PFNRecv)(void* pData, unsigned int uiDataSize);PFNConn pfnConn;
PFNRecv pfnRecv;DWORD WINAPI _threadIOCPWork(LPVOID param)
{while (true){DWORD dwBytes;SOCKET socketGet;SContext* contextGet;BOOL bIOSucc = GetQueuedCompletionStatus(hIOCP, &dwBytes, (PULONG_PTR)&socketGet, (LPOVERLAPPED*)&contextGet, INFINITE);if (contextGet->iOperateType == 1){pfnConn(bIOSucc);}if (contextGet->iOperateType == 2){pfnRecv(buf.buf, dwBytes);}}return 0;
}SRecvContext contextRecv = {0};
contextRecv.iOperateType = 2;
contextRecv.buf.buf = (char*)malloc(1024);
contextRecv.buf.len = 1024;
contextRecv.pfnRecv = pfnRecv;
WSARecv(socket, &contextRecv.buf, 1, NULL, &dwFlags, (OVERLAPPED*)&contextRecv, NULL);DWORD dwBytes;
SContext* contextGet;
BOOL bIOSucc = GetQueuedCompletionStatus(hIOCP, &dwBytes, (PULONG_PTR)&socketGet, (LPOVERLAPPED*)&contextGet, INFINITE);if (contextGet->iOperateType == 2)
{SRecvContext* contextRecv = (SRecvContext*)contextGet;contextRecv->pfnRecv(contextRecv->buf.buf, dwBytes);
}

实操例子

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()直接从底层的提供者那里取得函数的指针。


m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
GUID GuidAcceptEx = WSAID_ACCEPTEX; 
LPFN_ACCEPTEX                m_lpfnAcceptEx;   //函数指针GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; 
LPFN_GETACCEPTEXSOCKADDRS    m_lpfnGetAcceptExSockAddrs; if(SOCKET_ERROR == WSAIoctl(m_pListenContext->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &m_lpfnAcceptEx, sizeof(m_lpfnAcceptEx), &dwBytes, NULL, NULL)) if(SOCKET_ERROR == WSAIoctl(m_pListenContext->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidGetAcceptExSockAddrs,sizeof(GuidGetAcceptExSockAddrs), &m_lpfnGetAcceptExSockAddrs, sizeof(m_lpfnGetAcceptExSockAddrs),   &dwBytes, NULL, 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函数接受一个新的连接,返回本地和远程地址,并接收由客户端应用程序发送的第一块数据。

typedef struct _PER_IO_CONTEXT
{OVERLAPPED     m_Overlapped;                               // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)              SOCKET         m_sockAccept;                               // 这个网络操作所使用的SocketWSABUF         m_wsaBuf;                                   // WSA类型的缓冲区,用于给重叠操作传参数的char           m_szBuffer[MAX_BUFFER_LEN];                 // 这个是WSABUF里具体存字符的缓冲区OPERATION_TYPE m_OpType;                                   // 标识网络操作的类型(对应上面的枚举)
}PER_IO_CONTEXT, *PPER_IO_CONTEXT;PER_IO_CONTEXT* pAcceptIoContext;// 为以后新连入的客户端先准备好Socket( 这个是与传统accept最大的区别 ) pAcceptIoContext->m_sockAccept  = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);  
WSABUF *p_wbuf   = &pAcceptIoContext->m_wsaBuf;
OVERLAPPED *p_ol = &pAcceptIoContext->m_Overlapped;if(FALSE == m_lpfnAcceptEx( m_pListenContext->m_Socket, pAcceptIoContext->m_sockAccept, p_wbuf->buf, p_wbuf->len - ((sizeof(SOCKADDR_IN)+16)*2),   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接受的第一块数据中的本地和远程机器的地址返回给用户。即可以获取客户端发来的第一组数据。

PER_IO_CONTEXT* pIoContext;
SOCKADDR_IN* ClientAddr = NULL;SOCKADDR_IN* LocalAddr = NULL;  int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN);  m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf, pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),  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

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

typedef struct _PER_IO_CONTEXT
{OVERLAPPED     m_Overlapped;                               // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)              SOCKET         m_sockAccept;                               // 这个网络操作所使用的SocketWSABUF         m_wsaBuf;                                   // WSA类型的缓冲区,用于给重叠操作传参数的char           m_szBuffer[MAX_BUFFER_LEN];                 // 这个是WSABUF里具体存字符的缓冲区OPERATION_TYPE m_OpType;  
}PER_IO_CONTEXT, *PPER_IO_CONTEXT;OVERLAPPED           *pOverlapped = NULL;
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

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

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

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:一个指向发送操作完成后调用的完成例程的指针。(对于非重叠套接口则忽略)。


http://chatgpt.dhexx.cn/article/rfNZbrHe.shtml

相关文章

完成端口(Completion Port)详解

http://blog.csdn.net/piggyxp/article/details/6922277 手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解 ----- By PiggyXP(小猪) 前 言 本系列里完成端口的代码在两年前就已经写好了&#xff0c;但是由于许久没有写东西了&#xff0c;不知该如何提笔&#xf…

完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三

手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解 ----- By PiggyXP(小猪) 前 言 本系列里完成端口的代码在两年前就已经写好了&#xff0c;但是由于许久没有写东西了&#xff0c;不知该如何提笔&#xff0c;所以这篇文档总是在酝酿之中……酝酿了两年之后&…

树同构-树哈希

树同构-树哈希 题目描述 题解 对于无根树&#xff0c;由于数据范围较小&#xff0c;可以直接以每个点为根dfs一次&#xff0c;维护其树哈希的值&#xff0c;然后用并查集维护 &#xff08;若数据范围大一些&#xff0c;可以以树的重心跑dfs&#xff09; 代码实现 #include&…

2.3 树的同构(树,c)

树的同构 树的同构输入格式:输出格式:输入样例1&#xff08;对应图1&#xff09;&#xff1a;输出样例1:输入样例2&#xff08;对应图2&#xff09;&#xff1a;输出样例2: 题意理解输入两棵二叉树的信息&#xff0c;判断是否同构&#xff08;对应图1&#xff09; 求解思路二叉…

03-树1 树的同构

题目 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图2就不是同构的。 图1 图2 现给…

『树同构的判定(树Hash)』CF718D:Andrew and Chemistry

题目描述 题解 这道题目的难点在于如何判断树的同构&#xff0c;这就是所谓的树的哈希。 我们假设需要求解以x为根的子树的hash值&#xff0c;我们可以将子树的hash值存储到vector内&#xff0c;排序以后用map来判断重复。这个写法十分简单。具体如下&#xff1a; int dfs(i…

数据结构之树的同构

目录 前言 题意理解 求解思路 二叉树表示 程序框架搭建 读数据建二叉树 二叉树同构判别 前言 本篇主要讲有关二叉树的同构判断。 题意理解 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则称这两棵树是“同构”的。 例如&#xff1a; 左图…

2020牛客多校10:Identical Trees(树hash + 树同构 + 费用流模板)

题意&#xff1a;给出两棵同构的有根树&#xff0c;同构修改点的标号使得两棵树完全一样&#xff0c;至少需要修改多少次。 分析&#xff1a;肯定是将子树和另外一棵的某个子树对应&#xff0c;而两棵子树的问题是一个子问题&#xff0c;显然只有同构的子树才可以对应&#xf…

哈希算法在判定树同构方面的应用(下)

哈希算法在判定树同构方面的应用 在上一篇文章中我们介绍了 枚举根节点哈希 和 求重心哈希 两种方法来判断两棵无根树是否同构。 但是如果有些题目中我必须要计算出每个根节点的 f f f 值&#xff0c;且 n ≤ 1 e 5 n\le 1e5 n≤1e5&#xff0c;我们要怎么办呢&#xff1f;…

树的同构判断

使用递归的思路解决树的同构的判断 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图2…

树同构的判断

结构中的flag用来标记该元素是否被访问过&#xff0c;judge函数中的flag为了使一行的四个元素即使又被访问过的元素也要读完一整行。 #include<stdio.h> #include<stdbool.h> #include<stdlib.h> typedef struct TreeNode *Tree; struct TreeNode{int v;Tre…

哈希算法在判定树同构方面的应用(上)

哈希算法在判定树同构方面的应用&#xff08;上&#xff09; &#xff08;一&#xff09;需要掌握的前置知识&#xff1a; &#xff08;1&#xff09;素数筛法&#xff1a;埃氏筛或者欧拉筛均可以。 以下为欧拉筛&#xff1a; const int maxn100100; int p[maxn],cnt0; bool…

树同构判定算法

树同构判定 树同构判定 图同构与树同构 同的同构问题还没有有效算法。 树的同构本质上寻找不同树之间的双射关系。 通过对树编码&#xff0c;将树的同构问题转化为编码比较问题。 有根树的同构严格强于图同构关系。 如上&#xff0c;图同构的两张图转化成树&#xff0c;…

数据结构(三)—— 树(4):树的同构

数据结构系列内容的学习目录 → \rightarrow →浙大版数据结构学习系列内容汇总。 题意理解&#xff1a; 给定两棵树T1和T2。如果T1可以通过若干次左右子结点互换就变成T2&#xff0c;则我们称两棵树是“同构”的。         现给定两棵树&#xff0c;如下图所示&#xf…

【树】树的同构

【树】 树的同构 题目要求&#xff1a; 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而…

树的同构

来自大佬的文章&#xff1a;树的同构 给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2&#xff0c;则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的&#xff0c;因为我们把其中一棵树的结点A、B、G的左右孩子互换后&#xff0c;就得到另外一棵树。而图…

树的同构 (25分)

树的同构 (25分) 思路分析:树的同构,顾名思义两个树是否有一样的结构,每一个孩子结点的孩子的数量和值都要相同,但是它所在的位置可以左右交换(不是说两个父亲结点的孩子互相交换,而是说父亲结点的下一层孩子的左右可以交换),这样就转化了越来越小的树比较是否同构,假…

项目的数据库分析

一、表的建设 病人信息表&#xff1a;病人ID、病人姓名、卡号、住院号、病历号、医保诊断、身份证、性别、年龄、出生年月、婚否、职业、民族、国籍、出生地、省|市、县|区、单位、电话、户口地址、联系人、关系、联系电话、担保人、时间、预约日期、留观号 凭证信息表&#x…

数据分析学习之完整的数据挖掘项目流程

1、分析问题&#xff0c;明确目标 有目的解决问题才会事半功倍。 2、模型可行性分析 并不是所有问题都需要数据挖掘模型或着能通过数据挖掘模型来解决。在建模之前需要进行可行性分析。判断模型可行性的流程如图所示&#xff1a; 3、 选取模型 根据问题定义选则合适的模型&…

数据挖掘项目(一)Airbnb 新用户的民宿预定结果预测

摘要 本文主要根据对Airbnb 新用户的民宿预定结果进行预测&#xff0c;完整的陈述了从数据探索到特征工程到构建模型的整个过程。 其中&#xff1a; 1 数据探索部分主要基于pandas库&#xff0c;利用常见的:head()&#xff0c;value_counts()&#xff0c;describe()&#xff0c…