手把手教你玩转网络编程模型之完成端口(CompletionPort)篇
- 格式:docx
- 大小:638.70 KB
- 文档页数:26
使用VC++的网络编程总结1.套接字编程原理一个完整的网间通信进程需要由两个进程组成,并且只能用同一种高层协议。
也就是说,不可能通信的一端用TCP,而另一端用UDP。
一个完整的网络信需要一个五元组来标识:协议、本地地址、本地端口号、远端地址、远端端口号。
1.1 Client/server通信模型在客户/服务器模式中我们将请求服务的一方称为客户(client),将提供某种服务的一方称为服务器(server)。
一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。
在这个时刻,服务程序被“惊醒”并且为客户提供服务—对客户的请求作出适当的反应。
虽然基于连接的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过无连接的接口提供的。
客户机/服务器的请求/响应过程示意图如下所示。
图1 客户/服务器通信模型通过上面的分析,我们不难理解一个一个完整的网络应用程序包括客户端和服务器两个部分。
客户与服务器进程的作用是非对称的,因此编码不同。
服务进程一般是等待客户请求而启动的,只要系统运行,该服务进程一直存在,直到终止或强迫终止。
1.2 Windows Sockets规范Windows Sockets 规范是90年代初Microsoft公司联合其他几家大公司共同制定的一套在Windows下的二进制兼容网络编程接口规范。
它以U.C.Berkeley大学BSD UNIX中流行的Socket接口为基础,主要在其上扩充了一组针对Windows的扩展库函数,增加了符合Windows消息驱动特性的网络事件异步选择机制,以使程序员能够充分利用Windows消息驱动机制进行编程。
Windows Sockets 的用途是将基础网络抽象出来,这样,您不必对网络非常了解,并且您的应用程序可在任何支持套接字的网络上运行。
它为应用程序开发者定义了一套简单统一的API,并让各家网络软件供应商共同遵守。
前提概要在开发过程中在使用多线程进行并行处理一些事情的时候,大部分场景在处理多线程并行执行任务的时候,可以通过List 添加Future 来获取执行结果,有时候我们是不需要获取任务的执行结果的,方便后面引出 ExecutorCompletionService。
CompletionService 的介绍CompletionService 接口是一个独立的接口,并没有扩展ExecutorService 。
其默认实现类是ExecutorCompletionService。
接口CompletionService 的功能是:以异步的方式一边执行未完成的任务,一边记录、处理已完成任务的结果。
从而可以将任务的执行与处理任务的执行结果分离开来。
CompletionService 的实现原理CompletionService 就是监视着 Executor 线程池执行的任务,用 BlockingQueue 将完成的任务的结果存储下来。
要不断遍历与每个任务关联的Future,然后不断去轮询,判断任务是否已经完成,功能比较繁琐。
public interface CompletionService<V> {Future<V> submit(Callable<V> task);Future<V> submit(Runnable task, V result);Future<V> take() throws InterruptedException;Future<V> poll();Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;}方法摘要提交一个 Callable 任务;一旦完成,便可以由 take ()、poll () 方法获取Future submit(Callable task):提交一个 Runnable 任务,并指定计算结果;Future submit(Runnable task, V result):获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
IOCP模型与⽹络编程IOCP模型与⽹络编程⼀。
前⾔:在⽼师分配任务(“尝试利⽤IOCP模型写出服务端和客户端的代码”)给我时,脑⼦⼀⽚空⽩,并不知道什么是IOCP模型,会不会是像软件设计模式⾥⾯的⼯⼚模式,装饰模式之类的那些呢?嘿嘿,不过好像是⼀个挺好玩的东西,挺好奇是什么东西来的,⼜是⼀个新知识啦~于是,开始去寻找⼀⼤堆的资料,为这个了解做准备,只是呢,有时还是想去找⼀本书去系统地学习⼀下,毕竟⽹络的资料还是有点零散。
话说,本⼈学习这个模型的基础是,写过⼀个简单的Socket服务器及客户端程序,外加⼀个简单的Socket单服务器对多客户端程序,懂⼀点点的操作系统原理的知识。
于是,本着⼀个学习与应⽤的态度开始探究这个IOCP是个什么东西。
⼆。
提出相关问题:1. IOCP模型是什么?2. IOCP模型是⽤来解决什么问题的?它为什么存在?3. 使⽤IOCP模型需要⽤到哪些知识?4. 如何使⽤IOCP模型与Socket⽹络编程结合起来?5. 学会了这个模型以后与我之前写过的简单的socket程序主要有哪些不同点?三。
部分问题探究及解决:(绝⼤多数是个⼈理解,再加上个⼈是菜鸟,如果有什么不对的地⽅,欢迎指正)1. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作⽤?1) IOCP(I/O Completion Port),常称I/O完成端⼝。
2) IOCP模型属于⼀种通讯模型,适⽤于(能控制并发执⾏的)⾼负载服务器的⼀个技术。
3) 通俗⼀点说,就是⽤于⾼效处理很多很多的客户端进⾏数据交换的⼀个模型。
4) 或者可以说,就是能异步I/O操作的模型。
5) 只是了解到这些会让⼈很糊涂,因为还是不知道它究意具体是个什么东东呢?下⾯我想给⼤家看三个图:第⼀个是IOCP的内部⼯作队列图。
(整合于《IOCP本质论》⽂章,在英⽂的基础上加上中⽂对照)第⼆个是程序实现IOCP模型的基本步骤。
(整合于《深⼊解释IOCP》,加个⼈观点、理解、翻译)第三个是使⽤了IOCP模型及没使⽤IOCP模型的程序流程图。
完成IO使用总结IOCP(I/O Completion Port,I/O完成端口)是性能最好的一种I/O模型。
它是应用程序使用线程池处理异步I/O请求的一种机制。
在处理多个并发的异步I/O请求时,以往的模型都是在接收请求是创建一个线程来应答请求。
这样就有很多的线程并行地运行在系统中。
而这些线程都是可运行的,Windows内核花费大量的时间在进行线程的上下文切换,并没有多少时间花在线程运行上。
再加上创建新线程的开销比较大,所以造成了效率的低下。
调用的步骤如下:抽象出一个完成端口大概的处理流程:1:创建一个完成端口。
2:创建一个线程A。
3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是个阻塞函数。
4:主线程循环里调用accept等待客户端连接上来。
5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort 关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。
6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。
7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。
8:A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。
9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。
归根到底概括完成端口模型一句话:我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO 都完成了,那么就在完成端口那里排成一个队列)。
IOCP(完成端口)是什么以下就是IOCP的模型图,能看懂么?IOCP模型(图1.1)IOCP是什么IOCP模型相对于其它模型(select,。
),确实有点复杂。
它虽然就那么几个I/O操作函数,但它们都有一大串的参数,返回值的多样性,大大的加大了理解难度。
但是如果你理解了它,掌握了难点重点,也就那么一回事。
可以从以下几方理解什么是IOCP。
一、IOCP的实现步骤。
二、IOCP的核心--重叠异步I/O。
三、如何提交I/O请求。
四、如何取得I/O返回的操作结果,并作处理。
五、IOCP的一个完整例子。
< 二>重叠异步I/O重叠异步I/O要点:1.驱动原理2.VOERLAPPED 结构的作用3.OVERLAPPED 结构的扩展和应用1.重叠异步I/O的驱动原理IOCP的核心就是重叠异步I/O。
首先理解它的工作原理,什么是重叠异步I/O呢?重叠异步I/O的原理是:客户(应用程序)向I/O管理器提交I/O操作(读或写数据)请求,然后去忙其它事。
I/O操作完成之后,I/O管理器再通知客户。
同样,IOCP就是socket向I/O管理器提交各种请求,然后等待并取得I/O管理器返回的结果,如图1.1 所示。
IOCP就像你去银行办理业务,先去取个号码,然后等待叫号。
在拿到号码到被叫号期间,你可以干什么都行(IOCP和叫号还是有点区别的,就是IOCP会保留结果直到被取走,而叫号在叫到你的号码时,你不在,那么你的号码就作废了)。
也可以这么理解重叠异步I/O,就是在一头提交I/O请求(可以一次或多次—叫重叠),然后再另一头取得I/O操作的结果。
这两个操作是不同时、不连续的,这就是异步的原因。
2.VOERLAPPED 结构的作用提交I/O请求的socket函数有:ConnectEx、AcceptEx、WSASend、WSARecv。
取得I/O操作返回结果的函数有:GetQueuedCompletionStatus,GetOverlappedResult 等。
手把手教你学习网络编程教程网络编程是现代软件开发领域中非常重要的一项技能。
通过网络编程,我们可以实现不同设备之间的通信,搭建网络服务,构建分布式系统等等。
然而,对于初学者来说,网络编程可能会有一定的难度。
本文将手把手地教你学习网络编程的基础知识和技巧,帮助你快速入门。
一、网络编程概述网络编程是指通过计算机网络实现程序之间的数据交换和通信的过程。
在网络编程中,我们通常会使用一些网络协议来规定数据的传输方式和通信规则,常见的协议有TCP/IP、HTTP、UDP等。
二、网络编程基础1. 网络通信模型网络通信模型主要有两种,分别是客户端-服务器模型和对等模型。
在客户端-服务器模型中,客户端向服务器发送请求,服务器接收请求并做出响应;在对等模型中,两个设备之间既可以作为客户端发送请求,也可以作为服务器接收请求。
2. Socket编程Socket是网络编程中的一个重要概念,它是网络通信的一种机制。
在Socket编程中,我们可以通过建立Socket连接来进行数据的发送和接收。
通过Socket编程,我们可以方便地实现各种网络通信功能。
常用的Socket库有Python的socket模块和Java的包。
三、网络编程实战下面以Python为例,介绍一些网络编程的实践示例。
1. 创建Socket服务器首先,我们需要导入Python的socket模块。
然后,使用socket模块的socket方法来创建一个服务器的Socket实例。
接着,使用该实例的bind方法指定服务器的地址和端口。
最后,使用该实例的listen和accept方法来监听客户端的连接请求,并接收客户端发送的数据。
2. 创建Socket客户端同样地,我们需要导入Python的socket模块。
然后,使用socket模块的socket方法来创建一个客户端的Socket实例。
接着,使用该实例的connect方法来连接服务器。
最后,使用该实例的send方法发送数据给服务器,并使用recv方法接收服务器的响应。
如何在C++中进行网络编程开发网络编程是使用计算机网络进行信息传输和通信的一种编程技术。
在C++中进行网络编程开发,你需要使用C++的网络库或框架来实现网络通信的各种功能。
本文将介绍如何使用C++进行网络编程开发。
1.理解计算机网络基础知识在开始网络编程之前,首先需要了解计算机网络的基本概念和工作原理。
学习网络协议、IP地址、端口号等基础知识对于理解和开发网络应用非常重要。
2.学习C++网络编程库C++中有许多网络编程库和框架可供选择,其中常用的包括Boost.Asio、POCO、Qt Network等。
这些库提供了许多网络编程所需的底层功能和接口,简化了网络编程的开发过程。
3.创建服务器端和客户端网络编程通常涉及到服务器端和客户端的开发。
服务器端负责接收和处理客户端发送的请求,而客户端负责发送请求并接收服务器端的响应。
在C++中,你可以使用库中提供的类和函数来创建服务器端和客户端对象,并实现它们的具体功能。
4.设置套接字和网络连接套接字(socket)是网络编程的核心概念,它用于建立和管理网络连接。
在C++中,你需要使用库提供的函数来创建套接字、设置网络连接和进行数据传输。
例如,你可以使用`socket()`函数创建套接字,使用`bind()`函数绑定套接字到指定的IP地址和端口号,使用`listen()`函数监听连接请求,使用`accept()`函数接受连接请求等。
5.实现数据传输和通信在建立网络连接之后,服务器端和客户端可以通过套接字进行数据传输和通信。
在C++中,你可以使用`send()`和`recv()`函数进行数据的发送和接收。
服务器端可以使用`accept()`函数接受客户端的连接请求,并通过套接字与客户端进行数据交换。
6.处理异步网络编程网络编程中经常需要处理异步操作,例如同时接收多个客户端的连接请求或同时与多个客户端进行数据交换。
在C++中,你可以使用异步回调函数、事件循环或多线程来实现异步网络编程。
Windows I/O完成端口2009-10-30 10:51WINDOWS完成端口编程1、基本概念2、WINDOWS完成端口的特点3、完成端口(Completion Ports )相关数据结构和创建4、完成端口线程的工作原理5、Windows完成端口的实例代码WINDOWS完成端口编程摘要:开发网络程序从来都不是一件容易的事情,尽管只需要遵守很少的一些规则:创建socket,发起连接,接受连接,发送和接收数据,等等。
真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。
利用Windows完成端口进行重叠I/O的技术,可以很方便地在Windows平台上开发出支持大量连接的网络服务程序。
本文介绍在Windows平台上使用完成端口模型开发的基本原理,同时给出实际的例子。
本文主要关注C/S结构的服务器端程序,因为一般来说,开发一个大容量、具有可扩展性的winsock程序就是指服务程序。
1、基本概念设备---指windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。
绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile 等,所以我们不能看到**File函数就只想到文件设备。
与设备通信有两种方式,同步方式和异步方式:同步方式下,当调用ReadFile这类函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。
重叠I/O----顾名思义,就是当你调用了某个函数(比如ReadFile)就立刻返回接着做自己的其他动作的时候,系统同时也在对I/0设备进行你所请求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。
所以,重叠I/O是在异步方式下使用I/O设备的。
IOCP完成端口详解 VC VC++iocp完成端口详解vcvc++iocp完成端口详解――vc/vc++通常必须研发网络应用程序并不是一件随心所欲的事情,不过,实际上只要掌控几个关键的原则也就可以了――建立和相连接一个套传输层,尝试展开相连接,然后通话数据。
真正容易的就是必须写下一个可以采纳太少则一个,多则数千个相连接的网络应用程序。
本文将探讨如何通过winsock2在windowsnt和windows2000上研发低拓展能力的winsock 应用程序。
文章主要的焦点在客户机/服务器模型的服务器这一方,当然,其中的许多要点对模型的双方都适用于。
api与响应规模通过win32的重合i/o机制,应用程序可以提出申请一项i/o操作方式,重合的操作方式命令在后台顺利完成,而同一时间提出申请操作方式的线程回去搞其他的事情。
等重合操作方式顺利完成后线程接到有关的通告。
这种机制对那些耗时的操作方式而言特别有价值。
不过,像是windows3.1上的wsaasyncselect()及unix下的select()那样的函数虽然易于使用,但是它们不能满足响应规模的需要。
而完成端口机制是针对操作系统内部进行了优化,在windowsnt和windows2000上,使用了完成端口的重叠i/o机制才能够真正扩大系统的响应规模。
顺利完成端口一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠i/o请求的通知放入其中。
当某项i/o操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。
而套接字在被创建后,可以在任何时候与某个完成端口进行关联。
通常情况下,我们可以在应用程序中建立一定数量的工作者线程去处置这些通告。
线程数量依赖于应用程序的特定须要。
理想的情况就是,线程数量等同于处理器的数量,不过这也建议任何线程都不必须继续执行诸如同步读取、等候事件通告等堵塞型的操作方式,以免线程堵塞。
每个线程都将抽到一定的cpu时间,在此期间该线程可以运转,然后另一个线程将抽到一个时间片并已经开始继续执行。
手把手教你玩转网络编程模型系列之三 完成端口(CompletionPort)篇 ----- By PiggyXP(小猪)
前言 完成端口的代码在两年前就已经写好了,但是由于许久没有写东西了,不知该如何提笔,所以这篇文档总是在酝酿之中……酝酿了两年之后,终于决定开始动笔了,但愿还不算晚….. 这篇文档我非常详细并且图文并茂的介绍了关于网络编程模型中完成端口的编程模型方方面面的信息,从API的用法到使用的步骤,从完成端口的实现机理到实际使用的注意事项,都有所涉及,并且为了让朋友们更直观的体会完成端口的用法,本文附带了有详尽注释的使用MFC编写的图形界面的示例代码。 我的初衷是希望写一份互联网上能找到的最详尽的关于完成端口的教学文档,而且让对Socket编程略有了解的人都能够看得懂,都能学会如何来使用完成端口这么优异的网络编程模型,但是由于本人水平所限,不知道我的初衷是否实现了,但还是希望各位需要的朋友能够喜欢。 由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了多线程编程技术,太基本的概念我这里就略过不提了,网上的资料应该遍地都是。 本文档凝聚着笔者心血,如要转载,请指明原作者及出处,谢谢!不过代码没有版权,可以随便散播使用,欢迎改进,特别是非常欢迎能够帮助我发现Bug的朋友,以更好的造福大家。^_^ 本文配套的示例源码下载地址(在我的下载空间里) http://piggyxp.download.csdn.net/ (里面的代码包括VC++2008/VC++2010编写的完成端口服务器端的代码,还包括一个对服务器端进行压力测试的测试代码,都是经过我精心调试过,并且带有非常详尽的代码注释的。当然,作为教学代码,为了能够使得代码结构清晰明了,我还是对代码有所简化,如果想要用于产品开发,最好还是需要自己再完善一下,另外我的工程是用2010编写的,附带的2008工程不知道有没有问题,但是其中代码都是一样的,暂未测试) 忘了嘱咐一下了,文章篇幅很长很长,基本涉及到了与完成端口有关的方方面面,一次看不完可以分好几次,中间注意休息,好身体才是咱们程序员最大的本钱! 对了,还忘了嘱咐一下,因为本人的水平有限,虽然我反复修正了数遍,但文章和示例代码里肯定还有我没发现的错误和纰漏,希望各位一定要指出来,拍砖、喷我,我都能Hold住,但是一定要指出来,我会及时修正,因为我不想让文中的错误传遍互联网,祸害大家。 OK, Let’s go ! Have fun! 目录: 1. 完成端口的优点 2. 完成端口程序的运行演示 3. 完成端口的相关概念 4. 完成端口的基本流程 5. 完成端口的使用详解 6. 实际应用中应该要注意的地方
一. 完成端口的优点 1. 我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S通信模式中性能最好的网络通信模型,没有之一;甚至连和它性能接近的通信模型都没有。 2. 完成端口和其他网络通信方式最大的区别在哪里呢? (1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。 (2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不? (3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,这种神奇的效果具体是如何实现的请看下文。 3. 完成端口被广泛的应用于各个高性能服务器程序上,例如著名的Apache„.如果你想要编写的服务器端需要同时处理的并发客户端连接数量有数百上千个的话,那不用纠结了,就是它了。
二. 完成端口程序的运行演示 首先,我们先来看一下完成端口在笔者的PC机上的运行表现,笔者的PC配置如下: 图1 笔者计算机配置信息 大体就是i7 2600 + 16GB内存,我们进行如下的测试,通过Client生成3万个并发线程同时连接至Server,然后每个线程每隔3秒中发送一次数据,一共发送3次,然后观察服务器端的CPU和内存的占用情况。 如图2所示,是客户端3万个并发线程发送共发送9万条数据的log截图
图2客户端发3万并发线程log信息 图3是服务器端接收完毕3万个并发线程和每个线程的3份数据后的log截图 图3 服务器端接收3万并发线程log信息 最关键是图4,图4是服务器端在接收到28000个并发线程的时候,CPU占用率的截图,使用的软件是大名鼎鼎的Process Explorer,因为相对来讲这个比自带的任务管理器要准确和精确一些。
图4 服务器端和客户端的CPU占用情况 我们可以发现一个令人惊讶的结果,采用了完成端口的Server程序(蓝色横线所示)所占用的CPU才为 3.82%,整个运行过程中的峰值也没有超过4%,是相当气定神闲的„„哦,对了,这还是在Debug模式下运行的情况,如果采用Release方式执行,性能肯定还会更高一些,除此以外,在UI上显示信息也很大成都上影响了性能。 相反采用了多个并发线程的Client程序(紫色横线所示)居然占用的CPU高达11.53%,甚至超过了Server程序的数倍„„ 其实无论是哪种网络操模型,对于内存占用都是差不多的,真正的差别就在于CPU的占用,其他的网络模型都需要更多的CPU动力来支撑同样的连接数据。 虽然这远远算不上服务器极限压力测试,但是从中也可以看出来完成端口的实力,而且这种方式比纯粹靠多线程的方式实现并发资源占用率要低得多。
三. 完成端口的相关概念 在开始编码之前,我们先来讨论一下和完成端口相关的一些概念,如果你没有耐心看完这段大段的文字的话,也可以跳过这一节直接去看下下一节的具体实现部分,但是这一节中涉及到的基本概念你还是有必要了解一下的,而且你也更能知道为什么有那么多的网络编程模式不用,非得要用这么又复杂又难以理解的完成端口呢??也会坚定你继续学习下去的信心^_^
1. 异步通信机制及其几种实现方式的比较 我们从前面的文字中了解到,高性能服务器程序使用异步通信机制是必须的。 而对于异步的概念,为了方便后面文字的理解,这里还是再次简单的描述一下: 异步通信就是在咱们与外部的I/O设备进行打交道的时候,我们都知道外部设备的I/O和CPU比起来简直是龟速,比如硬盘读写、网络通信等等,我们没有必要在咱们自己的线程里面等待着I/O操作完成再执行后续的代码,而是将这个请求交给设备的驱动程序自己去处理,我们的线程可以继续做其他更重要的事情,大体的流程如图1所示:
图1 异步通信流程 我可以从图中看到一个很明显的并行操作的过程,而“同步”的通信方式是在进行网络操作的时候,
应用程序发出一个网络操作调用异步:这个网络操作会由操
作系统来帮我们完成
异步:应用程序执行其他的事情,而不用挂起等待网络操作会和我们的应用程序同步进行
通知应用程序处理网络数据网络操作完成主线程就挂起了,主线程要等待网络操作完成之后,才能继续执行后续的代码,是没法这样并行的; 这样无疑比“阻塞模式+多线程”的方式效率要高的多,这也是前者为什么叫“异步”,后者为什么叫“同步”的原因了,因为不需要等待网络操作完成再执行别的操作。 而在Windows中实现异步的机制同样有好几种,而这其中的区别,关键就在于图1中的最后一步“通知应用程序处理网络数据”上了,因为实现操作系统调用设备驱动程序去接收数据的操作都是一样的,关键就是在于如何去通知应用程序来拿数据。它们之间的具体区别我这里多讲几点,文字有点多,如果没兴趣深入研究的朋友可以跳过下一面的这一段,不影响的:) (1) 设备内核对象,使用设备内核对象来协调数据的发送请求和接收数据协调,也就是说通过设置设备内核对象的状态,在设备接收数据完成后,马上触发这个内核对象,然后让接收数据的线程收到通知,但是这种方式太原始了,接收数据的线程为了能够知道内核对象是否被触发了,还是得不停的挂起等待,这简直是根本就没有用嘛,太低级了,有木有?所以在这里就略过不提了,各位读者要是没明白是怎么回事也不用深究了,总之没有什么用。 (2) 事件内核对象,利用事件内核对象来实现I/O操作完成的通知,其实这种方式其实就是我以前写文章的时候提到的《基于事件通知的重叠I/O模型》,链接在这里,这种机制就先进得多,可以同时等待多个I/O操作的完成,实现真正的异步,但是缺点也是很明显的,既然用WaitForMultipleObjects()来等待Event的话,就会受到64个Event等待上限的限制,但是这可不是说我们只能处理来自于64个客户端的Socket,而是这是属于在一个设备内核对象上等待的64个事件内核对象,也就是说,我们在一个线程内,可以同时监控64个重叠I/O操作的完成状态,当然我们同样可以使用多个线程的方式来满足无限多个重叠I/O的需求,比如如果想要支持3万个连接,就得需要500多个线程„用起来太麻烦让人感觉不爽; (3) 使用APC( Asynchronous Procedure Call,异步过程调用)来完成,这个也就是我以前在文章里提到的《基于完成例程的重叠I/O模型》,链接在这里,这种方式的好处就是在于摆脱了基于事件通知方式的64个事件上限的限制,但是缺点也是有的,就是发出请求的线程必须得要自己去处理接收请求,哪怕是这个线程发出了很多发送或者接收数据的请求,但是其他的线程都闲着„,这个线程也还是得自己来处理自己发出去的这些请求,没有人来帮忙„这就有一个负载均衡问题,显然性能没有达到最优化。 (4) 完成端口,不用说大家也知道了,最后的压轴戏就是使用完成端口,对比上面几种机制,完成端口的做法是这样的:事先开好几个线程,你有几个CPU我就开几个,首先是避免了线程的上下文切换,因为线程想要执行的时候,总有CPU资源可用,然后让这几个线程等着,等到有用户请求来到的时候,就把这些请求都加入到一个公共消息队列中去,然后这几个开好的线程就排队逐一去从消息队列中取出消息并加以处理,这种方式就很优雅的实现了异步通信和负载均衡的问题,因为它提供了一种机制来使用几个线程“公平的”处理来自于多个客户端的输入/输出,并且线程如果没事干的时候也会被系统挂起,不会占用CPU周期,挺完美的一个解决方案,不是吗?哦,对了,这个关键的作为交换的消息队列,就是完成端口。 比较完毕之后,熟悉网络编程的朋友可能会问到,为什么没有提到WSAAsyncSelect或者是WSAEventSelect这两个异步模型呢,对于这两个模型,我不知道其内部是如何实现的,但是这其中一定没有用到Overlapped机制,就不能算作是真正的异步,可能是其内部自己在维护一个消息队列吧,总之这两个模式虽然实现了异步的接收,但是却不能进行异步的发送,这就很明显说明问题了,我想其内部的实现一定和完成端口是迥异的,并且,完成端口非常厚道,因为它是先把用户数据接收回来之后再通知用户直接来取就好了,而WSAAsyncSelect和WSAEventSelect之流只是会接收到数据到达的通知,而只