1。恒珈科技的网上阅卷系统分析
-----------------------------
以前用完成端口主要是使用从网络上下载的一个类:CompletionPortModel,应该说这个类封装
得很好,也很好使用。但当时对完成端口看得很高深很神秘,所以也就没有大胆的放下手去编写程序了。
在CompletionPortModel的使用中,最重要的可能就是下面三个数据结构了:
//
//自定义枚举数据类型,用来标识套接字IO动作类型
//
typedef enum _IO_OPERATION
{
IoAccept, //AcceptEx/accept
IoRead, //WSARecv/recv/ReadFile
IoWrite, //WSASend/send/WriteFile
IoEnd
}IO_OPERATION, *PIO_OPERATION;
//
//自定义结构,即"完成键"(单句柄数据)
//
typedef struct _PER_HANDLE_CONTEXT
{
SOCKET IoSocket;
struct _PER_HANDLE_CONTEXT* pNext;
}PER_HANDLE_CONTEXT, *PPER_HANDLE_CONTEXT;
//
//单IO数据,扩展的WSAOVERLAPPED
//
typedef struct _PER_IO_CONTEXT
{
WSAOVERLAPPED ol;
char szBuffer[PACKET_BUF_SIZE]; //??
WSABUF wsaBuffer;
SOCKET sClient;
unsigned int unId;//?????
IO_OPERATION IoOperation;
struct _PER_IO_CONTEXT* pNext;
}PER_IO_CONTEXT, *PPER_IO_CONTEXT;
当时在作恒珈科技的网上阅卷系统的时候,就是在工作线程和PER_IO_CONTEXT的内存管理上
花了很大的功夫,可是感觉解决方案还不是很好。我当时参考网络游戏服务器的编程设计思想,想把事务处理放在线程池中,但当时由于对每一个用户的请求处理本身必须要串行化,比如用户注销就应该放在试卷提交和试卷请求的处理之后。我当时的处理是简单的为试卷请求,试卷提交分别开了一个线程来处理。
在PER_IO_CONTEXT的内存管理基于采用LookAside链表的管理机制。现在到公司看同行们的代码发现
他们也是采用类似的处理方式,呵呵:) 不过我当时设计的内存管理机制在早期内存分配,内存泄露跟踪
上的处理他们却并没有。我比他们的还先进一点? ^_^
2。一个较完美的完成端口网络解决方案
-----------------------------------
由于公司的代码保密做得很严,所以到现在该方案中最核心的代码还没看到。只好简单讲讲
接口和编程模型啦!公司研发部把完成端口封装在lib和dll中,并提供了一个头文件来调用。前天我看了
看同事的代码发现了一个我认为很好的完成端口网络解决方案,基于公司的完成端口封装库Vnetsvr.lib
,整个编程模型如下:
+--------+ +-----------+ +----------+
| Client | <====>| 子服务器 | <===> | 中央处理 |
+--------+ +-----------+ | 服务器 |
+----------+
(I). 子服务器的启动分析
子服务器启动的时候首先读取服务器配置文件,建立对象缓冲池,启动完成端口并创建完成线程。
同时,还会创建一个EVENT对象来实现对子服务器跟中央处理服务器连接的监控,以下我将详细介绍。
然后,子服务器去向中央处理服务器(CCServ)发送服务器注册请求包,子服务器在收到CCServ发送回来的服务器注册请求响应包之后,在指定的端口开始Listen,接受用户的连接和服务请求。
(II).子服务器线程分析
子线程的线程结构如下:
----------------------------------------------------------------
| 线程 说明
-----------------------------------------------------------------
+--------+
| 主线程 | 主线程在完成子服务器的启动之后便进入
+--------+ 一个状态监控状态。其结构如下:
int main()
{
CPortalServ * pPortalServ = new CPortalServ;
pPortalServ->Init();
pPortalServ->StartService();
while (TRUE)
{
if (!pPortalServ->ThreadLoop())
{
pPortalServ->Release();
while (!Restart(pPortalServ))
{
Sleep(5000);
}
}
else
{
IOCOMP_GetFrameInfo(&lConnectionTotal,
&lRecvTimesTotal,
&lSentTimesTotal,
&lRecvBytes,
&lSentBytes
);
}
}
-----------------------------------------------------------------
+--------+ 通过调用IOCOMP_GetNextPacket不断获取完成端口状态
|完成线程| 然后QueueUserWorkItem(ThreadPool, (PVOID)pBufferObj, WT_EXECUTEDEFAULT);
+--------+ 将对网络包的具体处理丢给系统提供的线程池去完成。QueueUserWorkItem
这个API的具体使用可以看看MSDN的相关文档。
------------------------------------------------------------------
+--------+ 根据完成端口邦定的私有数据,进行数据包的分发。
|系统线程| 我们可以很简单的判别是客户端发来的数据包还是中央
| 池 | 服务器发来的数据包通过设定特殊的私有数据。
+--------+
------------------------------------------------------------------
(III).数据包分发机制
+----....
DATATYPE_RECV_PACKET |
+------ ---------------- HandleRecv(...)--->+----.....
| DATATYPE_SEND_COMPLETE
DefMessage-->+----------------------- HandleSent(...)
| DATATYPE_ERROR
+----------------------- HandleError(...)
(IV).与中央服务器连接的监控
由于与中央服务器之间的连接至关重要,所以系统设计中一旦发现与中央服务器之间的连接出现
错误,立即自动重启子服务器。在中央服务器的消息处理中HandleError将设定指定的EVENT为Signal 状态。从而主线程可以立刻知道,然后调用Restart来重新启动子服务器。
Okey!写到这里吧,用我的笔记本写汉字真是受罪。我贴相关的代码上来,有兴趣的兄弟们可以分析一下。
完成端口封装
子服务器代码