一个重叠I / O请求最终完成后,我们的应用程序要负责取回重叠I / O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中, Wi n s o c k会更改与一个W S A O V E R L A P P E D结构对应的一个事件对象的事件传信状态,将其从“未传信”变成“已传信”。 由于一个事件对象
已分配给W S A O V E R L A P P E D结构,所以只需简单地调用W S AWa i t F o r M u l t i p l e E v e n t s函数,从而判断出一个重叠I / O调用在什么时候完成。
注意: W S AWa i t F o r M u l t i p l e E v e n t s返回只是说明重叠IO操作完成,但是是成功的完成还是失败的完成还要调用W S A G e t O v e r l a p p e dR e s u l t(取得重叠结构)函数
如W S A G e t O v e r l a p p e d R e s u l t函数调用成功,返回值就是T R U E。这意味着我们的重叠I / O操作已成功完成,而且由l p c b Tr a n s f e r参数指向的值已进行了更新。
我们向大家阐述了如何编制一个简单的服务器应用,令其在一个套接字上对重叠I / O操作进行管理,程序完全利用了前述的事件通知机制。对该程序采用的编程步骤总结如下:
1) 创建一个套接字,开始在指定的端口上监听连接请求。
2) 接受一个进入的连接请求。
3) 为接受的套接字新建一个W S A O V E R L A P P E D结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由W S AWa i t F o r M u l t i p l e E v e n t s函数使用。
4) 在套接字上投递一个异步W S A R e c v请求,指定参数为W S A O V E R L A P P E D结构。
注意函数通常会以失败告终,返回S O C K E T _ E R R O R错误状态W S A _ I O _ P E N D I N G
(I/O操作尚未完成)。
5) 使用步骤3 )的事件数组,调用W S AWa i t F o r M u l t i p l e E v e n t s函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6) WSAWa i t F o r M u l t i p l e E v e n t s函数完成后,针对事件数组,调用W S A R e s e t E v e n t(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7) 使用W S A G e t O v e r l a p p e d R e s u l t函数,判断重叠调用的返回状态是什么。
8) 在套接字上投递另一个重叠W S A R e c v请求。
9) 重复步骤5 ) ~ 8 )。
点击(此处)折叠或打开
- #include "stdafx.h"
- #include <winsock2.h>
- #define DATA_BUFSIZE 4096
- #pragma comment(lib, "ws2_32.lib")
- int _tmain(int argc, _TCHAR* argv[])
- {
- DWORD EventTotal = 0,RecvBytes = 0, Flags = 0;
- char buffer[DATA_BUFSIZE];
- WSABUF DataBuf;
- WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
- WSAOVERLAPPED AcceptOverlapped;
- SOCKET Listen,Accept;
- //step1:
- //start Winsock and set up a listening socket
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2,2), &wsaData);
- ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- u_short port = 27015;
- char* ip;
- sockaddr_in service;
- service.sin_family = AF_INET;
- service.sin_port = htons(port);
- hostent* thisHost;
- thisHost = gethostbyname("");
- ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
- service.sin_addr.s_addr = inet_addr(ip);
- //-----------------------------------------
- // Bind the listening socket to the local IP address
- // and port number
- bind(ListenSocket, (SOCKADDR *) &service, sizeof(SOCKADDR));
- //-----------------------------------------
- // Set the socket to listen for incoming
- // connection requests
- listen(ListenSocket, 1);
- printf("Listening
");
- //step2:
- Accept=accept(Listen,NULL,NULL);
- //step3:
- //set up an overlapped structure
- EventArray[EventTotal]=WSACreateEvent();//先存到事件数组中
- ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
- AcceptOverlapped.hEvent = EventArray[EventTotal];
- DataBuf.len = DATA_BUFSIZE;
- DataBuf.buf =buffer;
- EventTotal++;
- //step4:
- //投递WSARecv准备在Accept套接字上接收数据
- WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);
- while (TRUE){
- //step5:
- //等待overlapped IO调用的完成
- DWORD Index;
- Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
- //step6:
- // Reset the signaled event
- WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
- //step7:
- // Determine the status of the overlapped event
- WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);
- // If the connection has been closed, close the accepted socket
- if (BytesTransferred == 0) {
- printf("Closing Socket %d
", AcceptSocket);
- closesocket(AcceptSocket);
- WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
- return;
- }
- // If data has been received,do something with received data
- //DataBuf contains the received data
- //step8:
- //post another WSARecv () request on the socket
- Flag=0;
- ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
- AcceptOverlapped.hEvent=EventArray[Index - WSA_WAIT_EVENT_0];//该事件对象已经被复位
- DataBuf.len = DATA_BUFSIZE;
- DataBuf.buf =buffer;
- WSARecv(Accept,&DataBuf,1,&RecvBytes,&Flag,&AcceptOverlapped,NULL);
- }
- return 0;
- }
改进后的程序(并非实际可以运行的程序,只是为了理清思路):
点击(此处)折叠或打开
- #define LISTEN_PORT 5000
- #define DATA_BUFSIZE 8192
- #define POST_RECV 0X01
- #define POST_SEND 0X02
- int main( )
- {
- LPPER_HANDLE_DATA lpPerHandleData;
- SOCKET hListenSocket;
- SOCKET hClientSocket;
- SOCKADDR_IN ClientAddr;
- int nAddrLen;
- HANDLE hThread;
- // Start WinSock and create a listen socket.
- listen(hListenSocket, 5);
- for (; ;)
- {
- nAddrLen = sizeof(SOCKADDR);
- hClientSocket = accept(hListenSocket, (LPSOCKADDR)&ClientAddr, &nAddrLen);
- lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
- lpPerHandleData->hSocket = hClientSocket;
- // 注意这里将连接的客户端的IP地址,保存到了lpPerHandleData字段中了
- strcpy(lpPerHandleData->szClientIP, inet_ntoa(ClientAddr.sin_addr));
- // 为本次客户请求产生子线程
- hThread = CreateThread(
- NULL,
- 0,
- OverlappedThread,
- lpPerHandleData, // 将lpPerHandleData传给子线程
- 0,
- NULL
- );
- CloseHandle(hThread);
- }
- return 0;
- }
- DWORD WINAPI OverlappedThread(LPVOID lpParam)
- {
- LPPER_HANDLE_DATA lpPerHandleData = (LPPER_HANDLE_DATA)lpParam;
- WSAOVERLAPPED Overlapped;
- WSABUF wsaBuf;
- WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];//事件对象数组
- DWORD dwEventTotal = 0, // 程序中事件的总数
- char Buffer[DATA_BUFSIZE];
- BOOL bResult;
- int nResult;
- EventArray[dwEventTotal] = WSACreateEvent();
- ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
- Overlapped.hEvent = EventArray[dwEventTotal]; // 关联事件
- ZeroMemory(Buffer, DATA_BUFSIZE);
- wsaBuf.buf = Buffer;
- wsaBuf.len = sizeof(Buffer);
-
- lpPerHandleData->nOperateType = POST_RECV; // 记录本次操作是Recv(..)
- dwEventTotal ++; // 总数加一
- dwFlags = 0;
- nResult = WSARecv(
- lpPerHandleData->hSocket, // Receive socket
- &wsaBuf, // 指向WSABUF结构的指针
- 1, // WSABUF数组的个数
- &dwNumOfBytesRecved, // 存放当WSARecv完成后所接收到的字节数,实际接收到的字节数
- &dwFlags, // A pointer to flags
- &Overlapped, // A pointer to a WSAOVERLAPPED structure
- NULL // A pointer to the completion routine,this is NULL
- );
- if ( nResult == SOCKET_ERROR && GetLastError() != WSA_IO_PENDING)
- {
- printf("WSARecv(..) failed.
");
- free(lpPerHandleData);
- closesocket(lpPerHandleData->hSocket;
- WSACloseEvent(EventArray[dwEventTotal]);
- return -1;
- }
- while (TRUE)
- {
- DWORD dwIndex;
- dwIndex = WSAWaitForMultipleEvents(dwEventTotal, EventArray ,
- FALSE ,WSA_INFINITE,FALSE);
- WSAResetEvent(EventArray[dwIndex– WSA_WAIT_EVENT_0]);
- bResult = WSAGetOverlappedResult(
- lpPerHandleData->hSocket,
- &Overlapped,
- &dwBytesTransferred, // 当一个同步I/O完成后,接收到的字节数
- TRUE, // 等待I/O操作的完成
- &dwFlags
- );
- if (bResult == FALSE && WSAGetLastError() != WSA_IO_INCOMPLETE)
- {
- printf("WSAGetOverlappdResult(..) failed.
");
- free(lpPerHandleData);
- return 0; // 错误退出
- }
- if (dwBytesTransferred == 0)
- {
- printf("客户端已退出,将断开与之的连接!
");
- closesocket(lpPerHandleData->hSocket);
- free(lpPerHandleData);
- break;
- }
- // 在这里将接收到的数据进行处理
- printf("Received from IP: %s.
data: %s
", lpPerHandleData->szClientIP, wsaBuf.buf);
- // 发送另外一个请求操作
- ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
- lpPerHandleData->nOperateType = POST_RECV;
- dwFlags = 0;
- nResult = WSARecv();
- if (){}
- }
- return 1;
- }
最后的一个改进版本,看上去是个不错的版本,相对来说算是比较实用的。但是使用了过多的全局变量,代码是C风格的,不可取。
点击(此处)折叠或打开
- #include <winsock2.h>
- #include <stdio.h>
- #define PORT 5150
- #define MSGSIZE 1024
- #pragma comment(lib, "ws2_32.lib")
- typedef struct
- {
- WSAOVERLAPPED overlap;
- WSABUF Buffer;
- char szMessage[MSGSIZE];
- DWORD NumberOfBytesRecvd;
- DWORD Flags;
- }PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
- int g_iTotalConn = 0;
- 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);
- void Cleanup(int);
- int main()
- {
- WSADATA wsaData;
- SOCKET sListen, sClient;
- SOCKADDR_IN local, client;
- DWORD dwThreadId;
- int iaddrSize = sizeof(SOCKADDR_IN);
- // Initialize Windows Socket library
- WSAStartup(0x0202, &wsaData);
- // Create listening socket
- sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- // Bind
- local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
- local.sin_family = AF_INET;
- local.sin_port = htons(PORT);
- bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
- // Listen
- listen(sListen, 3);
- //网上的代码如此,好奇怪,为什么是在连接还没开始的时候就创建一个线程,而不是像上面的程序一样,accept一个
- //connection创建一个线程,并将从connection 的socket中获取的信息当作形参传递给workThread?
- //这里是只创建一个线程为重叠socket I/O操作服务
- // Create worker thread
- CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
- while (TRUE)
- {
- // Accept a connection
- sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
- //这很好,在accept函数中的后两个形参中获取client的相关信息,并直接在server端的控制台中显示出来
- printf("Accepted client:%s:%d
", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
- g_CliSocketArr[g_iTotalConn] = sClient;//添加到socket数组中
-
- // Allocate a PER_IO_OPERATION_DATA structure
- g_pPerIODataArr[g_iTotalConn] = ER(LPPER_IO_OPATION_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]->eszMssage;
- g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
- // Launch an asynchronous operation
- WSARecv(
- g_CliSocketArr[g_iTotalConn],
- &g_pPerIODataArr[g_iTotalConn]->Buffer,
- 1,
- &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
- &g_pPerIODataArr[g_iTotalConn]->Flags,
- &g_pPerIODataArr[g_iTotalConn]->overlap,
- NULL);
-
- g_iTotalConn++;
- }
- closesocket(sListen);
- WSACleanup();
- return 0;
- }
- DWORD WINAPI WorkerThread(LPVOID lpParam)
- {
- int ret, index;
- DWORD cbTransferred;
- while (TRUE)
- {
- ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
- if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
- {
- continue;
- }
- index = ret - WSA_WAIT_EVENT_0;
- WSAResetEvent(g_CliEventArr[index]);
- WSAGetOverlappedResult(
- g_CliSocketArr[index],
- &g_pPerIODataArr[index]->overlap,
- &cbTransferred,
- TRUE,
- &g_pPerIODataArr[g_iTotalConn]->Flags);
- if (cbTransferred == 0)
- {
- // The connection was closed by client
- Cleanup(index);
- }
- else
- {
- // g_pPerIODataArr[index]->szMessage contains the received data
- g_pPerIODataArr[index]->szMessage[cbTransferred] = '0';
- send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,
- cbTransferred, 0);
- // Launch another asynchronous operation
- WSARecv(
- g_CliSocketArr[index],
- &g_pPerIODataArr[index]->Buffer,
- 1,
- &g_pPerIODataArr[index]->NumberOfBytesRecvd,
- &g_pPerIODataArr[index]->Flags,
- &g_pPerIODataArr[index]->overlap,
- NULL);
- }
- }
- return 0;
- }
- void Cleanup(int index)
- {
- closesocket(g_CliSocketArr[index]);
- WSACloseEvent(g_CliEventArr[index]);
- HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
- if (index < g_iTotalConn - 1)
- {
- g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
- g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
- g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
- }
- g_pPerIODataArr[--g_iTotalConn] = NULL;
- }
这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的hEvent被Signaled。由于下面这个语句g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;
使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。我们现在应该做的就是与调用WSARecv相同的WSAOVERLAPPED结构为参数调用WSAGetOverlappedResult,从而得到本次I/O传送的字节数等相关信息。在取得接收的数据后,把数据原封不动的发送到客户端,然后重新激活一个WSARecv异步操作。