为啥完成端口是win

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为啥完成端口是win相关的知识,希望对你有一定的参考价值。

参考技术A 1. 完成端口的实质
个人感觉完成端口就是一个“闹钟”,它可以被当做任何事情的提醒设备。比如说,(没有试过)ReadFile可以异步操作,可以将这个异步操作的完成这个事件的提醒交给完成端口来完成。所以,原文中所使用的完成端口,只是将这个“闹钟”用在了网络传输的提醒上。这也就说明了,尽管我们可以使用完成端口来完成各种提醒,但是主框架仍然是Socket那套流程。
为了更清晰地表达,请看下面的描述(从上往下)。这是作者的代码的API调用流程。其实我不太喜欢作者的第三部分的那些流程图,因为其中用了太多的自定义函数而掩盖了底层的API调用。现在我把他还原。
初始化库,WSAStart()
创建完成端口,CreateIoCompletionPort(-1,...);
创建工作线程,CreateThread()
创建listen socket,WSASocket
绑定listen socket 至完成端口,CreateIoCompletionPort(socket, …)
listen socket与本地端口绑定 ,bind()
开始监听,listen()
向listen socket投递accept请求,AcceptEx()
干其他事情或者等待结束命令
通信结束,通知Worker线程们退出,PostQueuedCompletionStatus()

至于Working 线程的做法和原先一样。
可以看到,尽管我们说完成端口的性能是最好的,然而它只是一个时间走得特别准的“闹钟”而已。 网络编程的基本框架还是原来的那套。

2. 关于Accept投递和WSARev投递
整偏博文里我看得最头痛的就是这个“投递”。它到底是什么?
刚才说了,这里只是把完成端口作为“提醒”机制。具体来说,就是listen之后,通常的调用是如下面所示的
While(1)

Socket = Accept();
_beginthreadex(…); // 传入socket


只不过,这样的调用就是阻塞型调用。而使用完成端口的话,就是将这个Accept(当然,源代码中使用更复杂的AcceptEx,其实差不多的)的调用(其实在内部,就相当于一个要求被accept的请求)委托给完成端口,然后主线程马上返回,并且让后者来提醒之前已经创建好的、在线程函数中调用了GetQueuedCompletionStatus函数的某个线程。当然,至于是哪个线程拿到了,那就是系统调度问题,这对我们来说是黑盒的。这就是那个AcceptEx投递的含义了。说得通俗点,所谓的Accept投递,就是告诉完成端口,“我——listen socket,向你提交一个请求,如果你有客户端的连结请求了,请立即告诉我”。所以可以说,AcceptEx投递就是listen socket的一个IO请求而已。
同样的道理,如果我们不是让完成端口去通知Accept完成,而是去让它通知Receive操作完成,那就是原文中的“投递WSARev操作”了。显然,这是两种操作。

3. 关于工作线程中的一些关键点
那么作者在“详细”那一节第六步中说的那个难点是怎么回事呢?我的理解是这样的。负责监听完成端口的线程(就是之前已经创建好的、在线程函数中调用了GetQueuedCompletionStatus函数的某个线程),它们是在GetQueuedCompletionStatus返回之前是不知道到底是那个操作被“闹钟”了,再加上之前说过的,具体是哪个线程被“闹钟”是我们所不知道的。所以说,你根本就无法判断某个线程会被用做干嘛。所以就需要一种机制来分别了。作者采用的方法是,自定义一些说明性的信息,然后在投递的时候附带送过来,这样在接受的时候就可以通过这些附加信息加以区别了。这就是那个PER_SOCKET_CONTEXT和PER_IO_CONTEXT的作用了。

具体来说,如果是为投递Accept而准备的闹钟,那么某个线程GetQueuedCompletionStatus返回之后,将会获得和Accept投递有关的Socket和此请求的附加信息。和accept投递有关的socket是哪个呢?自然是用于做listen的那个socket(GetQueuedCompletionStatus函数的第三个参数返回它的上下文——其实就是一个和它相匹配的补充性结构,那是在将socket和完成端口绑定时传入的第三个参数)。
注意到一个socket上面可以有很多个请求(类比一个客户端/服务器连结上可以发送多次数据,每一次发送数据都需要通过一个“发送数据请求”),从而在listen socket上面,我们之前投递了多个acceptEx请求,那么到底是哪个请求到来了呢?其实对于AcceptEx请求而言,哪一个并不那么重要,反正大家的工作都是创建一个客户端/服务器连结socket,然后进行通信。然而如果真的重要了,那么就需要这个值了(比如说不同连结需要不同操作之类)。具体来说,GetQueuedCompletionStatus
倒数第二个参数OVERLAPPED*,返回的就是和这个请求相关的信息。它其实就是我们在投递这个请求的时候所附加给完成端口的说明性材料。注意看,这是一个OVERLAPPED结构指针,应该和我们自定义的PER_IO_CONTEXT没有关系。实则不然,回顾CIOCPModel::_PostAccept里面AcceptEx调用的最后一个参数实际上是,&pAcceptIoContext->m_Overlapped。也就是说这里传回来的是PER_IO_CONTEXT的第一个成员m_Overlapped的地址。通过位于_WorkThread函数中的风骚宏CONTAINING_RECORD
就能够得到这个PER_IO_CONTEXT的首地址啦。
另外,如果投递的是WSARev请求呢?这个附加信息怎么穿进去?一样的啦,就是通过WSARev函数的OVERLAPPED结构体参数。这个结构体的实际作用,就是在异步IO操作之后,提供给你一些信息的。我们可以通过继承这个结构(或者和这里的做法一致——采用风骚宏CONTAINING_RECORD)来拓展它,让它包含我们需要的信息。
接下去的事情就好办了,根据自定义结构PER_IO_CONTEXT的成员m_OpType了解一些信息,知道这个回应,到底是listen socket的accept请求的回应(ACCEPT_POSTED)还是真正的客户端/服务器连结socket的发送或者接收请求的回应(RECV_POSTED和SEND_POSTED)。

如果是对accept请求的回应,那么返回的socket context和io context都是这个accept请求的,所以作者强调在_DoAccept中是一定不能够动这两个结构的。然而,如果是普通连结的发送/接收请求的回应,那么当然就是另外一回事情了。

我用的WIN10的系统,防火墙添加了特定端口入站规则,为啥端口还是没有开放

我用的WIN10的系统,防火墙添加了特定端口入站规则,为什么端口还是没有开放

操作步骤:
1、WIN+X调出系统配置菜单,选择控制面板;
2、选择windows 防火墙;
3、点击左侧的“高级设置”选项;
4、设置入站规则(入站规则:别人电脑访问自己电脑;出站规则:自己电脑访问别人电脑),点击“新建规则”,点选“端口”,单击 “下一步”;
5、选择相应的协议,如添加8080端口,我们选择TCP,在我写本地端口处输入8080;
6、选择“允许连接”,点击“下一步”;
7、勾选“域”,“专用”,“公司”,点击“下一步”;
8、输入端口名称,点“完成”即可。
9、具体效果如下,已经在防火墙里开放单独端口。
参考技术A 和楼主遇到了同样问题,win10系统。先在入站规则里开放了端口,试了试不行;又把公用网络、专用网络、域网络防火墙都关了,通过局域网访问也还是拒绝访问。楼主解决了吗,能否分享一下经验。
已修改:貌似我重启电脑又行了。也不用再关防火墙了,只添加了入站规则。楼主可以重启试试。

以上是关于为啥完成端口是win的主要内容,如果未能解决你的问题,请参考以下文章

Win32 IO 完成端口和同步出现的 IO 会发生啥?

为啥我的 IOCompetionCallback 从未在我的 IO 完成端口上执行?

WINSOCK.07.完成端口模型

IO 完成端口和重叠管理

开启win10防火墙开启某端口

WinSock 异步I/O模型-4