一个对Winsock完成端口模型封装的类
-
文章来源:中国电脑教育报 作者:elssann
在WINDOWS下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握 Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解。如果不了解,推荐几本书:《Inside Windows 2000,《WINDOWS核心编程》,《WIN32多线程程序设计》、《WINDOWS网络编程技术》。在去年,我在C语言下用完成端口模型写了一个 WEBSERVER,前些天,我决定用C++重写这个WEBSERVER,给这个WEBSERVER增加了一些功能,并改进完成端口操作方法,比如采用 AcceptEx来代替accept和使用LOOKASIDE LIST来管理内存,使得WEBSERVER的性能有了比较大的提高。
在重写的开始,我决定把完成端口模型封装成一个比较通用的C++类,针对各种网络服务端程序的开发,只要简单地继承这个类,改写其中两个虚拟函数就能满足各种需要。到昨天为止,WEBSERVER重写完毕,我就写了这篇文章对完成端口模型做一个总结,并介绍一下我的这个类。
一:完成端口模型
至于完成端口和Winsock完成端口模型的详细介绍,请参见我上面介绍的那几本书,这里只是我个人对完成端口模型理解的一点心得。
首先我们要抽象出一个完成端口大概的处理流程:
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()这里。
具体的流程请看附图,其中红线表示是WINDOWS系统进行的处理,不需要我们程序干预。
归根到底概括完成端口模型一句话:
我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了,那么就在完成端口那里排成一个队列)。我们在另外一个线程里从完成端口不断地取出IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。
二:提高完成端口效率的几种有效方法
1:使用AcceptEx代替accept。AcceptEx函数是微软的Winsosk 扩展函数,这个函数和accept的区别就是:accept是阻塞的,一直要到有客户端连接上来后accept才返回,而AcceptEx是异步的,直接就返回了,所以我们利用AcceptEx可以发出多个AcceptEx调用
等待客户端连接。另外,如果我们可以预见到客户端一连接上来后就会发送数据(比如WEBSERVER的客户端浏览器),那么可以随着AcceptEx 投递一个BUFFER进去,这样连接一建立成功,就可以接收客户端发出的数据到BUFFER里,这样使用的话,一次AcceptEx调用相当于 accpet和recv的一次连续调用。同时,微软的几个扩展函数针对操作系统优化过,效率优于WINSOCK 的标准API函数。
2:在套接字上使用SO_RCVBUF和SO_SNDBUF选项来关闭系统缓冲区。这个方法见仁见智,详细的介绍可以参考《WINDOWS核心编程》第9章。这里不做详细介绍,我封装的类中也没有使用这个方法。
3:内存分配方法。因为每次为一个新建立的套接字都要动态分配一个“单IO数据”和“单句柄数据”的数据结构,然后在套接字关闭的时候释放,这样如果有成千上万个客户频繁连接时候,会使得程序很多开销花费在内存分配和释放上。这里我们可以使用lookaside list。开始在微软的platform sdk里的SAMPLE里看到lookaside list,我一点不明白,MSDN里有没有。后来还是在DDK的文档中找到了,,
lookaside list
A system-managed queue from which entries of a fixed size can be allocated and into which entries can be deallocated dynamically. Callers of the Ex(ecutive) Support lookaside list routines can use a lookaside list to manage any dynamically sized set of fixed-size buffers or structures with caller-determined contents.
For example, the I/O Manager uses a lookaside for fast allocation and deallocation of IRPs and MDLs. As another example, some of the system-supplied SCSI class drivers use lookaside lists to allocate and release memory for SRBs.
lookaside list名字比较古怪(也许是我孤陋寡闻,第一次看到),其实就是一种内存管理方法,和内存池使用方法类似。我个人的理解:就是一个单链表。每次要分配内存前,先查看这个链表是否为空,如果不为空,就从这个链表中解下一个结点,则不需要新分配。如果为空,再动态分配。使用完成后,把这个数据结构不释放,而是把它插入到链表中去,以便下一次使用。这样相比效率就高了很多。在我的程序中,我就使用了这种单链表来管理。
在我们使用AcceptEx并随着AcceptEx投递一个BUFFER后会带来一个副作用:比如某个客户端只执行一个connect操作,并不执行 send操作,那么AcceptEx这个请求不会完成,相应的,我们用GetQueuedCompletionStatus在完成端口中得不到操作结果,这样,如果有很多个这样的连接,对程序性能会造成巨大的影响,我们需要用一种方法来定时检测,当某个连接已经建立并且连接时间超过我们规定的时间而且没有收发过数据,那么我们就把它关闭。检测连接时间可以用SO_CONNECT_TIME来调用getsockopt得到。
还有一个值得注意的地方:就是我们不能一下子发出很多AcceptEx调用等待客户连接,这样对程序的性能有影响,同时,在我们发出的 AcceptEx调用耗尽的时候需要新增加AcceptEx调用,我们可以把FD_ACCEPT事件和一个EVENT关联起来,然后用 WaitForSingleObject等待这个Event,当已经发出AccpetEx调用数目耗尽而又有新的客户端需要连接上来,FD_ACCEPT 事件将被触发,EVENT变为已传信状态,
WaitForSingleObject返回,我们就重新发出足够的AcceptEx调用。
关于完成端口模型就介绍到这里。下面介绍我封装的类,这个类写完后,我用这个类做了个ECHOSERVER。
void main()
{
CompletionPortModel p;
p.Init();
p.AllocEventMessage();
if (FALSE == p.PostAcceptEx())
{
return;
}
p.ThreadLoop();
return;
}
我在我自己的机器上测试,
客户端的代码是
for (int i=0; i<10000; i++)
{
SOCKET s = socket(….);
connect(….);
send(…);
recv(…..)
cout << buffer << endl;
}
结果客户端程序在循环到3000多次的时候死掉,但是服务端程序运行良好,重新启动客户端程序,发送接收数据正常。
使用的时候,只需要从这个类派生一个子类,并改写HandleData和DataAction这两个虚函数,对于那些需要连续发送相关联的数据应用(比如传送文件),使用者需要自己扩展这两个函数,比如创建一个全局队列,每次从完成端口里得到数据后插入队列,然后用另外一个线程专门处理这个队列。。。
从结果来看,这个类还有不少需要改进的地方,比如没考虑多处理器上运行的情况。没有考虑完成端口线程阻塞情况,如果考虑完成端口阻塞情况,那么应该创建CPU数据*2个完成端口线程等等,,因为我同时正在做的毕业设计NDIS驱动防火墙开发正在一个比较难的地方卡住了,时间和精力有限,就没有对这个类进行进一步完善,程序中也许有不合理和错误的地方,请高手多多指教。对于高性能的服务端程序开发是比较难的,记得有次和腾讯一个技术人员聊天,他说,像腾讯QQ的开发,难点不在客户端,而在服务端各个服务器之间的通信和同步。服务端程程序的集群和负载平衡是一个很复杂的问题,我在这方面刚接触,希望能有更多的高手出来共享自己的经验。
封装这个类的时候,我把最新的PLATFORM SDK里的例子看了一遍,借鉴了其中很多思路和方法,在此对写这个例子的微软程序员表示感谢:)
DEMO就是一个ECHOSERVER,记得使用Release模式编译。
分享到:
相关推荐
### 一个对Winsock完成端口模型封装的类的研究文档 #### 概述 本文档主要探讨了在Windows环境下如何高效地使用Winsock完成端口模型,并通过C++类的形式对该模型进行了封装,以简化网络服务端程序的开发流程。完成...
Winsock 完成端口模型封装的类 Windows Sockets规范以U.C. Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Micosoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数;也包含...
Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解...
Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握 Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解...
1. **完成端口对象**:创建一个表示IOCP的类,该类在构造时会初始化一个IOCP,并在析构时关闭。这个类还需要提供方法来添加或删除工作线程,这些线程将处理由IOCP发出的完成事件。 2. **工作线程**:工作线程的主要...
总结来说,这个Delphi代码示例展示了如何利用完成端口模型在Delphi中构建一个高效的TCP回显服务器。通过IOCP,我们可以利用多线程有效地处理并发连接,提高了服务器的吞吐量和响应能力。这种模型对于大型网络服务...
描述中提到的"一个对Winsock 完成端口模型封装的类"可能是一个C++类库,它抽象了IOCP的相关API,如CreateIoCompletionPort、GetQueuedCompletionStatus和PostQueuedCompletionStatus等,提供了一套面向对象的接口,...
通过调用`CreateIoCompletionPort`函数,可以为特定的设备句柄(如socket)分配一个完成端口,设置相关参数,如线程调度策略等。 2. **连接管理**:IOCP模型下的连接处理不同于传统的阻塞式I/O。程序会使用异步方式...
这个名为"一个Socket连接类,封装了Winsock API"的VB类,可能包含以下核心功能: 1. 初始化与配置:类的初始化通常涉及设置Winsock控件的基本属性,如本地主机地址、端口号等。这一步确保Socket正确绑定到特定的...
在使用这个类时,开发人员首先需要创建一个完成端口对象,然后将需要进行I/O操作的句柄关联到这个端口。当一个I/O操作完成后,操作系统会将一个完成包(IOCP包)放入完成端口,等待与之关联的线程来处理。这个类可能...
在VC++中,使用CreateIoCompletionPort函数创建一个完成端口。这个函数需要指定一个设备句柄(如socket)以及一个线程池,用于处理I/O操作的完成。 3. IOCP与Socket结合: Socket是网络通信的基础,而在IOCP模型...
Winsock I/O模型: IOCP (完成端口模型) 代码示例包括Client和Server, common下包括Functor和Thread两个模块。Functor封装了函数对象,用于各种Callback;Thread封装了线程函数对象,用于将类的成员函数绑定到一个...
本篇将详细介绍WinSock API的主要函数及其在Windows Socket API封装中的应用,以及如何利用这些技术来构建一个基于局域网的一对一网络即时通讯工具。 首先,了解WinSock API的基础知识至关重要。WinSock API主要...
"34424一个对Winsock 完成端口模型封装的类.rar",显然与网络编程相关,Winsock是Windows下的网络编程接口,而完成端口模型是高并发服务器的一种常见设计,用于优化I/O处理。 为了提供更具体的IT知识,我将解释一下...
本文将深入探讨Windows Socket的封装技术,特别是使用完成端口(IOCP,I/O Completion Port)和select模型。 首先,我们需要理解Socket的基本概念。Socket是网络通信中的一个抽象概念,它代表一个通信端点,允许...
在`IOCP_Server.h`中,定义了相关的类和结构,用于封装套接字操作和完成端口的管理。 `Client.cpp`则包含了客户端的实现,它负责与服务器建立连接,发送数据并接收服务器的响应。客户端通常使用阻塞或非阻塞模式的...
【winsockclass】文件名暗示了压缩包可能包含一个封装了Winsock网络API的类库,用于简化与完成端口配合使用的网络编程。Winsock是Windows操作系统中实现TCP/IP协议栈的API,提供了丰富的功能,如创建套接字、建立...
MFC的`CAsyncSocket`类是对Winsock API的封装,提供了异步事件驱动的编程模型,简化了套接字的管理和网络通信的处理。 在实际编程中,需要注意错误处理。Winsock API的每个函数都可能失败,因此在每次调用后检查...
例如,可以创建一个CWinApp派生类处理Winsock的初始化和清理,一个CServerSocket类处理服务器端的监听和连接,以及一个CClientSocket类处理客户端的连接和通信。 在聊天室程序中,还需要处理多用户并发连接的问题。...