WinSock 完成端口模型
之前写了关于Winsock的重叠IO模型,按理来说重叠IO模型与之前的模型相比,它的socket即是非阻塞的,也是异步的,它基本上性能非常高,但是它主要的缺点在于,即使我们使用历程来处理完成通知,但是我们知道历程它本身是在对应线程暂停,它借用当前线程的线程环境来执行完成通知,也就是说要执行完成通知就必须暂停当前线程的工作。这对工作线程来说也是一个不必要的性能浪费,这样我们自然就会想到,另外开辟一个线程来执行完成通知,而本来的线程就不需要暂停,而是一直执行它自身的任务。处于这个思想,WinSock提供了一个新的模型——完成端口模型。
完成端口简介
完成端口本质上是一个线程池的模型,它需要我们创建对应的线程放在那,当完成通知到来时,他会直接执行线程。在这5中模型中它的性能是最高的。
在文件中我们也提到过完成端口,其实我们利用Linux上一切皆文件的思想来考虑这个问题就可以很方便的理解,既然我们需要异步的方式来读写网卡的信息,这与读写文件的方式类似,既然文件中存在完成端口模型,网络上存在也就不足为奇了。
对于完成端口Windows没有引入新的API函数,而是仍然采用文件中一堆相关的函数。可以使用CreateIoCompletionPort来创建完成端口的句柄,该函数原型如下:
HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle,
__in_opt HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);
第一个参数是与完成端口绑定的文件句柄,如果我们要创建完成端口句柄,这个值必须传入INVALID_HANDLE_VALUE。如果是要将文件句柄与完成端口绑定,这个参数必须穿入一个支持完成端口的文件句柄。在Winsock中如果要绑定SOCKET到完成端口只需要将SOCKET强转为HANDLE。
第二个参数是一个已知的完成端口句柄,如果是创建完成端口,这个参数填入NULL。
第三个参数是一个LONG型的指针,它作为一个标志,由完成通知传入完成线程中,用来标识不同的完成通知。一般我们会定义一个扩展来OVERLAPPED结构来标识不同的完成通知,所以这个参数一般不用传入NULL。
第四个参数是同时执行的线程数,如果是绑定文件句柄到完成端口,则这个参数填入0
我们可以在对应的完成线程中调用GetQueuedCompletionStatus函数来获取完成通知,这个函数只有当有IO操作完成时才会返回,函数原型如下:
BOOL WINAPI GetQueuedCompletionStatus(
__in HANDLE CompletionPort,
__out LPDWORD lpNumberOfBytes,
__out PULONG_PTR lpCompletionKey,
__out LPOVERLAPPED* lpOverlapped,
__in DWORD dwMilliseconds
);
它的第一个参数是一个完成端口的句柄。
第二个参数表示当前有多少字节的数据完成IO操作。
第三个参数是一个标记值,用来标识不同文件句柄对应的完成通知,它是通过 CreateIoCompletionPort 函数设置的那个标识。
第四个参数是OVERLAPPED结构。
第五个参数表示等待的时间,如果填入INFINITE则会一直等到有IO操作完成。
完成端口的示例:
下面是一个完成端口的示例
typedef struct _tag_MY_OVERLAPPED
{
OVERLAPPED m_overlapped;
SOCKET m_sClient;
long m_lEvent;
DWORD m_dwNumberOfBytesRecv;
DWORD m_dwFlags;
char *m_pszBuf;
LONG m_dwBufSize;
}MY_OVERLAPPED, *LPMY_OVERLAPPED;
unsigned int __stdcall IOCPThread(LPVOID lpParameter);
#define BUFFER_SIZE 1024
#define SERVER_PORT 6000
int _tmain(int argc, TCHAR *argv)
{
WSADATA wd = {0};
WSAStartup(MAKEWORD(2, 2), &wd);
SYSTEM_INFO si = {0};
GetSystemInfo(&si);
//创建完成端口对象
HANDLE hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, si.dwNumberOfProcessors);
//创建完成端口对应的线程对象
HANDLE *pThreadArray = (HANDLE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * si.dwNumberOfProcessors);
for (int i = 0; i < 2 * si.dwNumberOfProcessors; i++)
{
pThreadArray[i] = (HANDLE)_beginthreadex(NULL, 0, IOCPThread, &hIocp, 0, NULL);
}
SOCKET SrvSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
SOCKADDR_IN SockAddr = {0};
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = htons(SERVER_PORT);
SockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(SrvSocket, (SOCKADDR*)&SockAddr, sizeof(SOCKADDR));
listen(SrvSocket, 5);
SOCKET sClient = accept(SrvSocket, NULL, NULL);
CreateIoCompletionPort((HANDLE)sClient, hIocp, NULL, 0);
WSABUF buf = {0};
buf.buf = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BUFFER_SIZE);
buf.len = BUFFER_SIZE;
MY_OVERLAPPED AcceptOverlapped = {0};
AcceptOverlapped.m_dwBufSize = BUFFER_SIZE;
AcceptOverlapped.m_lEvent = FD_READ;
AcceptOverlapped.m_pszBuf = buf.buf;
AcceptOverlapped.m_sClient = sClient;
WSARecv(sClient, &buf, 1, &AcceptOverlapped.m_dwNumberOfBytesRecv, &AcceptOverlapped.m_dwFlags, &AcceptOverlapped.m_overlapped, NULL);
while (TRUE)
{
int nVirtKey = GetAsyncKeyState(VK_ESCAPE); //用户按下退出键(ESC)
{
break;
}
}
for (int i = 0; i < si.dwNumberOfProcessors * 2; i++)
{
//向IOCP发送FD_CLOSE消息,以便对应线程退出
AcceptOverlapped.m_lEvent = FD_CLOSE;
PostQueuedCompletionStatus(hIocp, si.dwNumberOfProcessors * 2, 0, &AcceptOverlapped.m_overlapped);
}
WaitForMultipleObjects(2 * si.dwNumberOfProcessors, pThreadArray, TRUE, INFINITE);
for (int i = 0; i < si.dwNumberOfProcessors * 2; i++)
{
CloseHandle(pThreadArray[i]);
}
HeapFree(GetProcessHeap(), 0, buf.buf);
shutdown(sClient, SD_BOTH);
closesocket(sClient);
CloseHandle(hIocp);
WSACleanup();
return 0;
}
unsigned int __stdcall IOCPThread(LPVOID lpParameter)
{
HANDLE hIocp = *(HANDLE*)lpParameter;
DWORD dwNumberOfBytes = 0;
MY_OVERLAPPED *lpOverlapped = NULL;
ULONG key = 0;
BOOL bLoop = TRUE;
while (bLoop)
{
BOOL bRet = GetQueuedCompletionStatus(hIocp, &dwNumberOfBytes, &key, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
if (!bRet)
{
continue;
}
switch (lpOverlapped->m_lEvent)
{
case FD_CLOSE: //退出
{
bLoop = FALSE;
printf("线程[%08x]准备退出......\n", GetCurrentThreadId());
}
break;
case FD_WRITE:
{
printf("数据发送完成......\n");
shutdown(lpOverlapped->m_sClient, SD_BOTH);
closesocket(lpOverlapped->m_sClient);
}
break;
case FD_READ:
{
printf("client>%s", lpOverlapped->m_pszBuf);
lpOverlapped->m_lEvent = FD_WRITE;
WSABUF buf = {0};
buf.buf = lpOverlapped->m_pszBuf;
buf.len = dwNumberOfBytes;
lpOverlapped->m_dwFlags = 0;
WSASend(lpOverlapped->m_sClient, &buf, 1, &lpOverlapped->m_dwNumberOfBytesRecv, lpOverlapped->m_dwFlags, &lpOverlapped->m_overlapped, NULL);
}
}
}
return 0;
}
在上述代码中,首先定义了一个结构体用来保存额外的数据。在main函数中首先查询CPU的核数,然后创建这个数目2倍的线程。接着创建一个完成端口对象。然后进行SOCKET的创建、绑定、监听、接收连接的操作。当有连接进来的时候。创建对应的扩展结构并调用WSARecv投递一个接收操作。由于后面的收发操作都在对应的线程中操作,因此在主线程中只需要等待即可。当用户确定退出时。先调用PostQueuedCompletionStatus函数向完成线程中发送完成通知,并将网络事件设置为FD_CLOSE,表示让线程退出。在这里没有使用TerminateThread这种暴力的方式,而选择了一种让线程自动退出的温和的方式。接着进行资源的回收,最后退出。
在线程中,我们首先在循环中调用 GetQueuedCompletionStatus函数来获取完成通知,当发生完成事件时,我们在switch中根据不同的额网络事件来处理,针对FD_CLOSE事件,直接退出线程。针对FD_READ事件,先打印客户端发送的信息,然后调用WSASend将信息原样返回,接着设置网络事件为FD_WRITE,以便断开与客户端的链接。
几种模型的比较
最后针对5种模型和两种socket工作模式来做一个归纳说明。
- 最先学习的是SOCKET的阻塞模式,它的效率最低,它会一直等待有客户端连接或者有数据发送过来才会返回。这就好像我们在等某个人的信,但是不知道这封信什么时候能送到,于是我们在自家门口的收信箱前一直等待,直到有信到来。
- 为了解决这个问题,提出了SOCKET的非阻塞模式,它不会等待连接或者收发数据的操作完成,当我们调用对应的accept或者send、recv时会立即返回,但是我们不知道它什么时候有数据要处理,如果针对每个socket都等待直到有数据到来,那么跟之前的阻塞模式相比没有任何改进,于是就有了socket模式,它会等待多个socket,只要其中有一个有数据就返回,并处理。用收信的模型类比的话,现在我们不用在邮箱前等待了。但是我们会每隔一段时间就去邮箱那看看,有没有信,有信就将它收回否则空手而归。
- 我们说select模型的最大问题在于不知道什么时候有待决的SOCKET,因此我们需要在循环中不停的等待。为了解决这个时机问题,又提出了WSAAsyncSelect模型和WSAEvent模型,它们主要用来解决调用对应函数的时机。用收信的例子类比就是现在我在邮箱上装了一个报警的按钮,只有有信,警报就会响,这个时候我们就去收信。而不用向之前那样每隔一段时间就去邮箱看看
- 我们说解决了时机的问题,但是调用send和recv对网卡进行读写操作仍然是同步的操作,CPU需要傻傻的等着数据从网卡读到内存或者从内存写到网卡上。因此又有了重叠IO的模型和一些列的新的API,向WSARecv和WSASend等等函数。这样就相当于当有信来的警报响起时,我们不需要自己去取信了,另外派了一个人帮我们拿信,这样我们的工作效率又提高了一些。节约了我们的时间
- 重叠IO也有它的问题,如果使用重叠IO的事件模型时,也需要在合适的时候等待,就好像我们虽然派了一个人来帮忙拿信,但是我们自己却需要停下手头上的工作,询问拿信的人回来了。而使用完成历程也存在自己的问题,因为它需要使用主线程的资源来执行历程,它需要主线程暂停下来,这样就可能出现两种情况:1)有通知事件到来,但是并没有进入可警告状态;2)进入可警告状态却没有客户端发送请求。这就相当于可能我们不停的等待但是拿信的那个人却没有回来,或者拿信的人回来了,我们却没有时间处理信件。
- 针对重叠IO的上述问题,提出了完成端口的解决方案,完成事件由对应的线程处理,而主线程只需要专注于它自己的工作就好了,这就相当于警报响了,我们知道信来了,直接派一个人去拿信,后面的我就不管了,而拿信的人把信拿回来的时候将信放好。当我们忙完之后去处理这封信。没忙完的话信就一直放在那,甚至让拿信的人处理这封信,这样就能更高效的集中注意力来处理眼前的工作。
WinSock 完成端口模型的更多相关文章
- Winsock完成端口模型-Delphi代码
原文出处 <Windows网络编程技术>第8章 完成端口模型 由于原书附的是C代码,我把其翻译成Delphi代码. 其中winsock2.pas在delphi中不带,要另外下载http:/ ...
- winsock编程IOCP模型实现代码
winsock编程IOCP模型实现代码 话不多说,上代码.借鉴<windows核心编程>部分源码和CSDN小猪部分代码. stdafx.h依赖头文件: #include <iostr ...
- winsock I/O模型的分析
几种winsock I/O模型的分析 套接字是通信的基础,是支持网络协议数据通信的基本接口.Winsocket 提供了一些有趣的I/O模型,有助于应用程序通过一种“异步”方式,一次对一个或者多个套接字 ...
- winsock的io模型(终极篇)
最近在看服务器框架的搭建,看了不少,都是零零碎碎的,觉得看的差不多了,可以写点最后的总结了,然后,竟然发现了这篇文章,总结做的特别好,肯定比我总结写要好多了,所以我也就不写了,直接转吧...... 套 ...
- winsock编程WSAEventSelect模型
winsock编程WSAEventSelect模型 WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统, ...
- winsock编程WSAAsyncSelect模型
winsock编程WSAAsyncSelect模型 WSAAsyncSelect模型也称异步选择模型,其核心函数是WSAAsyncSelect.它可以用来在一个socket上接收以windows消息为 ...
- winsock编程select模型
winsock编程select模型 网络服务端连接数量过多时,为每一个连接申请一个线程会让机器性能急剧下降(大多说是因为线程在用户态和内核态之间切换会占用大量的CPU时间片).为了解决多线程带来的性能 ...
- WinSock 重叠IO模型
title: WinSock 重叠IO模型 tags: [WinSock 模型, 网络编程, 重叠IO模型] date: 2018-06-29 20:26:13 categories: Windows ...
- WinSock异步IO模型之Select
如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的. Windows操作系统提供了五种I/O模型,分别是: ■ 选择(select): ■ 异步选择(WSAAsyncSelec ...
随机推荐
- 安装vm tools(方便虚拟机复制粘贴切换)
在VMware虚拟机中安装好了VMware Tools,才能实现主机与虚拟机之间的文件共享,同时可支持自由拖拽的功能,鼠标也可在虚拟机与主机之前自由移动(而不再用按ctrl+alT释放),而且还可以令 ...
- 华为敏捷/DevOps实践:产品经理如何开好迭代计划会议
大家好,我是华为云DevCloud项目管理服务的产品经理恒少,作为布道师和产品经理,出差各地接触客户是常态,线下和华为云的客户交流.布道.技术沙龙. 但是线下交流,覆盖的用户总还是少数.我希望借助线上 ...
- go的三个常用命令go run go build go install
go的三个常用命令 go run go build go install 命令源码文件:含有 main函数 的文件 库源码文件:不包含 main函数 的文件, 主要用于编译成静态文件.a供其他包调用 ...
- 瀑布模型,(增量开发)渐增式开发,原型化开发,统一过程模型(RUP)
瀑布模型:设计在开发阶段 瀑布模型有以下优点 1)为项目提供了按阶段划分的检查点. 2)当前一阶段完成后,您只需要去关注后续阶段. 3)可在迭代模型中应用瀑布模型. 增量迭代应用于瀑布模型.迭代1解决 ...
- 洛谷 P3388 【模板】割点(割顶)
题目链接 题解 今天复习了一下割点. 关于\(tarjan\)这里不多讲 \(dfn\)和\(low\)数组的定义想必大家都知道 仔细观察一下,可以发现 假设便利\(u->v\)这条边 如果 \ ...
- HDU 6357.Hills And Valleys-动态规划(区间翻转l,r找最长非递减子序列)
题意:给一串由n个数字组成的字符串,选择其中一个区间进行翻转,要求翻转后该字符串的最长非降子序列长度最长,输出这个最长非降子序列的长度以及翻转的区间的左右端点 #include<bits/std ...
- vue的eventBus
首先在main.js中 Vue.prototype.$eventBus = new Vue() A组件中通过触发事件或者其他什么,然后发射数据 this.$eventBus.$emit('key',' ...
- D. Diverse Garland-----CF字符串
D. Diverse Garland time limit per test 1 second memory limit per test 256 megabytes input standard i ...
- .net core EF Cde First
注意事项记录: public class StudentsModel { /// <summary> /// 一定需要id /// 一般用model名称+id作为表主键 /// 或者直接用 ...
- PIE SDK与Matlab结合说明文档
1.功能简介 Matlab是三大数学软件之一,它在数学类科技应用软件中在数值计算方面首屈一指.Matlab可以进行矩阵运算.绘制函数和数据.实现算法.创建用户界面.连接其他编程语言的程序等,主要应用于 ...