《windows网络编程技术》之 Winsock I/O方法(2)
重叠模式 之 基于事件通知的重叠模型
重叠模式是的总体设计以WIN32重叠I/O机制为基础,要想在一个套接字上使用重叠I/O模型,必须使用WSA_FLAG_OVERLAPPED这个标志,如下:
s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
如果使用的是socket函数,则默认设置为WSA_FLAG_OVERLAPPED。
使用重叠模式,就不得不熟悉WSAOVERLAPPED结构,下面是这个结构的定义:
typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
其中前4个参数都是系统内部调用的,不关我们的事。
WSAOVERLAPPED结构中的hEvent即可和一个事件对象进行关联:用WSACreateEvent创建一个事件对象,将重叠结构的hEvent字段分配给新创建的事件对象。然后调用下列winsock函数(带重叠结构类型的参数),
WSASend 、WSASendTo、 WSARecv、 WSARecvFrom、 WSAIoctl、AcceptEx、 TransimitFile
在对重叠I/O请求完成后,Winsock会更改重叠结构中的事件对象的事件传信状态,从“未传信”-->“已传信”,由于WSACreate最开始在一种未传信的工作状态中,并用一种人工重设模式创建句柄的,所以在I/O请求完成后我们的程序有责任将工作状态从“已传信”转到“未传信”,即调用WSAResetEvent函数。
对重叠I/O请求完成后,我们可以调用WSAGetOverlappedResult函数来判断重叠调用是否成功,定义如下:
BOOL WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
其中,lpcbTransfer对应一个双字变量,负责接收一次重叠发送或接收操作实际传输的字节数。
fWait,用于决定是否应该等待一次重叠操作完成。
lpdwFlags,负责接收结果标志。
下面是一个完整的过程:
1.创建一个套接字,开始在指定的端口上监听连接请求。
2.接收进入的连接请求。
3.为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄,同时把该对象句柄分配给一个事件数组,以便由WSAWaitForMultipleEvent函数使用。
4.在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。
5.使用步骤3中的事件数组,调用WSAWaitForMultipleEvent,等待与重叠调用关联的事件对像进入“已传信”状态(即,等待事件触发)
6.WSAWaitForMultipleEvent函数完成后,针对事件数组,调用WSAResetEvent重设事件对象,并对完成的重叠请求进行处理。
7.使用WSAGetOverlappedResult函数,判断重叠调用的返回状态。
8.在套接字上投递另一个重叠WSARecv请求。
9.重复5--8步骤。
代码如下:
#include
#include
#include
#define PORT 5150
#define DATA_BUFSIZE 8192
typedef struct _SOCKET_INFORMATION {
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
WSAOVERLAPPED Overlapped;
DWORD BytesSEND;
DWORD BytesRECV;
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
DWORD WINAPI ProcessIO(LPVOID lpParameter);
DWORD EventTotal = 0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
LPSOCKET_INFORMATION SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
CRITICAL_SECTION CriticalSection;
void main(void)
{
WSADATA wsaData;
SOCKET ListenSocket, AcceptSocket;
SOCKADDR_IN InternetAddr;
DWORD Flags;
DWORD ThreadId;
DWORD RecvBytes;
INT Ret;
InitializeCriticalSection(&CriticalSection);
if ((Ret = WSAStartup(0x0202,&wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
WSACleanup();
return;
}
if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Failed to get a socket %d\n", WSAGetLastError());
return;
}
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(ListenSocket, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
{
printf("bind() failed with error %d\n", WSAGetLastError());
return;
}
if (listen(ListenSocket, 5))
{
printf("listen() failed with error %d\n", WSAGetLastError());
return;
}
// Setup the listening socket for connections.
if ((AcceptSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Failed to get a socket %d\n", WSAGetLastError());
return;
}
if ((EventArray[0] = WSACreateEvent()) == WSA_INVALID_EVENT)
{
printf("WSACreateEvent failed with error %d\n", WSAGetLastError());
return;
}
// Create a thread to service overlapped requests
if (CreateThread(NULL, 0, ProcessIO, NULL, 0, &ThreadId) == NULL)
{
printf("CreateThread failed with error %d\n", GetLastError());
return;
}
EventTotal = 1;
while(TRUE)
{
// Accept inbound connections
if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET)
{
printf("accept failed with error %d\n", WSAGetLastError());
return;
}
EnterCriticalSection(&CriticalSection);
// Create a socket information structure to associate with the accepted socket.
if ((SocketArray[EventTotal] = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return;
}
// Fill in the details of our accepted socket.
SocketArray[EventTotal]->Socket = AcceptSocket;
ZeroMemory(&(SocketArray[EventTotal]->Overlapped), sizeof(OVERLAPPED));
SocketArray[EventTotal]->BytesSEND = 0;
SocketArray[EventTotal]->BytesRECV = 0;
SocketArray[EventTotal]->DataBuf.len = DATA_BUFSIZE;
SocketArray[EventTotal]->DataBuf.buf = SocketArray[EventTotal]->Buffer;
if ((SocketArray[EventTotal]->Overlapped.hEvent = EventArray[EventTotal] =
WSACreateEvent()) == WSA_INVALID_EVENT)
{
printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());
return;
}
// Post a WSARecv request to to begin receiving data on the socket
Flags = 0;
if (WSARecv(SocketArray[EventTotal]->Socket,
&(SocketArray[EventTotal]->DataBuf), 1, &RecvBytes, &Flags,
&(SocketArray[EventTotal]->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return;
}
}
EventTotal++;
LeaveCriticalSection(&CriticalSection);
//
// Signal the first event in the event array to tell the worker thread to
// service an additional event in the event array
//
if (WSASetEvent(EventArray[0]) == FALSE)
{
printf("WSASetEvent failed with error %d\n", WSAGetLastError());
return;
}
}
}
DWORD WINAPI ProcessIO(LPVOID lpParameter)
{
DWORD Index;
DWORD Flags;
LPSOCKET_INFORMATION SI;
DWORD BytesTransferred;
DWORD i;
DWORD RecvBytes, SendBytes;
// Process asynchronous WSASend, WSARecv requests.
while(TRUE)
{
if ((Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE,
WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED)
{
printf("WSAWaitForMultipleEvents failed %d\n", WSAGetLastError());
return 0;
}
// If the event triggered was zero then a connection attempt was made
// on our listening socket.
if ((Index - WSA_WAIT_EVENT_0) == 0)
{
WSAResetEvent(EventArray[0]);
continue;
}
SI = SocketArray[Index - WSA_WAIT_EVENT_0];
WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
if (WSAGetOverlappedResult(SI->Socket, &(SI->Overlapped), &BytesTransferred,
FALSE, &Flags) == FALSE || BytesTransferred == 0)
{
printf("Closing socket %d\n", SI->Socket);
if (closesocket(SI->Socket) == SOCKET_ERROR)
{
printf("closesocket() failed with error %d\n", WSAGetLastError());
}
GlobalFree(SI);
WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
// Cleanup SocketArray and EventArray by removing the socket event handle
// and socket information structure if they are not at the end of the
// arrays.
EnterCriticalSection(&CriticalSection);
if ((Index - WSA_WAIT_EVENT_0) + 1 != EventTotal)
for (i = Index - WSA_WAIT_EVENT_0; i < EventTotal; i++)
{
EventArray[i] = EventArray[i + 1];
SocketArray[i] = SocketArray[i + 1];
}
EventTotal--;
LeaveCriticalSection(&CriticalSection);
continue;
}
// Check to see if the BytesRECV field equals zero. If this is so, then
// this means a WSARecv call just completed so update the BytesRECV field
// with the BytesTransferred value from the completed WSARecv() call.
if (SI->BytesRECV == 0)
{
SI->BytesRECV = BytesTransferred;
SI->BytesSEND = 0;
}
else
{
SI->BytesSEND += BytesTransferred;
}
if (SI->BytesRECV > SI->BytesSEND)
{
// Post another WSASend() request.
// Since WSASend() is not gauranteed to send all of the bytes requested,
// continue posting WSASend() calls until all received bytes are sent.
ZeroMemory(&(SI->Overlapped), sizeof(WSAOVERLAPPED));
SI->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
SI->DataBuf.buf = SI->Buffer + SI->BytesSEND;
SI->DataBuf.len = SI->BytesRECV - SI->BytesSEND;
if (WSASend(SI->Socket, &(SI->DataBuf), 1, &SendBytes, 0,
&(SI->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
else
{
SI->BytesRECV = 0;
// Now that there are no more bytes to send post another WSARecv() request.
Flags = 0;
ZeroMemory(&(SI->Overlapped), sizeof(WSAOVERLAPPED));
SI->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
SI->DataBuf.len = DATA_BUFSIZE;
SI->DataBuf.buf = SI->Buffer;
if (WSARecv(SI->Socket, &(SI->DataBuf), 1, &RecvBytes, &Flags,
&(SI->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
}
}