重叠模型的基本设计原理是让应用程序使用重叠的数据结构,一次投递一个或多个WinsockI/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。模型的总体设计以Windows重叠I/O机制为基础。这个机制可通过ReadFile和WriteFile两个函数,在设备上执行I/O操作。
要想在一个套接字上使用重叠I/O模型,首先必须创建一个设置了重叠标志的套接字。
主要有两种方法来管理重叠I/O的请求。.事件对象通知 .完成实例。
事件通知:
重叠I/O的事件通知方法要求将Windows事件对象与WSAOVERLAPPED结构关联在一起。若使用一个WSAOVERLAPPED结构,发出像WSASend和WSARecv这样的I/O调用,它们会立即返回。
WSAOVERLAPPED结构为重叠I/O请求的初始化及其后续的完成之间提供了一种通信媒介。结构的定义如下:
typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
}WSAOVERLAPPED, FAR* LPWSAOVERLAPPED;
Internal,InternalHigh,Offset,OffsetHigh字段均由系统在内部使用,不能有应用程序直接进行处理或使用。hEvent字段则允许应用程序将事件对象的句柄同操作关联起来。
一个重叠I/O完成以后,应用程序要负责获取重叠I/O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中,Winsock会更改与WSAOVERLAPPED结构关联的事件对象的事件传信状态,将未传信变成已传信。由于已经有一个事件对象分配给WSAOVERLAPPED结构,所有只需简单的调用WSAWaitForMultipleEvents函数,便可判断出重叠I/O调用将在什么时候完成。WSAWaitForMultipleEvents会等待一段指定时间,等待一个或多个事件进入已传信状态。 WSAWaitForMultipleEvents一次只能等待64个事件对象。确定某个重叠事件完成以后,接着需要调用WSAGetOverlappedResult函数,判断这个重叠调用是否成功。
BOOL WSAGetOverlappedResult(
SOCKET s, //重叠操作开始的时候,被指定的套接字
LPWSAOVERLAPPED lpOverlapped, //重叠操作开始的时候,被指定的WSAOVERLAPPED结构
LPDWORD lpcbTransfer,//负责接收一次重叠发送或接收操作实际传输的字节数
BOOL fWait,//用于决定函数是否应该等待挂起的重叠操作完成
LPWORD lpdwFlags //负责接收结果标志
);
若WSAGetOverlappedResult函数调用成功,返回值就是TRUE,意味着重叠操作完成成功,而且lpcbTransfer参数所指向的值已进行了更新,若返回FALSE,那么可能是由以下原因造成的:
.重叠I/O操作仍处于挂起状态
.重叠操作已经完成,但含有错误
.因为在提供给WSAGetOverlappedResult函数的一个或多个参数中存在错误,所有无法判断重叠操作的完成状态
失败后,lpcbTransfer所指向的值不会被更新,而且应用程序应调用WSAGetLastError函数查看错误原因。 利用事件通知机制设计一个简单的服务器应用程序,令其在一个套接字上对重叠I/O操作进行管理:
#define DATA_BUFSIZE 2046 void main(void)
{
WSABUF DataBuf;
char buffer[DATA_BUFSIZE];
DWORD EventTotal = ;
DWORD RecvBytes = ;
DWORD Flags = ;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket, AcceptSocket; //第一步
//启动Winsock,建立监听套接字
...
//第二步
//接收一个入站连接
AcceptSocket = accept(ListenSocket,NULL,NULL);
//第三步
//建立一个重叠结构 EventArray[EventTotal] = WSACreateEvent(); ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[EventTotal]; DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer; EventTotal++; //第四步
//接收一个WSARecv请求,以便在套接字上接收数据
if(SOCKET_ERROR ==
WSARecv(AcceptSocket, &DataBuf, , &RecvBytes, &Flags, &AcceptOverlapped, NULL))
{
if(WSA_IO_PENDING != WSAGetLastError())
{
//出错
}
}
//处理套件子上的重叠接收
while(TRUE)
{
DWORD Index;
//第五步
//等候重叠I/O调用结束
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
//索引应为0,因为EventArray中仅有一个事件 //第六步
//重置已传信事件
WSAResetEvent(EventArray[Index-WSA_WAIT_EVENT_0]);
//第七步
//确定重叠请求的状态
WSAGetOverlappedResult(AcceptSocket,&AcceptOverlapped,&BytesTransferred,FALSE,&Flags); //先 检查看通信对方是否已经关闭连接,如果关闭,则关闭套接字
if(BytesTransferred==)
{
printf("Closing socket %d\n", AcceptSocket);
closesocket(AcceptSocket);
WSACloseEvent(EventArray[Index-WSA_WAIT_EVENT_0]);
return ;
} //对接收到的数据进行某种处理
//DataBuf包含接收到的数据
... //第八步
//在套接字上投递另一个WSARecv请求 Flags = ;
ZeroMemory(&AccpetOverlapped, sizeof(WSAOVERLAPPED)); AcceptOverlapped.hEvent = EventArray[Index-WSA_WAIT_EVENT_0];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
if(SOCKET_ERROR ==
WSARecv(AcceptSocket, &DataBuf, , &RecvBytes, &Flags, &AcceptOverlapped, NULL))
{
if(WSA_IO_PENDING != WSAGetLastError())
{
//出错
}
}
}
}
对该程序采用的编程步骤总结如下:
.创建一个套接字,开始在指定的端口上监听连接请求
.接受一个入站的连接请求
.为接收的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将该事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents使用。
.将WSAOVERLAPPED指定为参数,在套接字上投递一个异步WSARecv请求
.使用步骤3的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入已传信状态
.使用WSAGetOverlappedResult函数,判断重叠调用的返回状态
.函数完成后,针对重叠数组,调用WSAResetEvent函数,从而重设事件对象,并对完成的重叠请求进行处理
.在套接字上投递另一个重叠WSARecv请求
.重复步骤5~
这个例子极易扩展,从而提供对多个套接字的支持。方法是将代码的重叠I/O处理部分移至一个对立的线程中,让主应用程序线程为额外的连接请求提供服务。
===================================================================
#include<winsock2.h>
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib"); #define PORT 5050
#define MSGSIZE 1024 typedef struct
{
WSAOVERLAPPED overlap;
WSABUF Buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA; int g_iTotalConn = ;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS]; DWORD WINAPI WorkerThread(LPVOID lpParam);
void Cleanup(int index); int main()
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN client, local;
DWORD dwThreadId;
int iAddrSize = sizeof(SOCKADDR_IN); WSAStartup(MAKEWORD(,), &wsaData);
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&local, , sizeof(SOCKADDR_IN));
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
local.sin_addr.s_addr = htonl(INADDR_ANY); bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN));
listen(sListen, ); CreateThread(NULL, , WorkerThread, NULL, , &dwThreadId); while(TRUE)
{
sClient = accept(sListen, (SOCKADDR*)&client, &iAddrSize);
printf("Accepted Client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
g_CliSocketArr[g_iTotalConn] = sClient;
g_pPerIoDataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA)
);
g_pPerIoDataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
g_pPerIoDataArr[g_iTotalConn]->Buffer.buf = g_pPerIoDataArr[g_iTotalConn]->szMessage;
g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
g_CliEventArr[g_iTotalConn] = g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent; WSARecv(g_CliSocketArr[g_iTotalConn],
&g_pPerIoDataArr[g_iTotalConn]->Buffer,
,
&g_pPerIoDataArr[g_iTotalConn]->NumberOfBytesRecvd,
&g_pPerIoDataArr[g_iTotalConn]->Flags,
&g_pPerIoDataArr[g_iTotalConn]->overlap,
NULL);
g_iTotalConn++;
}
closesocket(sListen);
WSACleanup(); return ;
} DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int ret, index;
DWORD cbTransferred;
while(TRUE)
{
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, , FALSE);
if(ret==WSA_WAIT_FAILED || ret==WSA_WAIT_TIMEOUT)
{
//如果当前没有客户端的话,要sleep一下,要不然CUP会占50%以上
if(g_iTotalConn==)
Sleep();
continue;
}
index = ret - WSA_WAIT_EVENT_0;
WSAResetEvent(g_CliEventArr[index]);
WSAGetOverlappedResult(g_CliSocketArr[index],
&g_pPerIoDataArr[index]->overlap,
&cbTransferred,
TRUE,
&g_pPerIoDataArr[index]->Flags);
if(cbTransferred==)
{
Cleanup(index);
}
else
{
g_pPerIoDataArr[index]->szMessage[cbTransferred] = '\0';
send(g_CliSocketArr[index],g_pPerIoDataArr[index]->szMessage,cbTransferred,); WSARecv(g_CliSocketArr[index],
&g_pPerIoDataArr[index]->Buffer,
,
&g_pPerIoDataArr[index]->NumberOfBytesRecvd,
&g_pPerIoDataArr[index]->Flags,
&g_pPerIoDataArr[index]->overlap,
NULL);
}
}
return ;
} void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
HeapFree(GetProcessHeap(), , g_pPerIoDataArr[index]);
if(index<g_iTotalConn-)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-];
g_pPerIoDataArr[index] = g_pPerIoDataArr[g_iTotalConn-];
}
g_pPerIoDataArr[--g_iTotalConn] = NULL;
} 完成例程:
完成例程是应用程序用来管理完成的重叠I/O请求的另一种方法。完成例程其实就是一些函数,我们将这些函数传递给重叠I/O请求,以供重叠I/O请求完成时由系统调用。它们的设计宗旨,是通过调用者的线程,为已完成的I/O请求提供服务。除此以外,应用程序可通过完成例程,继续进行重叠I/O的处理。
如果希望用完成例程为重叠I/O请求提供服务,应用程序必须为一个绑定I/O的Winsock函数指定一个完成例程,同时指定一个WSAOVERLAPPED结构。一个完成例程必须拥有下述函数原型:
void CALLBACK CompletionROUTINE(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
.dwError参数表明一个重叠操作的完成状态时什么
.cbTransferred参数指明了在重叠操作期间,实际传输的字节量是多大
.lpOverlapped参数指明传递到最初的I/O调用内的一个WSAOVERLAPPED结构
.dwFlags参数返回操作结束时可能用的标志
在用一个完成例程提交的重叠请求与用一个事件对象提交的重叠请求之间,存在着一个非常重要的区别。WSAOVERLAPPED的hEvent并未被使用,也就是说,不可以将一个事件对象同重叠请求关联在一起。用完成例程发出重叠I/O调用之后,调用线程一旦完成,最终必须为完成例程提供服务。这样,便要求我们将调用线程置于一种警觉等待状态。并在I/O操作完成后,对完成例程加以处理。WSAWaitForMultipleEvents可以将线程置于一种警觉的等待状态。这样做的缺点在于,我们必须还有一个事件对象可用于WSAWaitForMultipleEvents函数。假定应用程序用完成例程只对重叠请求进行处理,便不大可能有什么事件对象需要处理。作为一种变通方法,应用程序可用SleepEx函数将线程置于一种警觉的等待状态。当然,也可创建一个伪事件对象,它不与任何东西关联在一起。假如调用线程总是处于繁忙状态,而不是处于一种警觉的等待状态,那么根本不会有被投递的完成例程会得到调用。
WSAWaitForMultipleEvents通常会等待同WSAOVERLAPPED关联在一起的事件对象。该函数也用来将线程置于一种警觉的等待状态,并可为已经完成的重叠I/O请求进行完成例程的处理(前提是将fAlertable设为TRUE)。用完成例程接收重叠I/O请求之后,返回值是WSA_IO_COMPLETION,而不是事件数组中的一个事件对象的索引。SleepEx实际上和WSAWaitForMultipleEvents差不多,只是它不需要事件对象。
DWORD SleepEx(
DWORD dwMilliseconds,
BOOL bAlertable
);
dwMilliseconds定义了SleepEx函数的等待时间,以毫秒为单位,如果将dwMilliseconds设为INFINITE,那么SleepEx会无休止的等待下去。
bAlertable指定了完成例程的执行方式。假如将bAlertable设为FALSE,而且进行了一次I/O完成回叫,那么I/O完成函数就不会执行,而且该函数也不会返回。除非超过由dwMilliseconds规定的时间。若设为TRUE,那么完成例程便会得到执行,同时SleepEx函数返回WAIT_IO_COMPLETION。
下面代码演示了如果构建一个简单的服务器应用程序,令其采用前述方法,通过完成例程来实现对一个套接字请求管理:
#define DATA_BUFSIZE 4096 SOCKET AcceptSocket, ListenSocket;
WSABUF DataBuf;
char buffer[DATA_BUFSIZE];
WSAEVENT EventArray[MAXIMUM_WAIT_OBJECTS];
DWORD Flags, RecvBytes, Index; void main(void)
{
WSAOVERLAPPED Overlapped;
//第一步
//启动Winsock,建立监听套接字
... //第二步
//接受一个新连接
AcceptSocket = accept(ListenSocket, NULL, NULL); //第三步
//已经有一个接收套接字之后,开始使用带有完成例程的重叠I/O来处理I/O
//为了启动重叠I/O处理,先提交一个重叠WSARecv请求 Flags = ; ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED)); DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer; //第四步
//将WSAOVERLAPPED结构指定为一个参数,在套接字上投递一个异步WSARecv请求并提供下面的
//作为完成例程的WorkerRoutine函数 if(SOCKET_ERROR = WSARecv(AcceptSocket, &DataBuf, , &RecvBytes, &Flags, &Overlapped, WorkerRoutine))
{
if(WSA_IO_PENDING != WSAGetLastError())
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return ;
}
} //因为WSAWaitForMultipleEvents()API要求在一个或多个事件对象上等待,
//因此不得不创建一个伪事件对象。作为一种可选方案,也可使用SleepEx作为替代 EventArray[] = WSACreateEvent(); while(TRUE)
{
//第五步
Index = WSAWaitForMultipleEvents(, EventArray, FALSE, WSA_INFINITE, TRUE);
//第六步
if(Index==WSA_IO_COMPLETION)
{
//一个重叠请求完成例程结束,继续为更多的完成例程服务
continue;
}
else
{
//发生一个错误,停止处理
//如果正在处理一个事件对象,那么这也就可能是事件数组的一个索引
return ;
}
}
} void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred,
LPWSAOVERLAPPED Overlapped, DWORD InFlags)
{
DWORD RecvBytes, SendBytes;
DWORD Flags; if(Error!=||BytesTransferred==)
{
//要么是套接字上发生了一个错误,要么套接字已经被通信对方关闭
closesocket(AcceptSocket);
return ;
} //此刻,一个重叠的WSARecv请求顺利完成
//现在可接收DataBuf变量中包含的已接收的数据了
//处理完接收到的数据后,需要投递另外一个重叠的WSARecv或WSASend请求
//为简便起见,这里投递另外一个WSARecv请求 Flags = ; ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED)); DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer; if(SOCKET_ERROR = WSARecv(AcceptSocket, &DataBuf, , &RecvBytes, &Flags, &Overlapped, WorkerRoutine))
{
if(WSA_IO_PENDING != WSAGetLastError())
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return ;
}
}
}
程序主要的执行步骤:
.新建一个套接字,开始在指定端口上监听传入的连接
.接收一个入站连接
.为接收的套接字创建一个WSAOVERLAPPED结构
.在套接字上投递一个异步WSARecv请求,需要将WSAOVERLAPPED指定为一个参数,同时提供一个完成例程
.在将fAlertable设为TRUE的前提下,调用WSAWaitForMultipleEvents,并等待一个重叠I/O请求完成。重叠请求完成后,完成例程会自动执行,而且WSAWaitForMultipeEvents会返回一个WSA_IO_COMPLETION。在完成例程内,可用一个完成例程投递另一个重叠WSARecv请求。
.检查WSAWaitForMultipleEvents是否返回WSA_IO_COMPLETION
.重复步骤5~
重叠模型提供高性能的套接字I/O,因为使用这种模型的应用程序通知缓冲区收发系统直接使用的数据,所有跟前面几种不同。也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已到达套接字,则该数据将直接被拷贝到投递的缓冲区。在前述模型中,数据到达并被拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据将从单套接字缓冲区拷贝到应用程序的缓冲区。
在事件中使用重叠I/O的缺点,也就是每次最多只能等待64个事件这一局限性。完成例程是一个不错的替代方案,但必须注意确保投递完成操作的线程进入警觉的等待状态,以便使完成例程能够圆满的结束。同时,还要确保完成例程不要做过量的运算,以便在很重的负载之下,这些完成过程能够尽快开始运行。
=============================================================
#include <stdio.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib") #define PORT 5050
#define MSGSIZE 1024 typedef struct
{
WSAOVERLAPPED overlap;
WSABUF Buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
SOCKET sClient;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA; int g_iTotalConn = ;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS];
SOCKET g_sNewClientConnection = NULL;
BOOL g_bNewConnectionArrived = FALSE; DWORD WINAPI WorkerThread(LPVOID lpParam);
void CALLBACK CompletionRoutine(DWORD dwError, DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags); int main()
{
WSADATA wsaData;
SOCKET sListen;
SOCKADDR_IN local, client;
int iAddrSize = sizeof(SOCKADDR_IN);
DWORD dwThreadId;
WSAStartup(MAKEWORD(,), &wsaData);
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&local, , sizeof(SOCKADDR_IN));
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
local.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN));
listen(sListen, ); CreateThread(NULL, , WorkerThread, NULL, , &dwThreadId); while(TRUE)
{
g_sNewClientConnection = accept(sListen, (SOCKADDR*)&client, &iAddrSize);
g_bNewConnectionArrived = TRUE;
printf("Accepted Client:%s:%d \n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
}
return ;
} DWORD WINAPI WorkerThread(LPVOID lpParam)
{
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
while(TRUE)
{
if(g_bNewConnectionArrived)
{
lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
lpPerIOData->sClient = g_sNewClientConnection;
WSARecv(lpPerIOData->sClient,
&lpPerIOData->Buffer,
,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
CompletionRoutine);
g_bNewConnectionArrived = FALSE;
}
SleepEx(,TRUE);
}
return ;
} void CALLBACK CompletionRoutine(DWORD dwError, DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;
if(dwError!=||cbTransferred==)
{
closesocket(lpPerIOData->sClient);
HeapFree(GetProcessHeap(),,lpPerIOData);
}
else
{
lpPerIOData->szMessage[cbTransferred] = '\0';
send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, ); memset(&lpPerIOData->overlap, , sizeof(WSAOVERLAPPED));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
WSARecv(lpPerIOData->sClient,
&lpPerIOData->Buffer,
,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
CompletionRoutine);
}
}
用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后 CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。
完成例程内的实现代码比较简单,它取出接收到的数据,然后将数据原封不动的发送给客户端,最后重新激活另一个WSARecv异步操作。注意,在这里用到了 “尾随数据”。我们在调用WSARecv的时候,参数lpOverlapped实际上指向一个比它大得多的结构 PER_IO_OPERATION_DATA,这个结构除了WSAOVERLAPPED以外,还被我们附加了缓冲区的结构信息,另外还包括客户端套接字等重要的信息。这样,在完成例程中通过参数lpOverlapped拿到的不仅仅是WSAOVERLAPPED结构,还有后边尾随的包含客户端套接字和接收数据缓冲区等重要信息。这样的C语言技巧在我后面介绍完成端口的时候还会使用到。