论文网络模型之完成端口
- 格式:docx
- 大小:516.29 KB
- 文档页数:28
网络模型和完成端口简单介绍下完成端口吧。
废话不多说,先从简单的一点一点往深里说。
首先,要理解SOCKET编程,熟悉windows编程的估计都知道socket(xxx) 创建一个socket,可以实现网络通讯,实际上,linux下一样是用socket函数创建套接字,简单说socket是一个标准,并非windows独有。
当然windows也有自己的特色,比如WINDOWS下必须先调用WSAStartup 而linux 就不用。
额,扯远了,再说这个,比如我要实现一个TCP的程序,则需要使用send/recv 这两个函数,OK,这里就有个问题了,用过recv函数的人都知道,recv调用以后会停在那里,这个叫做阻塞,只有当有数据(IO操作)的时候,recv才会返回。
这个就是传说中的阻塞模式。
显然,如果我们是一个UI程序(图形界面程序)我们总不会希望我们的程序变成白白的窗口,直到SOCKET返回时候才继续执行,为了解决这个问题,诞生了几种网络模型。
先说最简单的一种,叫select 模型,为什么叫这个名字,因为这个模型的核心函数就是select 函数。
select模型有个核心的结构叫做typedef struct fd_set {u_int fd_count;SOCKET fd_array[FD_SETSIZE];} fd_set;他的主要思想是将socket全部放入这个结构中,然后select等待若干秒,然后检查某个socket 是否还在这个结构中,就知道这个socket是否发生事件(例如读事件)这样,一个线程循环检查就可以管理很多个socket实现一个简单的服务端,select模型虽然很简单,但是应用却很广泛。
一般用在需要实现一个简单的服务端或客户端功能,但是又没必要使用比较复杂的模型的时候。
当然,select也有一些致命的缺点,可以看到他管理套接字是用的一个数组,数组大小是FD_SETSIZE ,在winsock2.h 109行定义了/** Select uses arrays of SOCKETs. These macros manipulate such* arrays. FD_SETSIZE may be defined by the user before including* this file, but the default here should be >= 64.** CA VEAT IMPLEMENTOR and USER: THESE MACROS AND TYPES MUST BE* INCLUDED IN WINSOCK2.H EXACTL Y AS SHOWN HERE.*/#ifndef FD_SETSIZE#define FD_SETSIZE 64#endif /* FD_SETSIZE */也就是默认情况下最大只能支持64个套接字,当然可以重定义,但是实际上,select 模型处理大量连接的时候就显得无能为力了。
完成端口一、什么是完成端口从本质上讲,完成端口是一种异步I/O技术,它提供一个内核对象,可以关联多个I/O设备,同时关联一个线程池,线程池中的线程通常处于睡眠状态,当有I/O出现时,完成端口唤醒等待线程队列中的线程进行处理。
完成端口有着良好的伸缩性灵活性以及较高的效率,一般用来创建大型的服务器。
我们知道,一个服务器应用程序结构可以分为串行模式和并发模式。
在串行模式中,一次只能处理一个请求,第二个请求必须等待第一个请求被处理完毕才能开始处理,适合于客户量比较小的情况;在并发模式中,针对每个请求创建一个线程,使得多个请求可以同时得到处理,因而提高了程序的性能。
但是,我们再进一步思考,如果有多个设备同时发出IO请求,那么在并发模式中也必须创建与之相同个数的线程,但是,CPU的个数是有限的,多于CPU个数的可运行线程就没有意义了,系统不得不在多个线程间进行上下文切换,以使得多个线程并发执行,这必然浪费宝贵的CPU周期。
另外,虽然创建线程较进程而言开销要小,但也并不意味着没有开销,尤其当数量比较大的时候。
在完成端口模型中,引入了线程池的概念,在应用程序初始化时创建一个线程池,在没有请求时处于等待状态,当请求完成时唤醒一个线程运行,运行完毕后重新放入线程池中,等待其他请求使用。
由于不必为每个请求创建一个线程,从而减少了线程的数量,省去了运行中途创建线程的开销,进一步提高了程序的性能。
二、完成端口的内部结构由于完成端口也是一个内核对象,故我们看一下它的内部结构。
完成端口对象包含五个不同的数据结构:1、设备列表:表相:设备句柄、完成键。
当调用CreateIoCompletionPort时将设备与完成端口关联起来,同时在该数据结构中创建一项。
每当向完成端口关联一个设备时,系统向该完成端口的设备列表中加入一条信息。
2、/index.php/Main_Page-->: 150%; mso-bidi-font-size: 10.5pt">I/O完成队列:表相:传输的字节数、32位完成键、I/O请求的OVERLAPPED结构指针、错误代码当一个设备的异步I/O请求完成时,系统检测该设备是否关联了一个完成端口,如果是,系统就向该完成端口的I/O完成队列中加入完成的I/O请求项。
基于完成端口的文件传输设计在计算机网络通信中, 端口是指在同一编号的计算机同步通信接口。
在计算机通信中, 信息在两台主机之间传递时, 需要指定一些数字, 这就是端口。
端口可以使计算机的不同进程之间通信,也可以使不同主机之间的通信更加方便。
完成端口(FTP)是一种用于文件传输的标准网络协议, 它允许用户在网络上工作,通过在计算机之间传输文件。
在本文中, 我们将讨论基于完成端口的文件传输设计, 并探讨其在实际应用中的作用和潜在的问题。
在基于完成端口的文件传输设计中, 主要有两个角色, 分别是服务器和客户端。
服务器存储文件, 而客户端负责上传和下载服务器上的文件。
基于完成端口的文件传输设计一般包含以下几个步骤:1. 建立连接在进行文件传输之前, 客户端需要与服务器建立连接。
这通常是通过TCP/IP协议完成的。
客户端首先向服务器发出连接请求, 之后根据服务器的响应建立连接。
2. 用户认证建立连接后, 客户端需要进行用户认证。
这通常是通过用户名和密码完成的。
用户认证成功后, 客户端可以开始进行文件传输操作。
3. 传输文件客户端可以通过完成端口向服务器上传或下载文件。
在完成端口的文件传输中, 通常有两种模式:主动模式和被动模式。
在主动模式下, 客户端负责建立数据连接, 而在被动模式下, 服务器负责建立数据连接。
基于完成端口的文件传输设计在实际应用中扮演着重要的角色。
其作用主要包括以下几个方面:1. 文件传输基于完成端口的文件传输设计允许用户在网络上进行文件传输。
无论是上传文件还是下载文件, 都可以通过完成端口完成。
这为用户提供了便利。
2. 数据安全基于完成端口的文件传输设计支持用户认证, 这意味着用户需要提供有效的用户名和密码才能进行文件传输。
这一机制可以确保数据的安全性, 防止未经授权的用户进行非法访问。
3. 灵活性完成端口的文件传输设计支持主动模式和被动模式, 用户可以根据具体情况选择合适的传输模式。
这种灵活性使得文件传输更加可靠和高效。
基于完成端口的文件传输设计文件传输是计算机网络中最为基础的功能之一,也是我们日常使用计算机时非常常见的功能。
基于计算机网络的文件传输方式有很多种,如FTP、HTTP、SMB、SCP等,这些协议通常都是基于不同的端口进行传输。
在设计文件传输方案时,我们需要考虑到不同操作系统的兼容性、传输速度、数据安全等问题。
本文将重点介绍基于完成端口的文件传输设计。
所谓完成端口(Well-known port),是指一类被分配给特定服务的标准端口号。
完成端口一般分配在0~1023端口之间,这些端口被保留给某些服务以用于连接请求的应答。
在设计文件传输方案时,我们可以基于完成端口来进行数据传输,这样可以方便运用各种操作系统的内部配置,并且可以保证数据传输的稳定性和安全性。
在基于完成端口的文件传输设计中,我们可以根据不同的服务类型来选择不同的端口号。
例如,Web服务器通常使用TCP端口号80,FTP服务器则通常使用TCP端口号21,在服务器端和客户端之间建立连接时,将使用这些端口来传输数据。
此外,在数据传输过程中我们还需要注意一些常见的问题,例如流量控制、错误检测、数据包的分割、堵塞控制等。
在数据传输的过程中,我们还需要使用一些数据传输协议来保证数据传输的可靠性。
TCP是目前应用最为广泛的一种传输协议,它提供了可靠的数据传输,保证数据在传输过程中不发生丢失或重复等问题,并且可以在网络出现拥塞时自动调整传输速度,保证数据传输的稳定性。
总体来说,基于完成端口的文件传输设计是一种简单而有效的数据传输方案。
它利用操作系统内置的完成端口号来进行数据传输,不仅方便了操作系统的配置,还可以保证数据传输的稳定性和安全性。
在实际应用中,我们可以结合不同的操作系统和网络协议来设计适合自己的文件传输方案,以便更好地完成各种文件传输任务。
关于完成端口(IOCP)的文章汇总- [C/C++]版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明/logs/32007489.html首先讨论一下I/O Completion Ports试图解决什么样的问题。
写一个IO Intensive服务器程序,对每一个客户请求生成一个新的child process/worker thread来处理,每个process/thread使用同步IO,这是最经典古老的解法了。
在这之上的改进是prefork 多个process 或者使用线程池。
(使用process或thread,原理都差不多,thread的context switch花销要比process switch要小。
为了论述简单,下面只讨论线程。
)这种结构的并发性并不高,哪怕你用C++, C甚至汇编来写,效率都不会很高,究其原因,在于两点:一.同步IO,每个线程大多数时间在等IO request的结束。
IO相对于CPU,那是极极慢的。
我翻了翻手里的Computer Architecture, A Quantitative Approach第二版,1996年出的,里面对CPU Register, CPU Cache, RAM, Disk,列的access time如下:Java代码1.Registers: 2-5 nano seconds2.CPU Cache: 3-10 nano seconds3.RAM: 80-400 nano seconds4.Disk: 5000000 nano seconds (5 milli seconds)如今CPU又按照摩尔定律发展了十年后,这个硬盘还是机械式的磁头移来移去读写,尽管如今disk controller都有cache,也在发展,但和CPU相比,差距越来越大。
(谁有最新数据可以贴上来。
)二.生成数量大大超过CPU总数的线程。
这样做有两个弊端,第一是每个线程要占用内存,Windows底下每个thread自己stack的省缺大小为1M,32位程序下一个用户程序最大能利用的内存也就3G,生成3000个线程,内存就没了。
完成端口模型程序设计端口模型是一种计算机网络中常用的用于描述通信过程的模型。
在计算机网络中,不同设备之间的通信是通过端口来实现的。
端口模型是通过使用端口号来组织和管理通信过程,使得设备之间可以准确地找到和连接到所需的服务。
下面将详细介绍端口模型的程序设计。
首先,我们需要定义端口的数据结构,包括端口号、协议类型、服务类型等信息。
可以使用一个类来表示端口,其中包含对端口号、协议类型、服务类型的定义和访问方法。
例如,可以定义一个名为"Port"的类:```pythonclass Port:def __init__(self, port_number, protocol_type, service_type): self.port_number = port_numberself.protocol_type = protocol_typeself.service_type = service_typedef get_port_number(self):return self.port_numberdef get_protocol_type(self):return self.protocol_typedef get_service_type(self):return self.service_type```然后,我们可以创建一个端口管理器类来管理各种端口。
端口管理器类可以包含一个端口列表,用于存储和查询已经定义的端口。
例子如下:```pythonclass PortManager:def __init__(self):self.ports = []def add_port(self, port):self.ports.append(port)def find_port(self, port_number):for port in self.ports:if port.get_port_number( == port_number:return portreturn None```在程序设计完成后,我们可以使用PortManager类来添加和查询端口。
手把手教你玩转网络编程模型系列之三完成端口(CompletionPort)篇----- By PiggyXP(小猪)前言完成端口的代码在两年前就已经写好了,但是由于许久没有写东西了,不知该如何提笔,所以这篇文档总是在酝酿之中……酝酿了两年之后,终于决定开始动笔了,但愿还不算晚…..这篇文档我非常详细并且图文并茂的介绍了关于网络编程模型中完成端口的编程模型方方面面的信息,从API的用法到使用的步骤,从完成端口的实现机理到实际使用的注意事项,都有所涉及,并且为了让朋友们更直观的体会完成端口的用法,本文附带了有详尽注释的使用MFC编写的图形界面的示例代码。
我的初衷是希望写一份互联网上能找到的最详尽的关于完成端口的教学文档,而且让对Socket编程略有了解的人都能够看得懂,都能学会如何来使用完成端口这么优异的网络编程模型,但是由于本人水平所限,不知道我的初衷是否实现了,但还是希望各位需要的朋友能够喜欢。
由于篇幅原因,本文假设你已经熟悉了利用Socket进行TCP/IP编程的基本原理,并且也熟练的掌握了多线程编程技术,太基本的概念我这里就略过不提了,网上的资料应该遍地都是。
本文档凝聚着笔者心血,如要转载,请指明原作者及出处,谢谢!不过代码没有版权,可以随便散播使用,欢迎改进,特别是非常欢迎能够帮助我发现Bug的朋友,以更好的造福大家。
^_^ 本文配套的示例源码下载地址(在我的下载空间里)/(里面的代码包括VC++2008/VC++2010编写的完成端口服务器端的代码,还包括一个对服务器端进行压力测试的测试代码,都是经过我精心调试过,并且带有非常详尽的代码注释的。
当然,作为教学代码,为了能够使得代码结构清晰明了,我还是对代码有所简化,如果想要用于产品开发,最好还是需要自己再完善一下,另外我的工程是用2010编写的,附带的2008工程不知道有没有问题,但是其中代码都是一样的,暂未测试)忘了嘱咐一下了,文章篇幅很长很长,基本涉及到了与完成端口有关的方方面面,一次看不完可以分好几次,中间注意休息,好身体才是咱们程序员最大的本钱!对了,还忘了嘱咐一下,因为本人的水平有限,虽然我反复修正了数遍,但文章和示例代码里肯定还有我没发现的错误和纰漏,希望各位一定要指出来,拍砖、喷我,我都能Hold住,但是一定要指出来,我会及时修正,因为我不想让文中的错误传遍互联网,祸害大家。
IOCP模型总结(转)2009-06-26 17:22IOCP(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都完成了,那么就在完成端口那里排成一个队列)。
关于重叠I/O,参考《WinSock重叠I/O模型》;关于完成端口的概念及内部机制,参考译文《深度探索I/O完成端口》。
完成端口对象取代了WSAAsyncSelect中的消息驱动和WSAEventSelect中的事件对象,当然完成端口模型的内部机制要比WSAAsyncSelect和WSAEventSelect模型复杂得多。
IOCP内部机制如下图所示:在WinSock中编写完成端口程序,首先要调用CreateIoCompletionPort 函数创建完成端口。
其原型如下:WINBASEAPI HANDLE WINAPICreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,DWORD CompletionKey,DWORD NumberOfConcurrentThreads );第一次调用此函数创建一个完成端口时,通常只关注NumberOfConcurrentThreads,它定义了在完成端口上同时允许执行的线程数量。
一般设为0,表示系统内安装了多少个处理器,便允许同时运行多少个线程为完成端口提供服务。
每个处理器各自负责一个线程的运行,避免了过于频繁的线程上下文切换。
hCompletionPort =CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)这个类比重叠I/O事件通知模型中(WSA)CreateEvent。
然后再调用GetSystemInfo(&SystemInfo);取得系统安装的处理器的个数SystemInfo.dwNumberOfProcessors,根据CPU数创建线程池,在完成端口上,为已完成的I/O请求提供服务。
一般线程池的规模,即线程数 = CPU数*2+2。
下面的代码片段演示了线程池的创建。
// 创建线程池,规模为CPU数的两倍for(int i= 0; i< SystemInfo.dwNumberOfProcessors* 2; i++) {HANDLE ThreadHandle;// 创建一个工作线程,并将完成端口作为参数传递给它。
浅谈完成端口网络通讯模型在计算机网络通讯方式中使用TCP 协议的C/S程序是其中非常重要的一种,在这种程序模型中服务器端程序开发又是其中的重点,本文简单地谈论在Windows系统下性能最高的一种网络通讯模型——完成端口通讯模型。
标签:完成端口网络通讯模型;TCP 协议;网络通讯方式在网络世界中,有两种网络通讯方式。
第一种是我们最常见的Web形式,平时使用360、火狐、UC等浏览器都是使用这种模式来访问网络数据。
第二种是C/S服务器客户端模式即在远程服务器上运行服务器程序,在终端机上包括传统的PC机和现在的移动终端Android和iOS设备上的APP运行客户端程序,服务器程序和客户机程序通过在IP地址和端口上使用TCP协议连接。
在第二种方式中服务器程序往往是编程中比较复杂的部分。
本文讨论在Windows下性能最高的通讯模型——完成端口模型。
完成端口是Windows下使用异步非阻塞方式的系统内核对象,它的主要功能是提供一个服务器程序的内核部分,以便让几万个客户端程序接入,负责同时与这几万个客户端程序接收和发送数据(即网络I/O操作)。
其程序设计包括下面三个实体部分,第一个是程序主线程,第二个是负责与客户端收发和接收数据的线程,这里我们称其为工作线程,第三个是操作系统本身。
首先主线程开始三个工作,第一工作是负责初始化套接字库,由于要接入上万个客户端,要为每一个客户端提供套接字,而新建一个套接字需要花费比较多的时间,所以在初始化时,要先多建一些套接字,等有新客户端连接上来的时候直接用上而不是新建以便加快系统速度。
工作线程具体的数量由网络状态来确定,比如并发数量很高的话就准备比较多的新建套接字,并发量如果不高的话就不准备太多。
在新建完一定数量套接字后把它们组成队列。
第二个工作是新建一个完成端口,把新建的套接字和完成端口绑定好以便监听。
第三个工作是生成一定数量的工作线程负责网络I/O操作也就是负责发送数据到客户端和接收客户端发送来的数据,由于要避免线程之间的太多切换,我们生成CPU核心数两倍加二个工作线程。
基于完成端口的文件传输设计随着互联网的迅猛发展,文件传输在我们的日常生活中变得愈发重要。
无论是个人用户还是企业,都需要在不同设备之间快速、安全地传输文件。
在这个背景之下,基于完成端口的文件传输成为了一种备受关注的技术。
本文将对基于完成端口的文件传输进行设计,并分析其优势和应用场景。
一、什么是完成端口?完成端口(FTP,File Transfer Protocol)是一种用于在网络上进行文件传输的协议。
它是一种标准的通信协议,用来在计算机之间进行文件传输。
完成端口可以在不同的操作系统之间传输文件,支持不同的文件格式,包括文本文件和二进制文件。
基于完成端口的文件传输通常使用客户端-服务器模式。
客户端发送一个连接请求给服务器端,并指定一个端口号。
服务器端接受连接请求,并在另一个端口号上打开一个连接,通过这个连接来传输文件。
完成端口可以通过明文传输或者利用 SSL/TLS 加密进行传输,确保文件传输的安全性和隐私性。
1. 架构设计基于完成端口的文件传输通常涉及到客户端和服务器端两个角色。
在设计时,我们需要确定这两个角色之间的通信方式,并考虑安全性和可靠性。
客户端负责发送连接请求给服务器端,并进行文件的上传和下载操作。
服务器端负责接受连接请求,并提供文件存储和传输功能。
在设计时,需要考虑到客户端和服务器端运行在不同的操作系统上,需要考虑如何解决不同操作系统之间的兼容性问题。
2. 传输流程在基于完成端口的文件传输设计中,传输流程是至关重要的。
传输流程需要确保文件的安全传输和完整性。
传输流程通常包括以下几个步骤:首先客户端发送连接请求给服务器端,服务器端接受连接请求并打开一个新的端口用于传输文件。
然后客户端发送文件传输命令给服务器端,服务器端接受命令并开始传输文件。
一旦文件传输完成,服务器端发送传输完成的消息给客户端,客户端接收到消息后关闭连接。
在传输流程中,需要考虑异常情况的处理,比如网络中断、服务器宕机等情况。
基于完成端口的文件传输设计随着技术的不断发展和互联网的普及,文件传输已经成为人们日常工作和生活中不可或缺的一部分。
传统的文件传输方式主要依赖于电子邮件、网盘等方式,但这些方式存在着文件大小限制、传输速度慢、安全性低等问题。
为了解决这些问题,我们可以设计一种基于完成端口的文件传输方式。
基于完成端口的文件传输是一种利用传输层的TCP协议进行文件传输的方法。
完成端口是指客户端在主动发起连接后,再由操作系统为其分配的端口号。
这种方式可以避免传统方式中一些常用端口被占用的问题,提供更好的稳定性和安全性。
具体的实现过程可以分为以下几个步骤:1. 客户端发起连接:客户端通过指定目标IP地址和端口号,发起连接请求。
2. 服务端响应:服务端收到客户端的连接请求后,通过完成端口为客户端分配一个可用的端口号,并向客户端返回连接成功的消息。
3. 文件传输准备:客户端将要传输的文件进行分割,并对每个分块进行编号和校验。
客户端生成一个传输标识,用于标记这个文件传输的唯一性。
4. 数据传输:客户端将文件的数据分块通过TCP协议传输给服务端。
服务端收到数据后进行校验,确保数据的完整性和准确性。
5. 文件合并:服务端接收到所有数据分块后,进行校验并将其重新组合成完整的文件。
6. 传输完成:服务端将传输完成的消息发送给客户端,并关闭连接。
基于完成端口的文件传输方式相对于传统的文件传输方式具有以下优点:1. 传输速度快:完成端口方式利用了TCP协议的可靠性和高效性,能够更快速地进行文件传输。
2. 安全性高:完成端口方式需要进行身份验证和连接请求的确认,能够提供更高的数据传输安全性。
3. 文件大小无限制:完成端口方式没有传统方式中的文件大小限制,能够适应不同大小的文件传输需求。
4. 稳定性好:完成端口方式采用TCP协议作为传输层协议,能够提供可靠的数据传输,减少数据丢失和传输失败的可能性。
基于完成端口的文件传输是一种高效、安全且稳定的文件传输方式。
Socket编程模型之完成端口模型一、回顾重叠IO模型用完成例程来实现重叠I/O比用事件通知简单得多。
在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后CompletionROUTINE可以被内核调用。
如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。
完成例程内的实现代码比较简单,它取出接收到的数据,然后将数据原封不动的发送给客户端,最后重新激活另一个WSARecv异步操作。
注意,在这里用到了“尾随数据”。
我们在调用WSARecv的时候,参数lpOverlapped实际上指向一个比它大得多的结构PER_IO_OPERATION_DATA,这个结构除了WSAOVERLAPPED以外,还被我们附加了缓冲区的结构信息,另外还包括客户端套接字等重要的信息。
这样,在完成例程中通过参数lpOverlapped拿到的不仅仅是WSAOVERLAPPED结构,还有后边尾随的包含客户端套接字和接收数据缓冲区等重要信息。
这样的C语言技巧在我介绍完成端口的时候还会使用到。
二、完成端口模型“完成端口”模型是迄今为止最为复杂的一种I/O模型。
然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。
因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。
要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!完成端口模型是我最喜爱的一种模型。
WinSocket模型的探讨——完成端口模型众所皆知,完成端口是在WINDOWS平台下效率最高,扩展性最好的IO模型,特别针对于WINSOCK的海量连接时,更能显示出其威力。
其实建立一个完成端口的服务器也很简单,只要注意几个函数,了解一下关键的步骤也就行了。
这是篇完成端口入门级的文章,分为以下几步来说明完成端口:函数常见问题以及解答步骤例程1、函数:我们在完成端口模型下会使用到的最重要的两个函数是:CreateIoCompletionPort、GetQueuedCompletionStatusCreateIoCompletionPort 的作用是创建一个完成端口和把一个IO句柄和完成端口关联起来:// 创建完成端口HANDLE CompletionPort = CreateIoCompletionPort(INV ALID_HANDLE_V ALUE, NULL, 0, 0);// 把一个IO句柄和完成端口关联起来,这里的句柄是一个socket 句柄CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);其中第一个参数是句柄,可以是文件句柄、SOCKET句柄。
第二个就是我们上面创建出来的完成端口,这里就把两个东西关联在一起了。
第三个参数很关键,叫做PerHandleData,就是对应于每个句柄的数据块。
我们可以使用这个参数在后面取到与这个SOCKET对应的数据。
最后一个参数给0,意思就是根据CPU的个数,允许尽可能多的线程并发执行。
GetQueuedCompletionStatus 的作用就是取得完成端口的结果:// 从完成端口中取得结果GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)第一个参数是完成端口第二个参数是表明这次的操作传递了多少个字节的数据第三个参数是OUT类型的参数,就是前面CreateIoCompletionPort传进去的单句柄数据,这里就是前面的SOCKET句柄以及与之相对应的数据,这里操作系统给我们返回,让我们不用自己去做列表查询等操作了。
论述socket I/O 模型之完成端口2007-05-22 13:55转贴自:/kenlistian/archive/2006/05/26/7666.html转载,这篇文章非常经典,特此收录---Email:kruglinski_at_gmail_dot_comBlog:早在两年前我就已经能很熟练的运用完成端口这种技术了,只是一直没有机会将它用在什么项目中,这段时间见到这种技术被过分炒作,过分的神秘化,就想写一篇解释它如何工作的文章.想告诉大家它没有传说中的那么高深难懂!有什么错误的地方还请高人指正.转载请注明出处及作者,谢谢!以一个文件传输服务端为例,在我的机器上它只起两个线程就可以为很多个个客户端同时提供文件下载服务,程序的性能会随机器内CPU个数的增加而线性增长,我尽可能做到使它清晰易懂,虽然程序很小却用到了NT 5的一些新特性,重叠IO,完成端口以及线程池,基于这种模型的服务端程序应该是NT系统上性能最好的了.首先.做为完成端口的基础,我们应该理解重叠IO,这需要你已经理解了内核对象及操作系统的一些概念概念,什么是信号/非信号态,什么是等待函数,什么是成功等待的副作用,什么是线程挂起等,如果这些概令还没有理解,你应该先看一下Windows 核心编程中的相关内容.如果已经理解这些,那么重叠IO对你来说并不难.你可以这样认为重叠IO,现在你已经进入一个服务器/客户机环境, 请不要混淆概念,这里的服务器是指操作系统,而客户机是指你的程序(它进行IO操作),是当你进行IO操作(send,recv,writefile, readfile....)时你发送一个IO请求给服务器(操作系统),由服务器来完成你需要的操作,然后你什么事都没有了,当服务器完成IO请求时它会通知你,当然在这期间你可以做任何事,一个常用的技巧是在发送重叠IO请求后,程序在一个循环中一边调用PeekMessage, TranslateMessage和DispatchMessage更新界面,同时调用GetOverlappedResult等待服务器完成IO操作, 更高效一点的做法是使用IO完成例程来处理服务器(操作系统)返回的结果,但并不是每个支持重叠IO操作的函数都支持完成例程如TransmitFile 函数.例1.一次重叠写操作过程(GetOverlappedResult方法):1.填写一个OVERLAPPED结构2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针)3.做其它事(如更新界面)4.GetOverlappedResult取操作结果5.如果IO请求没有完成,并且没有出错则回到期36.处理IO操作结果例2.一次重叠写操作过程(完成例程方法):1.填写一个OVERLAPPED结构2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针),并指定完成例程3.做其它事(如更新界面)4.当完成例程被调用说明IO操作已经完成或出错,现在可以对操作结果进行处理了如果你已经理解上面的概念,就已经很接近IO完成端口了,当然这只是很常规的重叠操作它已经非常高效,但如果再结合多线程对一个File或是Socket进行重叠IO操作就会非常复杂,通常程序员很难把握这种复杂度.完成端口可以说就是为了充分发挥多线程和重叠IO操作相结合的性能而设计的.很多人都说它复杂,其实如果你自己实现一个多线程的对一个File或是Socket进行重叠IO操作的程序(注意是多个线程对一个HANDLE或SOCKET进行重叠 IO操作,而不是启一个线程对一个HANDLE进行重叠IO操作)就会发现完成端口实际上简化了多线程里使用重叠IO的复杂度,并且性能更高,性能高在哪?下面进行说明.我们可能写过这样的服务端程序:例3.主程序:1.监听一个端口2.等待连接3.当有连接来时4.启一个线程对这个客户端进行处理5.回到2服务线程:1.读客户端请求2.如果客户端不再有请求,执行63.处理请求4.返回操作结果5.回到16.退出线程这是一种最简单的网络服务器模型,我们把它优化一下例4.主程序:1.开一个线程池,里面有机器能承受的最大线程数个线程,线程都处于挂起(suspend)状态1.监听一个端口2.等待连接3.当有连接来时4.从线程池里Resume一个线程对这个客户端进行处理5.回到2服务线程与例3模型里的相同,只是当线程处理完客户端所有请求后,不是退出而是回到线程池,再次挂起让出CPU时间,并等待为下一个客户机服务.当然在此期间线程会因为IO操作(服务线程的第1,5操作,也许还有其它阻塞操作)挂起自己,但不会回到线程池,也就是说它一次只能为一个客户端服务.这可能是你能想到的最高效的服务端模型了吧!它与第一个服务端模型相比少了很多个用户态到内核态的CONTEXT Switch,反映也更加快速,也许你可能觉得这很微不足道,这说明你缺少对大规模高性能服务器程序(比如网游服务端)的认识,如果你的服务端程序要对几千万个客户端进行服务呢?这也是微软Windows NT开发组在NT 5以上的系统中添加线程池的原因.思考一下什么样的模型可以让一个线程为多个客户端服务呢!那就要跳出每来一个连接启线程为其服务的固定思维模式,我们把线程服务的最小单元分割为单独的读或写操作(注意是读或写不是读和写),而不是一个客户端从连接到断开期间的所有读写操作.每个线程都使用重叠IO进行读写操作,投递了读写请求后线程回到线程池,等待为其它客户机服务, 当操作完成或出错时再回来处理操作结果,然后再回到线程池.看看这样的服务器模型:例5.主程序:1.开一个线程池,里面有机器内CPU个数两倍的线程,线程都处于挂起(suspend)状态,它们在都等处理一次重叠IO操作的完成结果1.监听一个端口2.等待连接3.当有连接来时4.投递一个重叠读操作读取命令5.回到2服务线程:1.如果读完成,则处理读取的内容(如HTTP GET命令),否则执行32.投递一个重叠写操作(如返回HTTP GET命令需要的网页)3.如果是一个写操作完成,可以再投递一个重叠读操作,读取客户机的下一个请求,或者是关闭连接(如HTTP协议里每发完一个网页就断开)4.取得下一个重叠IO操作结果,如果IO操作没有完成或没有IO操作则回到线程池假设这是一个WEB服务器程序,可以看到工作者线程是以读或写为最小的工作单元运行的,在主程序里面进行了一次重叠读操作当读操作完成时一个线程池中的一个工作者线程被激活取得了操作结果,处理GET或POST命令,然后发送一个网页内容,发送也是一个重叠操作,然后处理对其它客户机的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.可以看到使用这种模型发送和接收可以是也可以不是一个线程.当发送操作完成时,线程池中的一个工作者线程池激活,它关闭连接(HTTP协议),然后处理其它的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.看看在这样的模型中一个线程怎么为多个客户端服务,同样是模拟一个WEB服务器例子:假如现在系统中有两个线程,ThreadA,ThreadB它们在都等处理一次重叠IO操作的完成结果当一个客户机ClientA连接来时主程序投递一个重叠读操作,然后等待下一个客户机连接,当读操作完成时ThreadA被激活,它收到一个HTTP GET命令,然后ThreadA使用重叠写操作发送一个网页给ClientA,然后立即回到线程池等待处理下一个IO操作结果,这时发送操作还没有完成, 又有一个客户机ClientB连接来,主程序再投递一个重叠读操作,当读操作完成时ThreadA(当然也可能是ThreadB)再次被激活,它重复同样步骤,收到一个GET命令,使用重叠写操作发送一个网页给ClientB,这次它没有来得及回到线程池时,又有一个连接ClientC连入,主程序再投递一个重叠读操作,读操作完成时ThreadB被激活(因为ThreadA 还没有回到线程池)它收到一个HTTP GET命令,然后ThreadB使用重叠写操作发送一个网页给ClientC,然后ThreadB回到线程池,这时ThreadA也回到了线程池.可以想象现在有三个挂起的发送操作分别是ThreadA发送给ClientA和ClientB 的网页,以及ThreadB发送给ClientC的网页,它们由操作系统内核来处理.ThreadA和ThreadB现在已经回到线程池,可以继续为其它任何客户端服务.当对ClientA的重叠写操作已经完成,ThreadA(也可以是ThreadB)又被激活它关闭与ClientA连接,但还没有回到线程池,与此同时发送给ClientB的重叠写操作也完成,ThreadB被激活(因为ThreadA还没有回到线程池)它关闭与ClientB的连接,然后回到线程池,这时ClientC的写操作也完成,ThreadB再次被激活(因为ThreadA还是没有回到线程池),它再关闭与ClientC的连接,这时 ThreadA回到线程池,ThreadB也回到线程池.这时对三个客户端的服务全部完成.可以看到在整个服务过程中,"建立连接","读数据","写数据"和"关闭连接"等操作是逻辑上连续而实际上分开的.到现在为止两个线程处理了三次读操作和三次写操作,在这些读写操作过程中所出现的状态机(state machine)是比较复杂的,我们模拟的是经过我简化过的,实际上的状态要比这个还要复杂很多,然而这样的服务端模型在客户端请求越多时与前两个模型相比的性能越高.而使用完成端口我们可以很容易实现这样的服务器模型.微软的IIS WEB服务器就是使用这样的服务端模型,很多人说什么阿帕奇服务器比IIS的性能好什么什么的我表示怀疑,除非阿帕奇服务器可以将线程分割成,为更小的单元服务,我觉得不太可能!这种完成端口模型已经将单个读或写操作作为最小的服务单元,我觉得在相同机器配置的情况下IIS的性能要远远高于其它WEB服务器,这也是从实现机理上来分析的,如果出现性能上的差别可能是在不同的操作系统上,也许Linux的内核比Windows的要好,有人真的研究过吗?还是大家一起在炒作啊.对于状态机概念,在很多方面都用到,TCPIP中有,编译原理中有,OpengGL中有等等,我的离散数学不好(我是会计专业不学这个),不过还是搞懂了些,我想如果你多花些时间看,还是可以搞懂的.最后是一个简单的文件传输服务器程序代码,只用了两个线程(我的机器里只有一块CPU)就可以服务多个客户端.我调试时用它同时为6个nc客户端提供文件下载服务都没有问题,当然更多也不会有问题,只是略为使用了一下NT 5的线程池和完成端口技术就可以有这样高的性能,更不用说IIS的性能咯!希望大家不要陷在这个程序的框架中,Ctrl+C,Ctrl+V 没有什么意义,要理解它的实质.程序使用Visual C++ 6.0 SP5+2003 Platform SDK编译通过,在Windows XP Professional下调试运行通过.程序运行的最低要求是Windows 2000操作系统./******************************************************************** created: 2005/12/24created: 24:12:2005 20:25modified: 2005/12/24filename: d:\vcwork\iocomp\iocomp.cppfile path: d:\vcwork\iocompfile base: iocompfile ext: cppauthor: kruglinski(kruglinski_at_gmail_dot_com)purpose: 利用完成端口技术实现的高性能文件下载服务程序********************************************************************* /#define _WIN32_WINNT 0x0500#include <cstdlib>#include <clocale>#include <ctime>#include <iostream>//一使用输入输出流程序顿时增大70K#include <vector>#include <algorithm>#include <winsock2.h>#include <mswsock.h>using namespace std;#pragma comment(lib,"ws2_32.lib")#pragma comment(lib,"mswsock.lib")const int MAX_BUFFER_SIZE=1024;const int PRE_SEND_SIZE=1024;const int QUIT_TIME_OUT=3000;const int PRE_DOT_TIMER=QUIT_TIME_OUT/80;typedef enum{ IoTransFile,IoSend,IoRecv,IoQuit } IO_TYPE;typedef struct{SOCKET hSocket;SOCKADDR_IN ClientAddr;}PRE_SOCKET_DATA,*PPRE_SOCKET_DATA;typedef struct{OVERLAPPED oa;WSABUF DataBuf;char Buffer[MAX_BUFFER_SIZE];IO_TYPE IoType;}PRE_IO_DATA,*PPRE_IO_DATA;typedef vector<PPRE_SOCKET_DATA> SocketDataVector;typedef vector<PPRE_IO_DATA> IoDataVector;SocketDataVector gSockDataVec;IoDataVector gIoDataVec;CRITICAL_SECTION csProtection;char* TimeNow(void){time_t t=time(NULL);tm *localtm=localtime(&t);static char timemsg[512]={ 0 };strftime(timemsg,512,"%Z: %B %d %X,%Y",localtm);return timemsg;}BOOL TransFile(PPRE_IO_DATA pIoData,PPRE_SOCKET_DATA pSocketData,DWORDdwNameLen){//这一句是为nc做的,你可以修改它pIoData->Buffer[dwNameLen-1]='\0';HANDLEhFile=CreateFile(pIoData->Buffer,GENERIC_READ,0,NULL,OPEN_EXISTING,0, NULL);BOOL bRet=FALSE;if(hFile!=INVALID_HANDLE_VALUE){cout<<"Transmit File "<<pIoData->Buffer<<" to client"<<endl; pIoData->IoType=IoTransFile;memset(&pIoData->oa,0,sizeof(OVERLAPPED));*reinterpret_cast<HANDLE*>(pIoData->Buffer)=hFile;TransmitFile(pSocketData->hSocket,hFile,GetFileSize(hFile,NU LL),PRE_SEND_SIZE,reinterpret_cast<LPOVERLAPPED>(pIoData),NULL,TF_USE _SYSTEM_THREAD);bRet=WSAGetLastError()==WSA_IO_PENDING;}elsecout<<"Transmit File "<<"Error:"<<GetLastError()<<endl;return bRet;}DWORD WINAPI ThreadProc(LPVOID IocpHandle){DWORD dwRecv=0;DWORD dwFlags=0;HANDLE hIocp=reinterpret_cast<HANDLE>(IocpHandle);DWORD dwTransCount=0;PPRE_IO_DATA pPreIoData=NULL;PPRE_SOCKET_DATA pPreHandleData=NULL;while(TRUE){if(GetQueuedCompletionStatus(hIocp,&dwTransCount,reinterpret_cast<LPDWORD>(&pPreHandleData),reinterpret_cast<LPOVERLAPPED*>(&pPreIoData),INFINITE)) {if(0==dwTransCount&&IoQuit!=pPreIoData->IoType){cout<<"Client:"<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr) <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port )<<" is closed"<<endl;closesocket(pPreHandleData->hSocket);EnterCriticalSection(&csProtection);IoDataVector::iteratoritrIoDelete=find(gIoDataVec.begin(),gIoDataVec.end(),pPreIoData);gIoDataVec.erase(itrIoDelete);SocketDataVector::iteratoritrSockDelete=find(gSockDataVec.begin(),gSockDataVec.end(),pPreHandle Data);gSockDataVec.erase(itrSockDelete);LeaveCriticalSection(&csProtection);delete *itrIoDelete;delete *itrSockDelete;continue;}switch(pPreIoData->IoType){case IoTransfile:cout<<"Client:"<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr) <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port )<<" Transmit finished"<<endl;CloseHandle(*reinterpret_cast<HANDLE*>(pPreIoData->B uffer));goto LRERECV;case IoSend:cout<<"Client:"<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr) <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port )<<" Send finished"<<endl;LRERECV:pPreIoData->IoType=IoRecv;pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));WSARecv(pPreHandleData->hSocket,&pPreIoData->DataBuf ,1,&dwRecv,&dwFlags,reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NU LL);break;case IoRecv:cout<<"Client:"<<inet_ntoa(pPreHandleData->ClientAddr.sin_addr) <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port )<<" recv finished"<<endl;pPreIoData->IoType=IoSend;if(!TransFile(pPreIoData,pPreHandleData,dwTransCount )){memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));strcpy(pPreIoData->DataBuf.buf,"File transmit error!\r\n");pPreIoData->DataBuf.len=strlen(pPreIoData->DataB uf.buf);WSASend(pPreHandleData->hSocket,&pPreIoData->Dat aBuf,1,&dwRecv,dwFlags,reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData ),NULL);}break;case IoQuit:goto LQUIT;default:;}}}LQUIT:return 0;}HANDLE hIocp=NULL;SOCKET hListen=NULL;BOOL WINAPI ShutdownHandler(DWORD dwCtrlType){PRE_SOCKET_DATA PreSockData={ 0 };PRE_IO_DATA PreIoData={ 0 };PreIoData.IoType=IoQuit;if(hIocp){PostQueuedCompletionStatus(hIocp,1,reinterpret_cast<ULONG_PTR>(&PreSockData),reinterpret_cast<LPOVERLAPPED>(&PreIoData));cout<<"Shutdown at "<<TimeNow()<<endl<<"wait for a moment please"<<endl;//让出CPU时间,让线程退出for(int t=0;t<80;t+=1){Sleep(PRE_DOT_TIMER);cout<<".";}CloseHandle(hIocp);}int i=0;for(;i<gSockDataVec.size();i++){PPRE_SOCKET_DATA pSockData=gSockDataVec[i];closesocket(pSockData->hSocket);delete pSockData;}for(i=0;i<gIoDataVec.size();i++){PPRE_IO_DATA pIoData=gIoDataVec[i];delete pIoData;}DeleteCriticalSection(&csProtection);if(hListen)closesocket(hListen);WSACleanup();exit(0);return TRUE;}LONG WINAPI MyExceptionFilter(struct _EXCEPTION_POINTERS*ExceptionInfo){ShutdownHandler(0);return EXCEPTION_EXECUTE_HANDLER;}u_short DefPort=8182;int main(int argc,char **argv){if(argc==2)DefPort=atoi(argv[1]);InitializeCriticalSection(&csProtection);SetUnhandledExceptionFilter(MyExceptionFilter);SetConsoleCtrlHandler(ShutdownHandler,TRUE);hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);WSADATA data={ 0 };WSAStartup(0x0202,&data);hListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(INVALID_SOCKET==hListen){ShutdownHandler(0);}SOCKADDR_IN addr={ 0 };addr.sin_family=AF_INET;addr.sin_port=htons(DefPort);if(bind(hListen,reinterpret_cast<PSOCKADDR>(&addr),sizeof(addr))==SOCKET_ERROR){ShutdownHandler(0);}if(listen(hListen,256)==SOCKET_ERROR)ShutdownHandler(0);SYSTEM_INFO si={ 0 };GetSystemInfo(&si);si.dwNumberOfProcessors<<=1;for(int i=0;i<si.dwNumberOfProcessors;i++){QueueUserWorkItem(ThreadProc,hIocp,WT_EXECUTELONGFUNCTION); }cout<<"Startup at "<<TimeNow()<<endl<<"work on port "<<DefPort<<endl<<"press CTRL+C to shutdown"<<endl<<endl<<endl;while(TRUE){int namelen=sizeof(addr);memset(&addr,0,sizeof(addr));SOCKEThAccept=accept(hListen,reinterpret_cast<PSOCKADDR>(&addr),&namelen);if(hAccept!=INVALID_SOCKET){cout<<"accept aclient:"<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;PPRE_SOCKET_DATA pPreHandleData=new PRE_SOCKET_DATA;pPreHandleData->hSocket=hAccept;memcpy(&pPreHandleData->ClientAddr,&addr,sizeof(addr));CreateIoCompletionPort(reinterpret_cast<HANDLE>(hAccept) ,hIocp,reinterpret_cast<DWORD>(pPreHandleData),0);PPRE_IO_DATA pPreIoData=new(nothrow) PRE_IO_DATA;if(pPreIoData){EnterCriticalSection(&csProtection);gSockDataVec.push_back(pPreHandleData);gIoDataVec.push_back(pPreIoData);LeaveCriticalSection(&csProtection);memset(pPreIoData,0,sizeof(PRE_IO_DATA));pPreIoData->IoType=IoRecv;pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;pPreIoData->DataBuf.buf=pPreIoData->Buffer;DWORD dwRecv=0;DWORD dwFlags=0;WSARecv(hAccept,&pPreIoData->DataBuf,1,&dwRecv,&dwFlags,reinterpret_cast<WSAOVERLAPPED*>(pPreIoData),NUL L);}else{delete pPreHandleData;closesocket(hAccept);}}}return 0;}参考资料:《MSDN 2001》《Windows 网络编程》《Windows 核心编程》《TCP/IP详解》。
基于完成端口的文件传输设计基于完成端口(FTP)是一种文件传输协议,它允许用户在计算机之间传输文件。
FTP 使用两个主要的端口,一个用于数据传输,另一个用于控制连接。
以下是基于完成端口的文件传输设计的详细解释。
1. 设计目标:设计一个能够稳定、高效地传输文件的系统,使用户能够方便地在计算机之间传输文件。
2. 系统组成:- 服务器端:接收和处理用户的文件传输请求。
- 客户端:发送文件传输请求并接收服务器的响应。
- 数据传输通道:用于在服务器和客户端之间传输文件数据。
3. 实现步骤:- 用户登录:用户通过客户端登录服务器,提供用户名和密码进行身份验证。
- 浏览文件列表:一旦登录成功,用户可以浏览服务器上的文件列表。
- 文件上传:用户可以选择要上传的文件,并将其发送到服务器。
客户端将文件数据划分为小块,并通过数据传输通道发送给服务器。
- 文件下载:用户可以选择要下载的文件,并从服务器下载到本地计算机。
客户端通过数据传输通道接收文件数据,并将其组合成完整的文件。
4. 安全性保证:- 身份验证:用户需要提供正确的用户名和密码才能登录服务器,确保只有授权用户才能访问系统。
- 数据加密:使用安全套接字层(SSL)或传输层安全性(TLS)协议对文件数据进行加密,保护数据的机密性和完整性。
- 文件权限控制:服务器设置文件权限以控制用户对文件的访问权限。
5. 错误处理:- 连接错误:如果在传输期间发生连接错误,客户端需要能够重新连接服务器,并从断点处继续传输文件。
- 命令错误:如果客户端发送错误的命令或参数,服务器应该能够返回错误响应并提示用户重新发送正确的命令。
- 文件传输错误:如果在文件传输期间出现错误,如文件损坏或丢失,客户端应该能够重新传输文件或提示用户重新上传/下载文件。
6. 性能优化:- 数据压缩:在传输过程中,可以使用数据压缩算法对文件进行压缩,减小文件的大小,加快传输速度。
- 并行传输:可以将文件分为多个部分,并同时传输这些部分,以提高传输速度。
理解I/O Completion Port(完成端口)欢迎阅读此篇IOCP教程。
我将先给出IOCP的定义然后给出它的实现方法,最后剖析一个Echo 程序来为您拨开IOCP的谜云,除去你心中对IOCP的烦恼。
OK,但我不能保证你明白IOCP 的一切,但我会尽我最大的努力。
以下是我会在这篇文章中提到的相关技术:I/O端口同步/异步堵塞/非堵塞服务端/客户端多线程程序设计Winsock API 2.0在这之前,我曾经开发过一个项目,其中一块需要网络支持,当时还考虑到了代码的可移植性,只要使用select,connect,accept,listen,send还有recv,再加上几个#ifdef的封装以用来处理Winsock和BSD套接字[socket]中间的不兼容性,一个网络子系统只用了几个小时很少的代码就写出来了,至今还让我很回味。
那以后很长时间也就没再碰了。
前些日子,我们策划做一个网络游戏,我主动承担下网络这一块,想想这还不是小case,心里偷着乐啊。
网络游戏好啊,网络游戏为成百上千的玩家提供了乐趣和令人着秘的游戏体验,他们在线上互相战斗或是加入队伍去战胜共同的敌人。
我信心满满的准备开写我的网络,于是乎,发现过去的阻塞同步模式模式根本不能拿到一个巨量多玩家[MMP]的架构中去,直接被否定掉了。
于是乎,就有了IOCP,如果能过很轻易而举的搞掂IOCP,也就不会有这篇教程了。
下面请诸位跟随我进入正题。
什么是IOCP?先让我们看看对IOCP的评价I/O完成端口可能是Win32提供的最复杂的内核对象。
[Advanced Windows 3rd] Jeffrey Richter这是[IOCP]实现高容量网络服务器的最佳方法。
[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports]Microsoft Corporation完成端口模型提供了最好的伸缩性。
哈尔滨理工大学毕业设计题目:____________________院、系:____________________姓名:____________________指导教师:____________________系主任:____________________年月日摘要本文以非常详细并且图文并茂的介绍了关于网络编程模型中完成端口的方方面面的信息,从API的用法到使用的步骤,从完成端口的实现机理到实际使用的注意事项,都有所涉及,并且为了让朋友们更直观的体会完成端口的用法,本文附带了有详尽注释的使用MFC编写的图形界面的示例代码。
关键词完成端口;线程同步;内核对象;异步通信Key words:目录网络模型之完成端口(Completion Port)分析第1章完成端口的优点1.1 完成端口的强大性。
我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S 通信模式中性能最好的网络通信模型,没有之一;甚至连和它性能接近的通信模型都没有。
1.2完成端口和其他网络通信方式最大的区别。
1.2.1首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。
1.2.2各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的。
1.2.3而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,这种神奇的效果具体是如何实现的请看下文。
1.3完成端口的实际应用。
完成端口被广泛的应用于各个高性能服务器程序上,例如著名的Apache….如果你想要编写的服务器端需要同时处理的并发客户端连接数量有数百上千个的话,那不用纠结了,就是它了。
第2章完成端口程序的运行演示首先,我们先来看一下完成端口在笔者的PC机上的运行表现,笔者的PC配置如下:大体就是i7 2600 + 16GB内存,我以这台PC作为服务器,简单的进行了如下的测试,通过Client生成3万个并发线程同时连接至Server,然后每个线程每隔3秒钟发送一次数据,一共发送3次,然后观察服务器端的CPU和内存的占用情况。
如图所示,是客户端3万个并发线程发送共发送9万条数据的log截图下图是服务器端接收完毕3万个并发线程和每个线程的3份数据后的log截图最关键是下图,是服务器端在接收到28000个并发线程的时候,CPU占用率的截图,使用的软件是大名鼎鼎的Process Explorer,因为相对来讲这个比自带的任务管理器要准确和精确一些。
我们可以发现一个令人惊讶的结果,采用了完成端口的Server程序(蓝色横线所示)所占用的CPU才为 3.82%,整个运行过程中的峰值也没有超过4%,是相当气定神闲的……哦,对了,这还是在Debug环境下运行的情况,如果采用Release方式执行,性能肯定还会更高一些,除此以外,在UI上显示信息也很大成都上影响了性能。
相反采用了多个并发线程的Client程序(紫色横线所示)居然占用的CPU高达11.53%,甚至超过了Server程序的数倍……其实无论是哪种网络操模型,对于内存占用都是差不多的,真正的差别就在于CPU的占用,其他的网络模型都需要更多的CPU动力来支撑同样的连接数据。
虽然这远远算不上服务器极限压力测试,但是从中也可以看出来完成端口的实力,而且这种方式比纯粹靠多线程的方式实现并发资源占用率要低得多。
第3章完成端口的相关概念在开始编码之前,我们先来讨论一下和完成端口相关的一些概念,如果你没有耐心看完这段大段的文字的话,也可以跳过这一节直接去看下下一节的具体实现部分,但是这一节中涉及到的基本概念你还是有必要了解一下的,而且你也更能知道为什么有那么多的网络编程模式不用,非得要用这么又复杂又难以理解的完成端口呢??也会坚定你继续学习下去的信心^_^3.1异步通信机制及其几种实现方式的比较我们从前面的文字中了解到,高性能服务器程序使用异步通信机制是必须的。
而对于异步的概念,为了方便后面文字的理解,这里还是再次简单的描述一下:异步通信就是在咱们与外部的I/O设备进行打交道的时候,我们都知道外部设备的I/O和CPU比起来简直是龟速,比如硬盘读写、网络通信等等,我们没有必要在咱们自己的线程里面等待着I/O 操作完成再执行后续的代码,而是将这个请求交给设备的驱动程序自己去处理,我们的线程可以继续做其他更重要的事情,大体的流程如下图所示:我可以从图中看到一个很明显的并行操作的过程,而“同步”的通信方式是在进行网络操作的时候,主线程就挂起了,主线程要等待网络操作完成之后,才能继续执行后续的代码,就是说要末执行主线程,要末执行网络操作,是没法这样并行的;“异步”方式无疑比“阻塞模式+多线程”的方式效率要高的多,这也是前者为什么叫“异步”,后者为什么叫“同步”的原因了,因为不需要等待网络操作完成再执行别的操作。
而在Windows中实现异步的机制同样有好几种,而这其中的区别,关键就在于图1中的最后一步“通知应用程序处理网络数据”上了,因为实现操作系统调用设备驱动程序去接收数据的操作都是一样的,关键就是在于如何去通知应用程序来拿数据。
它们之间的具体区别我这里多讲几点,文字有点多,如果没兴趣深入研究的朋友可以跳过下一面的这一段,不影响的:)3.1.1使用设备内核对象。
设备内核对象,使用设备内核对象来协调数据的发送请求和接收数据协调,也就是说通过设置设备内核对象的状态,在设备接收数据完成后,马上触发这个内核对象,然后让接收数据的线程收到通知,但是这种方式太原始了,接收数据的线程为了能够知道内核对象是否被触发了,还是得不停的挂起等待,这简直是根本就没有用嘛,太低级了,有木有?所以在这里就略过不提了,各位读者要是没明白是怎么回事也不用深究了,总之没有什么用。
3.1.2使用事件内核对象。
事件内核对象,利用事件内核对象来实现I/O操作完成的通知,其实这种方式其实就是我以前写文章的时候提到的《基于事件通知的重叠I/O模型》,链接在这里,这种机制就先进得多,可以同时等待多个I/O操作的完成,实现真正的异步,但是缺点也是很明显的,既然用WaitForMultipleObjects()来等待Event的话,就会受到64个Event等待上限的限制,但是这可不是说我们只能处理来自于64个客户端的Socket,而是这是属于在一个设备内核对象上等待的64个事件内核对象,也就是说,我们在一个线程内,可以同时监控64个重叠I/O操作的完成状态,当然我们同样可以使用多个线程的方式来满足无限多个重叠I/O的需求,比如如果想要支持3万个连接,就得需要500多个线程…用起来太麻烦让人感觉不爽;3.1.3使用APC。
使用APC( Asynchronous Procedure Call,异步过程调用)来完成。
这种方式的好处就是在于摆脱了基于事件通知方式的64个事件上限的限制,但是缺点也是有的,就是发出请求的线程必须得要自己去处理接收请求,哪怕是这个线程发出了很多发送或者接收数据的请求,但是其他的线程都闲着…,这个线程也还是得自己来处理自己发出去的这些请求,没有人来帮忙…这就有一个负载均衡问题,显然性能没有达到最优化。
3.1.4使用完成端口。
完成端口,不用说大家也知道了,最后的压轴戏就是使用完成端口,对比上面几种机制,完成端口的做法是这样的:事先开好几个线程,你有几个CPU我就开几个,首先是避免了线程的上下文切换,因为线程想要执行的时候,总有CPU资源可用,然后让这几个线程等着,等到有用户请求来到的时候,就把这些请求都加入到一个公共消息队列中去,然后这几个开好的线程就排队逐一去从消息队列中取出消息并加以处理,这种方式就很优雅的实现了异步通信和负载均衡的问题,因为它提供了一种机制来使用几个线程“公平的”处理来自于多个客户端的输入/输出,并且线程如果没事干的时候也会被系统挂起,不会占用CPU周期,挺完美的一个解决方案,不是吗?哦,对了,这个关键的作为交换的消息队列,就是完成端口。
比较完毕之后,熟悉网络编程的朋友可能会问到,为什么没有提到WSAAsyncSelect 或者是WSAEventSelect这两个异步模型呢,对于这两个模型,我不知道其内部是如何实现的,但是这其中一定没有用到Overlapped机制,就不能算作是真正的异步,可能是其内部自己在维护一个消息队列吧,总之这两个模式虽然实现了异步的接收,但是却不能进行异步的发送,这就很明显说明问题了,我想其内部的实现一定和完成端口是迥异的,并且,完成端口非常厚道,因为它是先把用户数据接收回来之后再通知用户直接来取就好了,而WSAAsyncSelect和WSAEventSelect之流只是会接收到数据到达的通知,而只能由应用程序自己再另外去recv数据,性能上的差距就更明显了。
最后,我的建议是,想要使用基于事件通知的重叠I/O和基于完成例程的重叠I/O 的朋友,如果不是特别必要,就不要去使用了,因为这两种方式不仅使用和理解起来也不算简单,而且还有性能上的明显瓶颈,何不就再努力一下使用完成端口呢?3.2 重叠结构(OVERLAPPED)我们从上一小节中得知,要实现异步通信,必须要用到一个很风骚的I/O数据结构,叫重叠结构“Overlapped”,Windows里所有的异步通信都是基于它的,完成端口也不例外。
至于为什么叫Overlapped?Jeffrey Richter的解释是因为“执行I/O请求的时间与线程执行其他任务的时间是重叠(overlapped)的”,从这个名字我们也可能看得出来重叠结构发明的初衷了,对于重叠结构的内部细节我这里就不过多的解释了,就把它当成和其他内核对象一样,不需要深究其实现机制,只要会使用就可以了,想要了解更多重叠结构内部的朋友,请去翻阅Jeffrey Richter的《Windows via C/C++》 5th 的292页,如果没有机会的话,也可以随便翻翻我以前写的Overlapped的东西,不过写得比较浅显……这里我想要解释的是,这个重叠结构是异步通信机制实现的一个核心数据结构,因为你看到后面的代码你会发现,几乎所有的网络操作例如发送/接收之类的,都会用WSASend()和WSARecv()代替,参数里面都会附带一个重叠结构,这是为什么呢?因为重叠结构我们就可以理解成为是一个网络操作的ID号,也就是说我们要利用重叠I/O提供的异步机制的话,每一个网络操作都要有一个唯一的ID号,因为进了系统内核,里面黑灯瞎火的,也不了解上面出了什么状况,一看到有重叠I/O的调用进来了,就会使用其异步机制,并且操作系统就只能靠这个重叠结构带有的ID号来区分是哪一个网络操作了,然后内核里面处理完毕之后,根据这个ID号,把对应的数据传上去。