完成端口(IOCP)的另一种设想——Socket与CompletionPort的多次关联
代码客 http://blog.csdn.net/guestcode
本文论坛讨论: http://topic.csdn.net/u/20091104/14/083f8353-e4dc-470f-b0a7-f570404ab338.html
先扯一下题外话。我发表过VC和Delphi版IOCP例程。有不少人提出了很好的建议,也有很多人提出了置疑。有自称内核编程的人说临界段同步不要需要进入内核态,用过临界段的人都知道,当竞争发生的时候,“后来”的线程总被挂起,那么线程挂起会进入什么态?
有人提出线程独立的内存资源管理,可以避免的同步操作,这不失为一个好办法,但带来的麻烦是,分配资源的时需要在取得“当前线程对象”上花费时间,其实临界段在竞争不激烈的情况下导致进入内核态的几率很少,临界段玩得的就是概率。
在工作线程和处理线程之间,有人建议最好做法是复制数据给处理线程,当然这个比较安全点,但数据复制也需要不少时间。甚至有人抨击,HandleData(socket连接的对象)和UserData(登录用户的对象)之间的指针关联方式效率底下,认为哈希表方式效率高,可是未必见得哈希表的操作比指针关联方式有优越之处。
内存管理是服务器性能的关键,有人却忽略了“分页内存”会被交换到“页交换文件”中去的可能。对象化(类)编程能够提高开发效率,但也要注意“级数深”的对象成员及虚函数访问的开销。事实上操作完成端口很容易,难的是如何组织管理内存和设计合理的结构。
进入正题。前面提到HandleData和UserData,做过Tcp服务开发的人都知道,每个客户端连接都有一个Socket对象(下称HandleData)与之对应。做过游戏开发的人也肯定知道,在服务器端每个登录的用户有个对象(下称UserData)记录用户信息,UserData和HandleData也是一一对应关系。但两者很难合并为一体(同一个数据结构或类对象),UserData是在用户输入ID和密码Login之后被会创建(或池分配),用户发送数据Logout之后会被摧毁(或回收入池),而在Logout之前,由于网络等诸多原因,会有N次的重新连接的可能性,如果断线不超过规定时间的话UserData是不能摧毁的,那么UserData在其生命期内对应的HandleData就不是唯一的了。一般HandleData和UserData关联的做法是,根据Login的用户ID在目前的在线用户信息列表中查找UserData,找到则关联(未超时和Logout),找不到则创建后再关联,此后根据关联信息无需遍历很快就可以知道来自这个HandleData的数据就是这个UserData的(用户登录频率远比数据收发频率底得多,以最快方式把Socket的数据定位到用户对象上去是高效率的一个关键)。他们关联的方式有:1、使用指针关联的,HandleData有指针指向UserData,UserData有指针指向HandleData);2、使用哈希表,用socket的值作为索引;3、UserData索引,就是登录以后返回UserData的索引,此后客户端发来的每个数据包里面都包含这个的索引,但会增加数据量,一般用在Udp服务器。上面这些是常用的方式,但是否还有其它刚好的方式呢?
了解完成端口的都知道,完成端口句柄和Socket关联的参数有个完成键(CompletionKey),这个CompletionKey一般是HandleData(或是担任这个角色的其它结构),通常我们仅建立一次CompletionPort与Socket的关联,因此某个客户端从连接到断开,CompletionKey一直是HandleData。假如有这么个设想不知道是否行得通,就是当用户发送ID和密码Login之后,用UserData作为CompletionKey再次把Socket和完成端口关联,那么此后的Io投递的返回数据就可以直接对应UserData。本人信息闭塞,尚未不知道有人提过这个方法,也不知道是否行得通,也希望大家互相探讨。具体做法是:
1、Accept/AcceptEx阶段:
CreateIoCompletionPort(Socket, CompletionPort, HandleData, 0);
2、用户发送数据Login,获得认证并建立UserData以后:
CreateIoCompletionPort(Socket, CompletionPort, UserData, 0);
3、Disconnect之后(恢复最初关联):
CreateIoCompletionPort(Socket, CompletionPort, HandleData, 0);
上面三步主要区别于CreateIoCompletionPort函数的第三个参数。假如行得通的话,那么Iocp Server的编程难度相对就增大了一些,UserData 也要具备HandleData的基本功能。在测试中多次调用CreateIoCompletionPort(Socket, CompletionPort, …)进行关联操作的返回值是正确的,但尚未得知的是多次关联对Iocp内部机制有没有影响,以后会有怎样的异常出现。本人也正在进一步编程求证中。