本文对于初学网络编程的极为友好,文中所有代码全部基于C语言实现,文中见解仅限于作者对于完成端口的初步认识,由于作者才疏学浅,出现的错误和纰漏,麻烦您一定要指出来,咱们共同进步。谢谢!!!


完成端口(completion Port)

前言:

网络通信分为两种:同步和异步。

  在同步通信中,每一次接受数据都会导致主线程的挂起,从而阻塞住了其他操作。为了解决这一问题,我们通常会采取同步通信+多线程的策略,即为每一个连入的Socket分配一个线程。然而随着连入的Socket的数量的增加,线程的数量也在增加,这样CPU则需要不停地进行线程的切换,因此难以成为高性能的服务器程序。
  异步通信则可以把接收数据这一操作交给内核,即在内核接收数据的时候,主线程可以不用被阻塞并且继续执行其他操作,而一旦接收数据完成以后,再由内核通知主线程。而如何通知主线程是一个关键,不同的异步通信策略有着不同的通知方式。
  在这样的情况下,完成端口这一I/O模型被提出,成为目前Windows下性能最好的I/O模型之一。

 (注:文中所有函数参数均已MSDN上的为标准,文中观点仅代表个人理解,如有错误,还请多多包涵并及时留言,我会第一时间改正,谢谢!!!)

完成端口模型简介:

上面所说的“初学”指你已经熟悉Socket进行TCP/IP编程的基本原理,前期基本的概念我这里就略过不提了,直入主题。

嗯~~!怎么说呢,完成端口是Windows的一种机制,这种机制是在重叠IO上的优化,所以说完成端口也是基于重叠结构的,换句话说如果对于重叠IO结构特别熟悉的话,那么完成端口对于你来说就特别简单。为什么说完成端口是在重叠IO上的一种优化呢?对比一下下面第一张和第二转张结构图,一定会有人好奇,为什么两张图差不多一样呢?仔细看会发现完成端口结构图里面操作系统有一步操作是将通知放进队列(第三张结构图,模仿消息队列原理系统会创建一个通知队列)。到这就可以说明完成端口在重叠IO具体优化的是什么了,熟悉重叠IO的都知道,重叠IO最严重的问题就是线程数量,有多少的客户端,那就得有多少根线程。肯定会有人说线程多了不是更好吗?速度跟快吗?程序执行时间更短码?那就错了,恰恰是相反的,上面我也大致提到了线程太多的问题。了解操作系统的都知道,线程在一个周期内分得的时间越多,那么执行就越快。换而言之如果线程数量增加,那么每根线程上所分得的时间就会变短,再加上切换线程的时间,这样一来反而时间更久。而理论上最优的线程数就是和CPU核数一样(还有其他的几种:CPU核数*2、CPU核数*2+2。为什么会有这几种情况,这里就不多多介绍了。)这样以来就可以充分的利用CPU资源。不过这也要求线程函数中没有调用诸如Sleep(),WSAWaitForMultipleEvents()...这类函数,这类函数会使线程挂起(但不占cpu时间片),从而使得CPU某个核空闲了,这就不好了,所以一般我们多建个两三根,以解决此类情况,让CPU不停歇,从而在整体上保证程序执行效率。本文采取的是和CPU核数一样多。而对于重叠IO中的无序性问题,完成端口采用了上述所说的创建一个通知队列(第三张结构图)来进行管理,从而达到有序。所以说完成端口是对重叠IO的改进也不为过。

    

完成端口原理以及部分函数用法:

1.CreateIoCompletionPort()函数创建一个完成端口。

对于 CreateIoCompletionPort()函数它有两个功能一个功能是创建完成端口,另一个功能就是将SOCKET与完成端口进行绑定,在这里就是创建完成端口。至于说功能不一样,也就是参数不同而已。

 HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle,
__in_opt HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);

参数(Parameters):

此函数若要是在不关联I/O完成端口的情况下创建I/O完成端口,如果指定了参数FileHandleINVALID_HANDLE_VALUE,在这种情况下,ExistingCompletionPort参数必须为NULL,而CompletionKey参数则被忽略可填0;那么参数NumberOfConcurrentThreads是允许此端口上最多同时运行的线程数量,一般设置为零(这里的零并不是参数3中忽略的意思,而是自动获取CPU核数。当然你也可以不用自动获取自己去指定通过函数GetSystemInfo())。

(注:这里简单介绍一下GetSystemInfo()函数的用法。这个函数也特别简单,参数也就一个SYSTEM_INFO类型的结构体,在这里我们只需要专注这个结构体里面的DWORD dwNumberOfProcessors成员即可; )

返回值(Return value):

函数执行成功会返回一个可用的端口变量,否则返回0;这里可以用GetLastError()获取错误码。
(注意:这里为什么不用WSAGetLastError()获取错误码?创建完成端口是Windows的一种机制,不是专门用于网络的,和网络是无关的。完成端口的模型只是利用了这种机制。)

2.用 CreateIoCompletionPort()函数将重叠套接字(客户端SOCKET+服务器SOCKET)与完成端口进行绑定。

毋庸置疑这就是CreateIoCompletionPort()函数的第二个功能:绑定重叠套接字与完成端口

 HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle,
__in_opt HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);

参数(Parameters):

FileHandle:要绑定的SOCKET。
ExistingCompletionPort:创建完成端口时返回的变量。
CompletionKey:这个参数就要和下面即将讲到的一个函数GetQueuedCompletionStatus()的参数3关联在一起比较着看,会很清楚。
      先大概说一下GetQueuedCompletionStatus()这个函数,上面我也提到过系统会把所有SOCKET上的通知放进通知队列里面,而GetQueuedCompletionStatus()
      函数就是从这个队列里面依次往外拿出通知然后进行分类处理,而CreateIoCompletionPort()函数的参数3就是告知函数GetQueuedCompletionStatus()
      队列里面拿出的事件通知具体是哪一个SOCKET上的发生的。
所以这里的参数就是要传入具体发生事件通知的SOCKET(如果是把所有的SOCKET装进数组里面的话,这里也可以传具体SOCKET的下标)。
NumberOfConcurrentThreads:如果参数ExistingCompletionPort不是NULL,则忽略此参数。可填0。

返回值(Return value):

函数执行成功返回自己,也就是再返回参数2;如果执行不成功那肯定就不等于参数2了啊!

3.使用AcceptEx(),WSARecv(),WSASend()函数投递请求。(这三个异步函数就偷个懒这里不过多的介绍了,因为是直接拿的重叠IO里面的函数,哈哈哈)

4.使用CreateThread()函数创建线程,使用GetSystemInfo()获得操作系统相关信息,比如获取CPU核数。

(GetSystemInfo()函数上文已经大致介绍了一下,和网络也没有太大的关系这里就不详细介绍了,想了解的可以看一下MSDN)

创建线程函数CreateThread()的功能就是一次创建一根线程,如果要创建多根线程,可以用循环

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

参数(Parameters):

lpThreadAttributes:线程句柄是否被继承,不继承就填NULL。如果不继承就是子线程与父线程共享一份线程句柄,相当于全局变量;
          如果继承的话子类复制一份父类的此时就会有两份,相当于局部变量自己用自己的;
          还有一个功能就是指定线程的权限,默认权限就填NULL。
          所以此参数填NULL就好。
dwStackSize:线程大小(栈区大小),填0,默认大小为1M。可以指定大小以字节为单位。
lpStartAddress:线程函数地址;
        线程函数函数头:DWORD WINAPI ThreadProc(LPVOID lpParameter); 这个函数的参数由函数CreateThread()的参数4传入
lpParameter:外部给线程传递数据,把传递进来的数据传递给参数3中的线程函数中;
dwCreationFlags:线程创建出来的一种执行状态;
         立即执行填0,也就是立即获得时间片分得的时间;
         挂起状态填CREATE_SUSPENDED(不占用时间周期)。调用ResumeThread()函数,激活挂起状态的线程。
         如果填STACK_SIZE_PARAM_IS_A_RESERVATION,这个宏是和参数2关联在一起的。如果想修改栈区大小,
         设置了这个宏,参数2就是修改的栈保留大小,即虚拟内存上栈得大小;如果没有设置修改的就是栈提交大小,即物理内存上的大小。
lpThreadId:线程ID,每根线程的ID都不一样。不用就填NULL。

返回值(Return value):

函数执行成功返回线程句柄,失败返回NULL。可以用GetLastError()获得错误码。
线程句柄是内核对象,用完要释放用CloseHandle()函数。

5.当系统异步处理完成后,会生成一个通知,这个通知就会放进通知队列里面,而完成端口就可以理解为通知队列的头。该队列由操作系统系统创建,维护。

6.通过GetQueuedCompletionStatus()函数从队列头一个一个往外拿,进行处理。

如果通知队列里没有通知,那么会使线程处于挂起状态,这样就不会占用CPU时间。 

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED *lpOverlapped,
DWORD dwMilliseconds
);

参数(Parameters):

CompletionPort:创建完成端口时返回的变量。
lpNumberOfBytesTransferred:收到或发送的字节数。如果是客户端SOKCET发生事件通知并且此参数返回的是0,那就说明是客户端退出。
lpCompletionKey:在上面写绑定重叠套接字与完成端口的时候已经介绍到了此参数,这里就不过多说了。它就是接收绑定完成端口的时候传进来的SOCKET。
lpOverlapped:返回一个发生事件通知的SOCKET上所绑定的那个重叠结构的地址。
dwMilliseconds:等待时间。可以是具体的等待时间以毫秒为单位;也可以一直等到有事件通知为止,一直等填INFINITE。

返回值(Return value):

函数执行成功返回TRUE,失败返回FALSE,可以用GetLastError()获取错误码。

完成端口代码逻辑:

1.打开网络库(WSAStartup())

2.校验版本(副版本:HIBYTE()、主版本:LOBYTE())

3.创建SOCKET(WSASocket())

4.绑定地址与端口号(bind())

5.创建完成端口(CreateIoCompletionPort())

6.将重叠套接字(客户端SOCKET+服务器SOCKET)与完成端口进行绑定(CreateIoCompletionPort())

7.开始监听(listen())

8.创建线程(CreteThread())

9.获取事件通知(GetQueuedCompletionPort())进行分类处理

10.释放

服务器开发基础-Tcp/Ip网络模型—完成端口(Completion Port)模型的更多相关文章

  1. TCP/IP 网络模型

    前言 互联网是怎么构成的,又是怎么运作的?什么是 TCP/IP 网络?为什么远隔万里的计算机可以互相通信?计算机网络作为 IT 行业的基石,是工程师永远绕不开的话题. 计算机网络的分层体系结构 计算机 ...

  2. 完成端口(Completion Port)详解(转)

    手把手叫你玩转网络编程系列之三    完成端口(Completion Port)详解                                                           ...

  3. (转载)完成端口(Completion Port, I/OCP)详解

    http://www.cnblogs.com/lancidie/archive/2011/12/19/2293773.html 手把手叫你玩转网络编程系列之三    完成端口(Completion P ...

  4. 转:完成端口(Completion Port)详解

    手把手叫你玩转网络编程系列之三    完成端口(Completion Port)详解                                                           ...

  5. 网络基础二 tcp/ip协议簇 端口 三次握手 四次挥手 11种状态集

    第1章 概念介绍 1.1 VLAN 1.1.1 什么是VLAN VLAN(Virtual LAN),翻译成中文是“虚拟局域网”.LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成 ...

  6. 网络基础tcp/ip协议五

    传输层的作用: ip层提供点到点的链接. 传输层提供端到端的链接. 传输层的协议: TCP: 传输控制协议可靠的,面向链接的协议,传输效率低. UDP: 用户数据报协议,不可靠,无连接的服务,传输效率 ...

  7. TCP/IP 协议簇 端口 三次握手 四次挥手 11种状态集

    第1章 概念介绍 1.1 VLAN 1.1.1 什么是VLAN VLAN(Virtual LAN),翻译成中文是“虚拟局域网”.LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成 ...

  8. 加深理解HTTP请求---网络基础TCP/IP

    为了了解HTTP,必须的了解TCP/IP协议族. 通常使用的网络实在TCP/IP协议族的基础上运作的.而HTTP就属于他的一个子集. 1.TCP/IP 协议族 计算机与网络设备要相互通信,双方就必须基 ...

  9. linux高性能服务器编程 (一) --Tcp/Ip协议族

    前言: 在学习swoole入门基础的过程中,遇到了很多知识瓶颈,比方说多进程.多线程.以及进程池和线程池等都有诸多的疑惑.之前也有学习相关知识,但只是单纯的知识面了解.而没有真正的学习他们的来龙去脉. ...

随机推荐

  1. iNeuOS工业互联平台,发布:消息管理、子用户权限管理、元件移动事件、联动控制和油表饼状图,v3.4版本

    目       录 1.      概述... 2 2.      平台演示... 2 3.      消息管理... 2 4.      子用户权限管理... 3 5.      元件移动事件... ...

  2. 前端 | 使用 ECharts 绘制关系图

    0 需求 做的项目需要画一个关系图,主要需求如下: 需要展示6种对象之间的关系:数据机构 数据 合约 模型 计算机构 应用 支持突出显示6种对象中的某一种的所有对象 支持Top x子图功能.top x ...

  3. .NET 开源配置组件 AgileConfig 初体验

    介绍 在微服务大行其道的今天,系统会被拆分成多个模块,作为单独的服务运行,同时为了集中化管理,我们还需要日志中心,配置中心等,很多开发人员可能更熟悉 ApolloConfig,这个组件功能也很完善,d ...

  4. 全网最详细的Linux命令系列-iptrad-ng网络流量监测命令

    观察网络流量的工具:IPTRAF 想知道你的Linux系统上网络流量有多大吗?想知道是哪一块网卡承载着网络流量吗?想知道哪一个进程产生了网络流量吗?iptraf可以帮你做到.在最新的Linux rel ...

  5. Java高并发测试框架JCStress

    前言 如果要研究高并发,一般会借助高并发工具来进行测试.JCStress(Java Concurrency Stress)它是OpenJDK中的一个高并发测试工具,它可以帮助我们研究在高并发场景下JV ...

  6. 记录给树莓派刷Raspberry Pi OS(Raspbian)系统的配置流程

    准备材料 树莓派(一定要贴散热片,最好再加个小风扇) TF内存卡 (记得选传输规范为Class10标准的) 读卡器 电脑(这里我使用的电脑是Windows系统,其它系统可能与下面的步骤有出入,还望悉知 ...

  7. elementui 表格格式化

    <el-table-column prop="userType" label="角色" width="180" :formatter= ...

  8. 使用Docker Toolbox 创建Docker虚拟机的方法-注意正确使用本地文件 file:参数的路径名

    使用Docker Toolbox 创建v1.12.6版的Docker虚拟机的方法, 一定要注意正确使用本地文件 file:// 参数的路径名, 之前尝试创建过多次,一直都没有成功过, 无法使用 fil ...

  9. C# Linq 延迟查询的执行

    在定义linq查询表达式时,查询是不会执行,查询会在迭代数据项时运行.它使用yield return 语句返回谓词为true的元素. var names = new List<string> ...

  10. Github Pages+Gridea设置DisqusJS评论

    1 前言 相信很多人都对Github Pages不默认,笔者之前写过了一篇文章,使用Github Pages去搭建博客的,链接可以戳这里. 但是唯一的不足是没有提到评论,因为当时笔者测试了一下,评论设 ...