服务器开发基础-Tcp/Ip网络模型—完成端口(Completion Port)模型
本文对于初学网络编程的极为友好,文中所有代码全部基于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完成端口,如果指定了参数FileHandle为INVALID_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)模型的更多相关文章
- TCP/IP 网络模型
		
前言 互联网是怎么构成的,又是怎么运作的?什么是 TCP/IP 网络?为什么远隔万里的计算机可以互相通信?计算机网络作为 IT 行业的基石,是工程师永远绕不开的话题. 计算机网络的分层体系结构 计算机 ...
 - 完成端口(Completion Port)详解(转)
		
手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解 ...
 - (转载)完成端口(Completion Port, I/OCP)详解
		
http://www.cnblogs.com/lancidie/archive/2011/12/19/2293773.html 手把手叫你玩转网络编程系列之三 完成端口(Completion P ...
 - 转:完成端口(Completion Port)详解
		
手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解 ...
 - 网络基础二  tcp/ip协议簇 端口 三次握手 四次挥手 11种状态集
		
第1章 概念介绍 1.1 VLAN 1.1.1 什么是VLAN VLAN(Virtual LAN),翻译成中文是“虚拟局域网”.LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成 ...
 - 网络基础tcp/ip协议五
		
传输层的作用: ip层提供点到点的链接. 传输层提供端到端的链接. 传输层的协议: TCP: 传输控制协议可靠的,面向链接的协议,传输效率低. UDP: 用户数据报协议,不可靠,无连接的服务,传输效率 ...
 - TCP/IP 协议簇 端口 三次握手 四次挥手 11种状态集
		
第1章 概念介绍 1.1 VLAN 1.1.1 什么是VLAN VLAN(Virtual LAN),翻译成中文是“虚拟局域网”.LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成 ...
 - 加深理解HTTP请求---网络基础TCP/IP
		
为了了解HTTP,必须的了解TCP/IP协议族. 通常使用的网络实在TCP/IP协议族的基础上运作的.而HTTP就属于他的一个子集. 1.TCP/IP 协议族 计算机与网络设备要相互通信,双方就必须基 ...
 - linux高性能服务器编程 (一) --Tcp/Ip协议族
		
前言: 在学习swoole入门基础的过程中,遇到了很多知识瓶颈,比方说多进程.多线程.以及进程池和线程池等都有诸多的疑惑.之前也有学习相关知识,但只是单纯的知识面了解.而没有真正的学习他们的来龙去脉. ...
 
随机推荐
- OpenGL 绘制你的 github skyline 模型
			
前言 好久没更新博客了,上一篇文章还是在去年6月份,乍一看居然快要过去一年了,不时还能看到粉丝数和排名在涨,可是却一直没有内容更新,怪不好意思的- -(主要是一直没想好要写些什么,中间也有过一些想法, ...
 - java例题_03 水仙花数
			
1 /*3 [程序 3 水仙花数] 2 题目:打印出所有的"水仙花数",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数本身. 3 例如:153 是一个 ...
 - 别人 echo 、你也 echo  ,是问 echo  知多少?-- Shell十三问<第三问>
			
别人 echo .你也 echo ,是问 echo 知多少?-- Shell十三问<第三问> 承接上一章所介绍的 command line ,这里我们用 echo 这个命令加以进一步说明. ...
 - DSP代码搬运至RAM运行
			
程序运行过程中,有些函数或程序段和数据等经常调用,正常情况下在FLASH中运行处理消耗时间和资源较大,通常将其移植至RAM中运行,可提高运行效率. 如: 1 #pragma CODE_SECTION( ...
 - ADFS修改默认访问端口
			
在安装Dynamics CRM部署IFD需要安装ADFS来进行身份验证.而ADFS默认会占用服务器的443端口.如果我们想自己使用443端口的话则需要修改ADFS的默认端口.(如果需要部署移动端的话还 ...
 - mp4视频中插入文字
			
最近接到一个需求,需要往mp4中动态插入文字,并且mp4中的乌云能在文字上有飘动的效果,一开始想用canvas,但是由于本人经验不足,没什么思路,看到css3有一个属性:mix-blend-mode, ...
 - Nginx的进程管理与重载原理
			
目录 进程结构图 信号量管理 Linux的信号量管理机制 利用信号量管理Nginx进程 配置文件重载原理 进程结构图 Nginx是多进程结构,多进程结构设计是为了保证Nginx的高可用高可靠,包含: ...
 - Axure常用操作备忘
			
目录 前言 技巧 边框重合 复制对象文本居中 复制粘贴样式 文本自适应 给图形添加连接点 导出图片无空白 前言 下面列出Axure画图过程中曾经遇到过的问题,备忘一下,避免别人也走弯路,法布施一下~ ...
 - 设计模式学习笔记(二):UML与面向对象设计原则
			
1 UML 1.1 UML UML(Unified Modeling Language)是统一建模语言,1997年11月UML1.1版本提交给OMG并正式通过,成为建模语言的个那个也标准.2003年6 ...
 - Linux下安装Anaconda 并进行用户共享
			
下载镜像 wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ Anaconda3-5.3.1-Linux-x86_64.sh 安装 ...