windows Socket编程之重叠IO模型

    xiaoxiao2025-02-06  12

    上一篇文章我们讲了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(); // 创建监听socket sListen = 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; }

    转载请注明原文地址: https://ju.6miu.com/read-1296169.html
    最新回复(0)