目录: 1. 重叠模型的优点 2. 重叠模型的基本原理 3. 关于重叠模型的基础知识 4. 重叠模型的实现步骤 5. 多客户端情况的注意事项
一. 重叠模型的优点 1. 可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。 2. 比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。 因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。 而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。 3. 从《windows网络编程》中提供的试验结果中可以看到,在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中,最大可以处理4万多个SOCKET连接,在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能,已经直逼完成端口了^_^
二. 重叠模型的基本原理 说了这么多的好处,你一定也跃跃欲试了吧,不过我们还是要先提一下重叠模型的基本原理。 概括一点说,重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。 需要注意的是,有两个方法可以用来管理重叠IO请求的完成情况(就是说接到重叠操作完成的通知): 1. 事件对象通知(event object notification) 2. 完成例程(completion routines) ,注意,这里并不是完成端口 而本文只是讲述如何来使用事件通知的的方法实现重叠IO模型,完成例程的方法准备放到下一篇讲 :) (内容太多了,一篇写不完啊) ,如没有特殊说明,本文的重叠模型默认就是指的基于事件通知的重叠模型。 既然是基于事件通知,就要求将Windows事件对象与WSAOVERLAPPED结构关联在一起(WSAOVERLAPPED结构中专门有对应的参数),通俗一点讲,就是。。。。对了,忘了说了,既然要使用重叠结构,我们常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替换掉了, 它们的用法我后面会讲到,这里只需要注意一点,它们的参数中都有一个Overlapped参数,我们可以假设是把我们的WSARecv这样的操作操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交给重叠结构去操心,而其中重叠结构又要与Windows的事件对象“绑定”在一起,这样我们调用完WSARecv以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,然后我们就可以来根据重叠操作的结果取得我们想要德数据了。 也许说了半天你还是不大明白,那就继续往后面看吧。。。。。。。-_-b,语言表达能力有限啊~~~
三. 关于重叠模型的基础知识 下面来介绍并举例说明一下编写重叠模型的程序中将会使用到的几个关键函数。 1. WSAOVERLAPPED结构 这个结构自然是重叠模型里的核心,它是这么定义的 typedef struct _WSAOVERLAPPED { 在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,因为要用刀重叠结构嘛,它是这样定义的: int WSARecv( 熟悉WSAEventSelect模型的朋友对这个函数肯定不会陌生,不对,其实大家都不应该陌生,这个函数与线程中常用的WaitForMultipleObjects函数有些地方还是比较像的,因为都是在等待某个事件的触发嘛。 因为我们需要事件来通知我们重叠操作的完成,所以自然需要这个等待事件的函数与之配套。 DWORD WSAWaitForMultipleEvents( 4. WSAGetOverlappedResult函数 既然我们可以通过WSAWaitForMultipleEvents函数来得到重叠操作完成的通知,那么我们自然也需要一个函数来查询一下重叠操作的结果,定义如下 BOOL WSAGetOverlappedResult( 四。 实现重叠模型的步骤 作了这么多的准备工作,费了这么多的笔墨,我们终于可以开始着手编码了。其实慢慢的你就会明白,要想透析重叠结构的内部原理也许是要费点功夫,但是只是学会如何来使用它,却是真的不难,唯一需要理清思路的地方就是和大量的客户端交互的情况下,我们得到事件通知以后,如何得知是哪一个重叠操作完成了,继而知道究竟该对哪一个套接字进行处理,应该去哪个缓冲区中的取得数据,everything will be OK^_^。 下面我们配合代码,来一步步的讲解如何亲手完成一个重叠模型。 【第一步】定义变量………… #define DATA_BUFSIZE 4096 // 接收缓冲区大小 【第二步】创建一个套接字,开始在指定的端口上监听连接请求 和其他的SOCKET初始化全无二致,直接照搬即可,在此也不多费唇舌了,需要注意的是为了一目了然,我去掉了错误处理,平常可不要这样啊,尽管这里出错的几率比较小。 WSADATA wsaData; 【第三步】接受一个入站的连接请求 一个accept就完了,都是一样一样一样一样的啊~~~~~~~~~~ 至于AcceptEx的使用,在完成端口中我会讲到,这里就先不一次灌输这么多了,不消化啊^_^ AcceptSocket = accept (ListenSocket, NULL,NULL) ; SOCKADDR_IN ClientAddr; // 定义一个客户端得地址结构作为参数 【第四步】建立并初始化重叠结构 为连入的这个套接字新建立一个WSAOVERLAPPED重叠结构,并且象前面讲到的那样,为这个重叠结构从事件句柄数组里挑出一个空闲的对象句柄“绑定”上去。 // 创建一个事件 【第五步】以WSAOVERLAPPED结构为参数,在套接字上投递WSARecv请求 各个变量都已经初始化OK以后,我们就可以开始Socket操作了,然后让WSAOVERLAPPED结构来替我们管理I/O 请求,我们只用等待事件的触发就OK了。 if(WSARecv(AcceptSocket ,&DataBuf,1,&dwRecvBytes,&Flags, 【第六步】 用WSAWaitForMultipleEvents函数等待重叠操作返回的结果 我们前面已经给WSARecv关联的重叠结构赋了一个事件对象句柄,所以我们这里要等待事件对象的触发与之配合,而且需要根据WSAWaitForMultipleEvents函数的返回值来确定究竟事件数组中的哪一个事件被触发了,这个函数的用法及返回值请参考前面的基础知识部分。 DWORD dwIndex; 【第七步】使用WSAResetEvent函数重设当前这个用完的事件对象 事件已经被触发了之后,它对于我们来说已经没有利用价值了,所以要将它重置一下留待下一次使用,很简单,就一步,连返回值都不用考虑 WSAResetEvent(EventArray[dwIndex]); 【第八步】使用WSAGetOverlappedResult函数取得重叠调用的返回状态 这是我们最关心的事情,费了那么大劲投递的这个重叠操作究竟是个什么结果呢?其实对于本模型来说,唯一需要检查一下的就是对方的Socket连接是否已经关闭了 DWORD dwBytesTransferred; 【第九步】“享受”接收到的数据 如果程序执行到了这里,那么就说明一切正常,WSABUF结构里面就存有我们WSARecv来的数据了,终于到了尽情享用成果的时候了!喝杯茶,休息一下吧~~~^_^ DataBuf.buf就是一个char*字符串指针,听凭你的处理吧,我就不多说了
【第十步】同第五步一样,在套接字上继续投递WSARecv请求,重复步骤 6 ~ 9 这样一路作下来,我们终于可以从客户端接收到数据了,但是回想起来,呀~~~~~,这样岂不是只能收到一次数据,然后程序不就Over了?…….-_-b 所以我们接下来不得不重复一遍第四步和第五步的工作,再次在这个套接字上投递另一个WSARecv请求,并且使整个过程循环起来,are u clear?? 大家可以参考我的代码,在这里就先不写了,因为各位都一定比我smart,领悟了关键所在以后,稍作思考就可以灵活变通了。
五。 多客户端情况的注意事项 完成了上面的循环以后,重叠模型就已经基本上搭建好了80%了,为什么不是100%呢?因为仔细一回想起来,呀~~~~~~~,这样岂不是只能连接一个客户端??是的,如果只处理一个客户端,那重叠模型就半点优势也没有了,我们正是要使用重叠模型来处理多个客户端。 所以我们不得不再对结构作一些改动。 1. 首先,肯定是需要一个SOCKET数组 ,分别用来和每一个SOCKET通信 其次,因为重叠模型中每一个SOCKET操作都是要“绑定”一个重叠结构的,所以需要为每一个SOCKET操作搭配一个WSAOVERLAPPED结构,但是这样说并不严格,因为如果每一个SOCKET同时只有一个操作,比如WSARecv,那么一个SOCKET就可以对应一个WSAOVERLAPPED结构,但是如果一个SOCKET上会有WSARecv 和WSASend两个操作,那么一个SOCKET肯定就要对应两个WSAOVERLAPPED结构,所以有多少个SOCKET操作就会有多少个WSAOVERLAPPED结构。 然后,同样是为每一个WSAOVERLAPPED结构都要搭配一个WSAEVENT事件,所以说有多少个SOCKET操作就应该有多少个WSAOVERLAPPED结构,有多少个WSAOVERLAPPED结构就应该有多少个WSAEVENT事件,最好把SOCKET – WSAOVERLAPPED – WSAEVENT三者的关联起来,到了关键时刻才会临危不乱:)
2. 不得不分作两个线程: 一个用来循环监听端口,接收请求的连接,然后给在这个套接字上配合一个WSAOVERLAPPED结构投递第一个WSARecv请求,然后进入第二个线程中等待操作完成。 第二个线程用来不停的对WSAEVENT数组WSAWaitForMultipleEvents,等待任何一个重叠操作的完成,然后根据返回的索引值进行处理,处理完毕以后再继续投递另一个WSARecv请求。 这里需要注意一点的是,前面我是把WSAWaitForMultipleEvents函数的参数设置为WSA_ INFINITE的,但是在多客户端的时候这样就不OK了,需要设定一个超时时间,如果等待超时了再重新WSAWaitForMultipleEvents,因为WSAWaitForMultipleEvents函数在没有触发的时候是阻塞在那里的,我们可以设想一下,这时如果监听线程忠接入了新的连接,自然也会为这个连接增加一个Event,但是WSAWaitForMultipleEvents还是阻塞在那里就不会处理这个新连接的Event了。也不知道说明白了没有。。。。。。-_-b 可能在这里你也体会不到,真正编码的时候就会明白了。
其他还有不明白的地方可以参考我的代码,代码里也有比较详尽的注释, Enjoy~~~ 不过可惜是为了照顾大多数人,使用的是MFC的代码,显得代码有些杂乱。 |