windows Socket编程之重叠IO模型

来源:互联网 发布:联通小米3支持4g网络吗 编辑:程序博客网 时间:2024/04/19 08:41

上一篇文章我们讲了EventSelect网络模型,它已经解决了等待数据到来的这一大部分时间,但是它还有一小部分时间没有节省下来。那就是把数据从网卡的缓冲区拷贝到我们应用程序的缓冲区里边。而这一篇的重叠IO模型就是将这一小部分的时间也给节省了下来。

首先,我们在主线程里边初始化网络环境,然后创建监听的socket,接下来,执行绑定,监听的操作,然后,创建一个工作者线程来对客户进行服务。执行以上操作之后呢,是一个死循环。在这个循环里边,我们首先调用accept函数来对一个客户进行连接操作。然后将该函数返回的客户端的socket保存到我们定义的一个全局socket数组里边进去。然后对我们自定义的结构体单IO操作分配一个空间,其声明如下:

typedef struct{   WSAOVERLAPPED overlap;<span style="white-space:pre"></span>//OVERLAPPED结构,该结构里边有一个event事件对象   WSABUF Buffer;<span style="white-space:pre"></span>//WSABUF结构,里边有一个buf的大小,还有一个指针指向buf   char szMessage[MSGSIZE];<span style="white-space:pre"></span>//消息数据   DWORD NumberOfBytesRecvd;<span style="white-space:pre"></span>//保存接收到的字节数   DWORD Flags;<span style="white-space:pre"></span>//标志位}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
注意,该结构体是我们自己定义的,其中第一个参数overlapped结构体是最重要的,必须把它放在第一个声明处。因为我们要的是overlapped的地址,而其它的则是我们自己进行扩展的,大家也可以进行自己的扩展。那什么叫做单IO操作呢,比如说你请了一个人来专门打理你的店铺,你每天呢都会发一个信息给这个人,信息的内容就是叫他每天干一些指定的的事情,而这个信息就是我们这个单IO操作这个数据结构所指定的内容。

我们在堆上给我们的单IO结构分配完空间之后呢,我们来对它进行初始化,我们把Buffer里面的指针指向szMessage,把里面的大小指定为szMessage的大小。然后,调用WSACreateEvent创建一个事件对象,将这个事件对象赋予给overlappped里边的事件对象。

最后,在循环的末尾我们调用WSARecv,来对数据进行接收,其声明如下:

int WSARecv(    SOCKET s,                                               <span style="white-space:pre"></span>//客户连接的socket  LPWSABUF lpBuffers,                                     <span style="white-space:pre"></span>//WSABUFFER指针  DWORD dwBufferCount,                                    <span style="white-space:pre"></span>//Buffer的个数,一般这里给个1  LPDWORD lpNumberOfBytesRecvd,                           <span style="white-space:pre"></span>//接收的字节数  LPDWORD lpFlags,                                        <span style="white-space:pre"></span>//标志位  LPWSAOVERLAPPED lpOverlapped,                           <span style="white-space:pre"></span>//overlaopped结构地址  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine  <span style="white-space:pre"></span>//没啥用,为NULL);
注意,该函数是异步的,也就是说它调用完就直接进行返回了,不用等待。所以整个循环,只有刚开始接收到一个客户的连接之后,我们就畅通无阻的往下执行一遍,然后再回到循环的开始继续等待客户的连接。接下来,看下工作者线程。

工作者线程,也是一个死循环,它和我们的EventSelect一样,刚开始也是调用了WSAWaitForMultipleEvents,来监控我们的socket数组哪一个有信号了,由于这个函数最多只能监控64个socket,所以我们的服务端只能同时进行64个客户的数据收发。调用完该函数之后,它会返回一个索引值,我们将调用WSAResetEvent,将我们全局event数组里边那个有信号的手动重置为无信号状态。因为我们用WSACreateEvent创建的事件对象是以手动重置的方式创建的。如果,不重置成无信号的状态,那么就像上面我们举得那个例子一样,我们请的那个人,他第二天查看信息的时候,还会继续的执行昨天的工作。

接下来,是我们重叠IO模型里边和EventSelect里边最大的不同点,我们会调用WSAGetOverlappedResult,来判断重叠IO调用是否成功,其声明如下:

BOOL WSAGetOverlappedResult(    SOCKET s,                      //有信号的那个socket  LPWSAOVERLAPPED lpOverlapped,  //overlapped结构地址  LPDWORD lpcbTransfer,          //接收的字节数  BOOL fWait,                    //TRUE表示操作完成就返回  LPDWORD lpdwFlags              //标志位);
这个函数的第三个参数和我们的WSARecv的第四个参数是一样的,操作系统会改写这个值,若该值为0表示客户端断开连接或该数据传输失败了。如果没有失败,我们就将数据保存到我们那个单IO结构里边的szMessage数组里边。然后再次调用WSARecv,告诉操作系统继续帮我们监控这个socket。
以下是重叠IO的实例代码:

#include <winsock2.h>#include <stdio.h>#define PORT 6000#define MSGSIZE 1024#pragma comment (lib, "Ws2_32.lib")BOOL WinSockInit(){WSADATA data = {0};if(WSAStartup(MAKEWORD(2, 2), &data))return FALSE;if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){WSACleanup();return FALSE;}return TRUE;}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(){SOCKET sListen, sClient;SOCKADDR_IN local, client;DWORD dwThreadId;int iaddrSize = sizeof(SOCKADDR_IN);// 初始化环境WinSockInit();// 创建监听socketsListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 绑定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(sListen, 3);// 创建工作者线程CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);while (TRUE){    // 接受连接    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));    g_CliSocketArr[g_iTotalConn] = sClient;    // 分配一个单io操作数据结构    g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(    GetProcessHeap(),    HEAP_ZERO_MEMORY,    sizeof(PER_IO_OPERATION_DATA));    //初始化单io结构    g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;    g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;    g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();    // 开始一个异步操作    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)Cleanup(index);//关闭客户端连接else{//将数据保存到szMessage里边g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';//这里直接就转发回去了send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,cbTransferred, 0);// 进行另一个异步操作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;}

0 0
原创粉丝点击