重叠IO
2016-05-14 23:42
375 查看
#include <winsock2.h> #include <windows.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; //第一个成员。。。重叠IO,必须要用OVERLAPPED WSABUF Buffer; //发送长度或者接受的时候允许的最大长度 char szMessage[MSGSIZE]; DWORD NumberOfBytesRecvd; //保存接收到的字节数 DWORD Flags; //保存操作类型 收 OR 发 }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 _tmain() { SOCKET sListen, sClient; SOCKADDR_IN local, client; DWORD dwThreadId; int iaddrSize = sizeof(SOCKADDR_IN); // 初始化windows socket库 WinSockInit(); // 创建监听socket sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //默认设置 WSA_FLAG_OVERLAPPED // 绑定 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)); 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); //WSARecv的参数中都有一个 Overlapped 参数,我们可以假设是把我们的WSARecv这样的操作“绑定”到这个重叠结构上, //提交一个请求,而不是将操作立即完成,其他的事情就交给重叠结构去做,而其中重叠结构又要与Windows的事件对象“绑定”在一起, // 这样我们调用完 WSARecv 以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成, //然后我们就可以来根据重叠操作的结果取得我们想要的数据了。 g_iTotalConn++; } closesocket(sListen); WSACleanup(); return 0; } DWORD WINAPI WorkerThread(LPVOID lpParam) { int ret, index; DWORD cbTransferred; while (TRUE) { //判断出一个重叠 I/O 调用是否完成 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 { // g_pPerIODataArr[index]->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; }
首先需要初始化网络环境,然后创建监听socket,绑定,监听,然后死循环,accpet,接受连接,代码中g_CliSocketArr是一个SOCKET数组,用来保存客户端连接的socket,g_iTotalConn是用来保存连接的数量,接下来我们创建了一个结构体LPPER_IO_OPERATION_DATA,简单看一下结构体的成员,第一个参数是WSAOVERLAPPED,这个是重叠IO的核心,必须要使用,因为其结构中的WSAEVENT会在后面使用到,第二个参数是发送长度或者接受的时候允许的最大长度,第三个是保存的字符szBuff,第四个是保存接收到的字节数,第五个是一个标签,用来表明此IO操作时接收还是发送。
然后HeapAlloc在栈上申请一片内存,其次把结构体中的成员进行赋值,这里要注意,g_CliEventArr是一个WSAEVENT,参见EventSelect
,然后需要把g_pPerIODataArr中的overlap.hEvent这个成员事件初始化,也就是WSACreatEvent这个函数,把信号量变成无。
接下来是一个异步操作,类似图中所说,我只需要接听,然后剩下的事情都交给“秘书”操办即可,我只用接电话就可以了,这里的“秘书”就是操作系统
这里要使用一个WSARecv函数,这个函数并不是阻塞函数,他会立即执行然后继续完成下面的代码,这个函数的第一个参数是SOCKET,客户端的Socket,第二个是一个LPWSABuff的参数,传入g_pPerIODataArr,第三个参数是有多少个WSABuff,给个1即可,第四个参数是接收到的Buffer长度,传入g_pPerIODataArr的NumberOfBytesRecvd这个成员,第五个参数是Flags标签,第六个很重要是一个LPWSAOVERLAPPED类型,我们需要传入overlap,第七个传入NULL,然后g_iTotalConn++,开始下一个循环。
我们在循环之前创建了一个工作线程,那么工作线程中 第一个函数WSAWaitForMultipleEvents,等待信号,当主线程收到信号后,这里的就会接收到,然后返回数组中是哪一个位置发生的信号,这个时候需要手动将信号设为无信号,也就是WSAResetEvent,如果不设置,会导致下一次接受的时候是有信号的,出现BUG,然后我们使用WSAGetOverlapperdResult这个函数,返回网络事件的结果,第一个参数是SOCKET,第二个是LPWSAOVERLAPPED,传入overlap,第三个是接收到的长度,第四个参数是BOOL型,TRUE不返回,如果FALSE,可以用WASGetLastError返回错误,第五个是标签,没什么用。
然后判断一下,如果接收到的数据长度为0,关闭客户端连接,否则,这里 g_pPerIODataArr[index]->szMessage[cbTransferred] 保存了接收到的数据,在这个数据最后加一个’\0’,然后你可以做你想做的事情,比如发送数据包给子客户端,这里不需要在使用异步操作,直接使用Send发送即可,最后还需要使用一个WSARecv函数,其中的参数和主线程一样,这里必须要是进行一次异步操作,不然会导致主线程的异步操作出错,最后Cleanup,清空处理。
总体来说重叠IO的模型已经将能简化的简化到最低底线了。就相当于老板和小秘,老板只需要接电话打电话告诉秘书,至于做的事情就是LPPER_IO_OPERATION_DATA ,告诉了小秘那么要做的事情就直接让小秘去做,就可以了。
重叠IO的缺点:
工作者线程与监听者线程之间的互斥。
工作者线程与工作者线程之间的同步。
相关文章推荐
- MySQL-5.6.24免安装版配置方法
- LoadLibrary文件路径及windows API相关的文件路径问题
- HOOK学习笔记与心得
- Tab Control控件简单使用
- Windows-核心编程-04-进程-获取进程 线程 模块(DLL)信息
- Windows-核心编程-03-什么是内核对象
- Windows-核心编程-09-如何用内核对象进行线程同步-事件内核对象
- Windows-核心编程-09-如何用内核对象进行线程同步-信号内核对象
- Windows-核心编程-16-什么是内存映射文件
- 深入剖析PE文件 (告诉你exe文件打开后是依据什么来创建进程并在系统中运行)
- 利用MinGW64编译多线程版FFTW
- Windows窗口创建
- C++实现代码雨
- windows编程之文件夹遍历
- windows应用程序开发系列四:宽字符问题详解
- windows应用程序开发系列二:windows窗口应用程序开发的步骤
- windows应用程序开发笔记一:windows应用程序介绍
- Windows编程中关于打开对话框的操作
- C++遍历所有IE浏览器,得到里面的所有元素
- 实现和IE浏览器交互的几种方法的介绍