Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

算法的力量
算法是计算机科学领域最重要的基石之一,但却受到了国内一些程序员的冷落。许多学生看到一些公司在招聘时要求的编程语言五花八门,就产生了一种误解,认为 学计算机就是学各种编程语言,或者认为,学习最新的语言、技术、标准就是最好的铺路方法。其实,大家被这些公司误导了。编程语言虽然该学,但是学习计算机 算法和理论更重要,因为计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论,例如数据结构、算法、编译原理、计算机体系结构、关系型数据库 原理等等。在“开复学生网”上,有位同学生动地把这些基础课程比拟为“内功”,把新的语言、技术、标准比拟为“外功”。整天赶时髦的人最后只懂得招式,没 有功力,是不可能成为高手的。
算法与我
当我在1980年转入计算机科学系时,还没有多少人的专业方向是计算机科学。有许多其他系的人嘲笑我们说:“知道为什么只有你们系要加一个‘科学’,而没 有‘物理科学系’或‘化学科学系’吗?因为人家是真的科学,不需要画蛇添足,而你们自己心虚,生怕不‘科学’,才这样欲盖弥彰。” 其实,这点他们彻底弄 错了。真正学懂计算机的人(不只是“编程匠”)都对数学有相当的造诣,既能用科学家的严谨思维来求证,也能用工程师的务实手段来解决问题——而这种思维和 手段的最佳演绎就是“算法”。
记得我读博时写的Othello对弈软件获得了世界冠军。当时,得第二名的人认为我是靠侥幸才打赢他,不服气地问我的程序平均每秒能搜索多少步棋,当他发 现我的软件在搜索效率上比他快60多倍时,才彻底服输。为什么在同样的机器上,我可以多做60倍的工作呢?这是因为我用了一个最新的算法,能够把一个指数 函数转换成四个近似的表,只要用常数时间就可得到近似的答案。在这个例子中,是否用对算法才是能否赢得世界冠军的关键。
还记得1988年贝尔实验室副总裁亲自来访问我的学校,目的就是为了想了解为什么他们的语音识别系统比我开发的慢几十倍,而且,在扩大至大词汇系统后,速 度差异更有几百倍之多。他们虽然买了几台超级计算机,勉强让系统跑了起来,但这么贵的计算资源让他们的产品部门很反感,因为“昂贵”的技术是没有应用前景 的。在与他们探讨的过程中,我惊讶地发现一个O(n*m)的动态规划(dynamic programming)居然被他们做成了O(n*n*m)。更惊 讶的是,他们还为此发表了不少文章,甚至为自己的算法起了一个很特别的名字,并将算法提名到一个科学会议里,希望能得到大奖。当时,贝尔实验室的研究员当 然绝顶聪明,但他们全都是学数学、物理或电机出身,从未学过计算机科学或算法,才犯了这么基本的错误。我想那些人以后再也不会嘲笑学计算机科学的人了吧!
网络时代的算法
有人也许会说:“今天计算机这么快,算法还重要吗?”其实永远不会有太快的计算机,因为我们总会想出新的应用。虽然在摩尔定律的作用下,计算机的计算能力 每年都在飞快增长,价格也在不断下降。可我们不要忘记,需要处理的信息量更是呈指数级的增长。现在每人每天都会创造出大量数据(照片,视频,语音,文本等 等)。日益先进的记录和存储手段使我们每个人的信息量都在爆炸式的增长。互联网的信息流量和日志容量也在飞快增长。在科学研究方面,随着研究手段的进步, 数据量更是达到了前所未有的程度。无论是三维图形、海量数据处理、机器学习、语音识别,都需要极大的计算量。在网络时代,越来越多的挑战需要靠卓越的算法 来解决。
再举另一个网络时代的例子。在互联网和手机搜索上,如果要找附近的咖啡店,那么搜索引擎该怎么处理这个请求呢?
最简单的办法就是把整个城市的咖啡馆都找出来,然后计算出它们的所在位置与你之间的距离,再进行排序,然后返回最近的结果。但该如何计算距离呢?图论里有不少算法可以解决这个问题。
这么做也许是最直观的,但绝对不是最迅速的。如果一个城市只有为数不多的咖啡馆,那这么做应该没什么问题,反正计算量不大。但如果一个城市里有很多咖啡馆,又有很多用户都需要类似的搜索,那么服务器所承受的压力就大多了。在这种情况下,我们该怎样优化算法呢?
首先,我们可以把整个城市的咖啡馆做一次“预处理”。比如,把一个城市分成若干个“格子(grid)”,然后根据用户所在的位置把他放到某一个格子里,只对格子里的咖啡馆进行距离排序。
问题又来了,如果格子大小一样,那么绝大多数结果都可能出现在市中心的一个格子里,而郊区的格子里只有极少的结果。在这种情况下,我们应该把市中心多分出 几个格子。更进一步,格子应该是一个“树结构”,最顶层是一个大格——整个城市,然后逐层下降,格子越来越小,这样有利于用户进行精确搜索——如果在最底 层的格子里搜索结果不多,用户可以逐级上升,放大搜索范围。
上述算法对咖啡馆的例子很实用,但是它具有通用性吗?答案是否定的。把咖啡馆抽象一下,它是一个“点”,如果要搜索一个“面”该怎么办呢?比如,用户想去 一个水库玩,而一个水库有好几个入口,那么哪一个离用户最近呢?这个时候,上述“树结构”就要改成“r-tree”,因为树中间的每一个节点都是一个范 围,一个有边界的范围(参考:http://www.cs.umd.edu/~hjs/rtrees/index.html)。
通过这个小例子,我们看到,应用程序的要求千变万化,很多时候需要把一个复杂的问题分解成若干简单的小问题,然后再选用合适的算法和数据结构。
并行算法:Google的核心优势
上面的例子在Google里就要算是小case了!每天Google的网站要处理十亿个以上的搜索,GMail要储存几千万用户的2G邮箱, Google Earth要让数十万用户同时在整个地球上遨游,并将合适的图片经过互联网提交给每个用户。如果没有好的算法,这些应用都无法成为现实。
在这些的应用中,哪怕是最基本的问题都会给传统的计算带来很大的挑战。例如,每天都有十亿以上的用户访问Google的网站,使用Google的服务,也 产生很多很多的日志(Log)。因为Log每分每秒都在飞速增加,我们必须有聪明的办法来进行处理。我曾经在面试中问过关于如何对log进行一些分析处理 的问题,有很多面试者的回答虽然在逻辑上正确,但在实际应用中是几乎不可行的。按照他们的算法,即便用上几万台机器,我们的处理速度都跟不上数据产生的速 度。
那么Google是如何解决这些问题的呢?
首先,在网络时代,就算有最好的算法,也要能在并行计算的环境下执行。在Google的数据中心,我们使用的是超大的并行计算机。但传统的并行算法运行 时,效率会在增加机器数量后迅速降低,也就是说,十台机器如果有五倍的效果,增加到一千台时也许就只有几十倍的效果。这种事倍功半的代价是没有哪家公司可 以负担得起的。而且,在许多并行算法中,只要一个结点犯错误,所有计算都会前功尽弃。
那么Google是如何开发出既有效率又能容错的并行计算的呢?
Google最资深的计算机科学家Jeff Dean认识到, Google 所需的绝大部分数据处理都可以归结为一个简单的并行算法: Map and Reduce(http://labs.google.com/papers/mapreduce.html)。 这个算法能够在很多种 计算中达到相当高的效率,而且是可扩展的(也就是说,一千台机器就算不能达到一千倍的效果,至少也可以达到几百倍的效果)。Map and Reduce 的另外一大特色是它可以利用大批廉价的机器组成功能强大的server farm。最后,它的容错性能异常出色,就算一个server farm里面的机 器down掉一半,整个farm依然能够运行。正是因为这个天才的认识,才有了Map and Reduce算法。借助该算法,Google几乎能无限地 增加计算量,与日新月异的互联网应用一同成长。
算法并不局限于计算机和网络
举一个计算机领域外的例子:在高能物理研究方面,很多实验每秒钟都产生几个TB的数据量。但因为处理能力和存储能力的不足,科学家不得不把绝大部分未经处 理的数据丢弃掉。可大家要知道,新元素的信息很有可能就藏在我们来不及处理的数据里面。同样的,在其他任何领域里,算法都可以改变人类的生活。例如人类基 因的研究,就可能因为算法而发明新的医疗方式。在国家安全领域,有效的算法可能避免下一个911的发生。在气象方面,算法可以更好地预测未来天灾的发生, 以拯救生命。
所以,如果你把计算机的发展放到应用和数据飞速增长的大环境下,你一定会发现,算法的重要性不是在日益减小,而是在日益加强。
给程序员的七个建议
(1)练内功。不要只花功夫学习各种流行的编程语言和工具,以及某些公司招聘广告上要求的科目。要把数据结构、算法、数据库、操作系统原理、计算机体系结 构、计算机网络,离散数学等基础课程学好。大家不妨试试高德纳所著The Art of Computer Programming里的题目,如果你能够 解决其中的大部分题目,就说明你在算法方面有一定的功力了。
(2)多实战。通过编程的实战积累经验、巩固知识。很多中国大学毕业生缺乏编程和调试经验;学习C语言,考试过关就算学会了;课题项目中,只要程序能够编 译,运行,并且输入输出满足要求就算了事。这些做法是不行的。写程序的时候,大家必须多想想如何把程序写得更加精炼、高效、高质量。建议大家争取在大学四 年中积累编写十万行代码的经验。我们必须明白的是:好程序员是写出来的,不是学出来的。
(3)求实干。不要轻视任何实际工作,比如一些看似简单的编码或测试。要不懈追求对细节一丝不苟的实干作风与敬业精神。我发现不少程序员对于知识的掌握很 肤浅,不求甚解,没有好奇心,不会刨根问底。比如,学会了C++,是否了解一个对象在编译后,在汇编代码中是如何被初始化的?这个对象的各个成员在内存中 是如何存放的?当一个成员函数被调用时,编译器在汇编代码中加入了哪些额外的动作?虚函数的调用是如何实现的? 这些东西恐怕在编程语言或编译原理中都没 有详细提到,只有通过踏实的实干才能真正掌握。
(4)重视数学学习。数学是思维的体操,数学无处不在。学计算机至少要学习离散数学、概率论、布尔代数、集合论和数理逻辑。这些知识并不难,但是对你未来 的工作帮助会很大。 尤其当你对一些“数学密集型”的领域如视频、图像处理等有兴趣时,这些知识将成为你手中的利器。
(5)培养团队精神,学会与人合作。今天的软件工程早已经不是一个人可以单独操作的,而必须靠团队合作才能成功。不懂得合作的人是不能成大器的。大家要多去寻找可以与人一起做项目的机会。
(6)激励创新意识,培养好奇心,不要死记硬背。没有掌握某种算法技术的根本原理,就不会有应变和创新的能力。想成为一位好程序员(其实从事任何一个行业 都是如此),重要的是要养成钻研,好奇,创新,动手,合作的优秀习惯,不满足于填鸭,不满足于考试交差,不满足于表象。这不是学几门课能够一蹴而就的。
(7)有策略地“打工”。在不影响学业的前提下,寻找真正有意义的暑期工作或兼职。去找一个重视技术的公司,在一个好的“老板”指导下完成真正会被用户使 用的程序。不要急于去一个要你做“头”而独挡一面的地方,因为向别人学习才是你的目的。找工作也是一样,不要只看待遇和职衔,要挑一个你能够学习的环境, 一个愿意培养员工的企业,一个重视你的专业的公司。最后,还要挑一个好老板。
希望大家都能把握机会,养成好的学习习惯,把算法学精学透;希望大家都能有一个美好的未来!

新浪科技讯 北京时间1月10日消息,据国外媒体报道,由于大量用户争相排队下载Windows 7 Beta导致服务器不堪重负,微软周五下午宣布延迟发布Windows 7 Beta。
  微软CEO史蒂夫·鲍尔默(Steve Ballmer)周三在消费电子展(CES)上发表主题演讲时宣布,将于本周五面向公众发布Windows 7 Beta。但由于排队下载的用户过多,导致微软服务器被挤爆。
  微软在Windows 7官方博客中称:“由于用户对Windows 7 Beta热情较高,导致服务器超负荷运转。在发布Beta之前,我们将对Microsoft.com网站增加额外的硬件支持。”
  微软还称:“我们将确保为用户提供最佳的下载体验,一旦发布Beta,我们会立即通知用户。”周五早上,在微软上传Beta文件之前,就已经有迹象表明微软服务器不堪重负。按计划,微软只提供250万份Windows 7 Beta下载。

刚下的,每秒1MB多啊(刚刚链接上的速度是10MB多,吓了我一跳,等稳定下来就1MB多)
补充两个windows 7地址:
Windows 7 Beta 32bit(2.44GB):
http://download.microsoft.com/download/6/3/3/633118BD-6C3D-45A4-B985-F0FDFFE1B021/EN/7000.0.081212-1400_client_en-us_Ultimate-GB1CULFRE_EN_DVD.iso
Windows 7 Beta 64bit(3.15GB):
http://download.microsoft.com/download/6/3/3/633118BD-6C3D-45A4-B985-F0FDFFE1B021/EN/7000.0.081212-1400_client_en-us_Ultimate-GB1CULXFRE_EN_DVD.iso
语言包:http://dl.pconline.com.cn/download/53202-1.html 没找到官方下载地址,就用这个吧.

未获取函数指针就调用函数(如直接连接mswsock..lib并直接调用AcceptEx)的消耗是很大的,因为AcceptEx 实际上是存在于Winsock2结构体系之外的。每次应用程序常试在服务提供层上(mswsock之上)调用AcceptEx时,都要先通过WSAIoctl获取该函数指针。如果要避免这个很影响性能的操作,应用程序最好是直接从服务提供层通过WSAIoctl先获取这些APIs的指针。  

奇迹世界 network 类里面就进行指针获取

void MsWinsockUtil::LoadExtensionFunction( SOCKET ActiveSocket )
{
//AcceptEx 窃荐 啊廉坷扁 (dll俊辑..)
GUID acceptex_guid = WSAID_ACCEPTEX;
LoadExtensionFunction( ActiveSocket, acceptex_guid, (void**) &m_lpfnAccepteEx);

//TransmitFile 窃荐 啊廉坷扁 (dll俊辑..)
GUID transmitfile_guid = WSAID_TRANSMITFILE;
LoadExtensionFunction( ActiveSocket, transmitfile_guid, (void**) &m_lpfnTransmitFile);

//GetAcceptExSockaddrs 窃荐 啊廉坷扁
GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
LoadExtensionFunction( ActiveSocket, guidGetAcceptExSockaddrs, (void**) &m_lpfnGetAcceptExSockAddrs);

//DisconnectEx 窃荐 啊廉坷扁
GUID guidDisconnectEx = WSAID_DISCONNECTEX;
LoadExtensionFunction( ActiveSocket, guidDisconnectEx, (void**) &m_lpfnDisconnectEx );
}

bool MsWinsockUtil::LoadExtensionFunction( SOCKET ActiveSocket, GUID FunctionID, void **ppFunc )
{
DWORD dwBytes = 0;

if (0 != WSAIoctl(
   ActiveSocket,
   SIO_GET_EXTENSION_FUNCTION_POINTER,
   &FunctionID,
   sizeof(GUID),
   ppFunc,
   sizeof(void *),
   &dwBytes,
   0,
   0))
{
   return false;
}

return true;
}

LPFN_ACCEPTEX     MsWinsockUtil::m_lpfnAccepteEx     = NULL;
LPFN_TRANSMITFILE    MsWinsockUtil::m_lpfnTransmitFile    = NULL;
LPFN_GETACCEPTEXSOCKADDRS MsWinsockUtil::m_lpfnGetAcceptExSockAddrs = NULL;
LPFN_DISCONNECTEX    MsWinsockUtil::m_lpfnDisconnectEx    = NULL;

 

收包和发包循环:

服务器需要进行的连接如下:

1、 与其他服务器连接

2、监听绑定端口

这个2个内容都封装进SESSION内里面,通过NETWORKOBJECT对象判断该进行哪部分的包处理

if( !pIOCPServer->Init( &desc, 1 ) )
根据参数&desc ,对完成端口进行设置

内容有:创建 io_thread(工作者线程), accept_thread(绑定端口),connect_thread(连接其他服务器), send_thread(收包线程),并根据连接的最大数目分配好session pool。

if( !pIOCPServer->StartListen( CLIENT_IOHANDLER_KEY, "127.0.0.1", 6000 ) )
{
   printf( "监听出错" );
   return 0;
}

pIOCPServer->Connect( CLIENT_IOHANDLER_KEY, pNetObj, "127.0.0.1", 7000 );

收包:

pIOCPServer->Update()      ---------》 IOHANDLER_MAP_ITER it->second->Update()    ----------》

VOID IoHandler::Update()
{
ProcessActiveSessionList();

if( !m_pAcceptedSessionList->empty() )
{
   ProcessAcceptedSessionList();
}

if( !m_pConnectSuccessList->empty() )
{
   ProcessConnectSuccessList();
}

if( !m_pConnectFailList->empty() )
{
   ProcessConnectFailList();
}

KickDeadSessions();
}   

收包循环

    if( !pSession->ProcessRecvdPacket( m_dwMaxPacketSize ) )
    {
     pSession->Remove();
    }

发包循环

unsigned __stdcall send_thread( LPVOID param )
{
IOCPServer *pIOCPServer = (IOCPServer*)param;
IOHANDLER_MAP_ITER it;
while( !pIOCPServer->m_bShutdown )
{
   Sleep( 10 );

   for( it = pIOCPServer->m_mapIoHandlers.begin(); it != pIOCPServer->m_mapIoHandlers.end(); ++it )
   {
    it->second->ProcessSend();
   }
}

return 0;
}

d、接受SOCKET连接并进行完成端口绑定

VOID IoHandler::ProcessAcceptedSessionList()
{
SESSION_LIST_ITER   it;
Session      *pSession;

// 立加俊 己傍茄 技记甸阑 罐酒敌 烙矫 府胶飘肺 颗辫
m_pAcceptedSessionList->Lock();
m_pTempList->splice( m_pTempList->end(), *m_pAcceptedSessionList );//将m_pAcceptedSessionList 合并到TEMPLIST
m_pAcceptedSessionList->Unlock();

// 立加俊 己傍茄 技记俊 措茄 贸府
for( it = m_pTempList->begin(); it != m_pTempList->end(); ++it )
{
   pSession = *it;

   // 弥绊悼立荐甫 檬苞窍绰 版快 角菩
   if( m_numActiveSessions >= m_dwMaxAcceptSession )
   {
    printf( "connection full! no available accept socket!\n" );
    m_pTempList->erase( it-- );
    ReuseSession( pSession );
    continue;
   }

   // IOCP绑定
   CreateIoCompletionPort( (HANDLE)pSession->GetSocket(), m_hIOCP, (ULONG_PTR)pSession, 0 );

   // Recv俊 角菩窍绰 版快 贸府
   if( !pSession->PreRecv() )
   {
    m_pTempList->erase( it-- );
    ReuseSession( pSession );
    continue;
   }

   //--------------------------------
   // 己傍利栏肺 立加等 技记 贸府
   //--------------------------------

   // 匙飘亏 坷宏璃飘 积己 夸没
   NetworkObject *pNetworkObject = m_fnCreateAcceptedObject();
   assert( pNetworkObject );

   // 匙飘亏 坷宏璃飘 官牢爹
   pSession->BindNetworkObject( pNetworkObject );

   // 立加矫 檬扁拳 棺 NetworkObject肺 立加 烹瘤
   pSession->OnAccept();

   // 悼立荐 刘啊
   ++m_numActiveSessions;
}

if( !m_pTempList->empty() )
{
   // 立加俊 己傍茄 技记甸阑 ActiveSessionList俊 眠啊
   m_pActiveSessionList->Lock();
   m_pActiveSessionList->splice( m_pActiveSessionList->begin(), *m_pTempList );
   m_pActiveSessionList->Unlock();
}
}

PreRecv() 的动作判断SOCKET是否继续有效

BOOL Session::PreRecv()
{
WSABUF wsabuf;

m_pRecvBuffer->GetRecvParam( (BYTE**)&wsabuf.buf, (int&)wsabuf.len );

ZeroMemory( &m_recvIoData, sizeof(OVERLAPPEDEX) );

m_recvIoData.dwOperationType = RECV_POSTED;

int ret = WSARecv( GetSocket(), &wsabuf, 1, &m_recvIoData.dwIoSize, &m_recvIoData.dwFlags, &m_recvIoData, NULL );

if( ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING )
{
   return FALSE;
}

return TRUE;
}

b、代码实现连接

连接每个服务器都用继承自ServerSession 的类实现

有如下类

AgentServerSession

BattleServerSession

FieldServerSession

GameDBProxySession

GuildServerSession

MasterServerSession

基类ServerSession 有 update 实现心跳连接

VOID ServerSession::Update()
{
if( IsForConnect() )
{
   // heartbeat 焊郴扁
   DWORD dwCurTick = GetTickCount();
   if( dwCurTick - m_dwLastHeartbeatTick > 10000 )
   {
    m_dwLastHeartbeatTick = dwCurTick;

    MSG_HEARTBEAT msg;
    msg.m_byCategory   = 0;
    msg.m_byProtocol   = SERVERCOMMON_HEARTBEAT;
    Send( (BYTE*)&msg, sizeof(MSG_HEARTBEAT) );
   }
}
}

每个Session要连接服务器的时候

VOID GameDBProxySession::OnConnect( BOOL bSuccess, DWORD dwSessionIndex )
{
ServerSession::OnConnect( bSuccess, dwSessionIndex );

if( bSuccess )
{
   ServerSession::SendServerType();

   g_pGameServer->ConnectTo( AGENT_SERVER );
}
else
{
   //SUNLOG( eFULL_LOG, "Can't connect to game DB proxy." );
}
}

VOID GameServer::ConnectTo( eSERVER_TYPE eServerType )
{
switch( eServerType )
{
case MASTER_SERVER:
   ConnectToServer( m_pMasterServerSession,
    (char*)m_pMasterServerSession->GetConnectIP().c_str(), m_pMasterServerSession->GetConnectPort() );
        break;

case GAME_DBPROXY:
   ConnectToServer( m_pGameDBProxySession,
    (char*)m_pGameDBProxySession->GetConnectIP().c_str(), m_pGameDBProxySession->GetConnectPort() );
   break;

case AGENT_SERVER:
   ConnectToServer( m_pAgentServerSession,
    (char*)m_pAgentServerSession->GetConnectIP().c_str(), m_pAgentServerSession->GetConnectPort() );
   break;
case GUILD_SERVER:
   ConnectToServer( m_pGuildServerSession,
    (char*)m_pGuildServerSession->GetConnectIP().c_str(), m_pGuildServerSession->GetConnectPort() );
   break;

default:
   ASSERT( !"弊繁 辑滚 鸥涝篮 绝绢夸" );
}
}

DWORD GameServer::ConnectToServer( NetworkObject * pNetworkObject, char * pszIP, WORD wPort )
{
return m_pIOCPServer->Connect( SERVER_IOHANDLER, pNetworkObject, pszIP, wPort );
}

DWORD IOCPServer::Connect( DWORD dwIoHandlerKey, NetworkObject *pNetworkObject, char *pszIP, WORD wPort )
{
if( pNetworkObject == NULL ) return 0;

IOHANDLER_MAP_ITER it = m_mapIoHandlers.find( dwIoHandlerKey );

assert( it != m_mapIoHandlers.end() );

return it->second->Connect( pNetworkObject, pszIP, wPort );
}

c、代码实现监听

VOID GameServer::StartListen()
{
SERVER_ENV * pServerEnv = m_pFileParser->GetServerEnv();

if( !m_pIOCPServer->IsListening( SERVER_IOHANDLER ) )
{
   DISPMSG( "[GameServer::StartListen] Starting listen(%s:%d)...\n", pServerEnv->ServerIoHandler.szIP, pServerEnv->ServerIoHandler.wPort );
   if( !m_pIOCPServer->StartListen( SERVER_IOHANDLER, pServerEnv->ServerIoHandler.szIP, pServerEnv->ServerIoHandler.wPort ) )
   {
    DISP_FAIL;
    return ;
   }
   DISP_OK;
}
}

BOOL IOCPServer::StartListen( DWORD dwIoHandlerKey, char *pIP, WORD wPort )
{
IOHANDLER_MAP_ITER it = m_mapIoHandlers.find( dwIoHandlerKey );

assert( it != m_mapIoHandlers.end() );

return it->second->StartListen( pIP, wPort );
}

1、服务器内容

a、不同机器上的分为

   DBProxy //数据库

Guild //公会数据

Master //主服务器 Agent //副本服务器

4种服务器,代码提供了很清晰的每个服务器的HANDLER FUNC TABLE(HASH)。

class PacketHandler : public Singleton<PacketHandler>
{
typedef VOID (*fnHandler)( CScence * pScence, GamePackHeader * pMsg, WORD wSize );
//typedef VOID (*fnHandler_CG)( Player * pPlayer, GamePackHeader * pMsg, WORD wSize );

public:
PacketHandler();
~PacketHandler();

BOOL       RegisterHandler_DG();
//BOOL       RegisterHandler_CG();
BOOL       RegisterHandler_GM();
BOOL       RegisterHandler_AG();
BOOL       RegisterHandler_Actor();

VOID       ParsePacket_DG( CScence * pScence, GamePackHeader * pMsg, WORD wSize );
//VOID       ParsePacket_CG( Player * pPlayer, GamePackHeader * pMsg, WORD wSize );
VOID       ParsePacket_GM( CScence * pScence, GamePackHeader * pMsg, WORD wSize );
VOID       ParsePacket_AG( CScence * pScence, GamePackHeader * pMsg, WORD wSize );
VOID       ParsePacket_Actor( CScence * pScence, GamePackHeader * pMsg, WORD wSize );

private:

BOOL       AddHandler_DG( BYTE category, BYTE protocol, fnHandler fnHandler );
//BOOL       AddHandler_CG( BYTE category, BYTE protocol, fnHandler_CG fnHandler );
BOOL       AddHandler_GM( BYTE category, BYTE protocol, fnHandler fnHandler );
BOOL       AddHandler_AG( BYTE category, BYTE protocol, fnHandler fnHandler );
BOOL       m_FunctionMap_Acotr( BYTE category, BYTE protocol, fnHandler fnHandler );

struct FUNC_DG : public BASE_FUNC
{
   fnHandler     m_fnHandler;
};
struct FUNC_GM : public BASE_FUNC
{
   fnHandler     m_fnHandler;
};
struct FUNC_AG : public BASE_FUNC
{
   fnHandler     m_fnHandler;
};
struct FUNC_ACTOR : public BASE_FUNC
{
   fnHandler     m_fnHandler;
};

FunctionMap      m_FunctionMap_DG;
FunctionMap      m_FunctionMap_CG;
FunctionMap      m_FunctionMap_GM;
FunctionMap      m_FunctionMap_AG;
FunctionMap      m_FunctionMap_Actor;
};

CPP。

#include "PacketHandler.h"

PacketHandler::PacketHandler()
{

}

PacketHandler::~PacketHandler()
{
}

BOOL PacketHandler::RegisterHandler_DG()
{
//#define HANDLER_DG( c, p ) if( !AddHandler_DG( c, p, Handler_DG_CHARINFO::On##p ) ) return FALSE

return TRUE;
}

BOOL PacketHandler::RegisterHandler_Actor()
{
#define HANDLER_GZ( c, p ) if( !AddHandler_Actor( c, p, Handler_GZ_GUILD::On##p ) ) return FALSE

return TRUE;
}

BOOL PacketHandler::RegisterHandler_GM()
{
//if( !AddHandler_GM( GM_CONNECTION, GM_CONNECTION_SERVER_INFO_CMD, Handler_GM::OnGM_CONNECTION_SERVER_INFO_CMD ) )
// return FALSE;
//if( !AddHandler_GM( GM_OPERATION, GM_RELOAD_DATA_CMD, Handler_GM::OnGM_RELOAD_DATA_CMD ) )
// return FALSE;
//if( !AddHandler_GM( SERVERCOMMON, SERVERCOMMON_SERVERSHUTDOWN_REQ, Handler_GM::OnSERVERCOMMON_SERVERSHUTDOWN_REQ ) )
// return FALSE;

return TRUE;
}

BOOL PacketHandler::RegisterHandler_AG()
{
// CG_CHARINFO
//if( !AddHandler_AG( CG_CHARINFO, CG_CHARINFO_SELECT_INFO_SYN, Handler_CG_CHARINFO::OnCG_CHARINFO_SELECT_INFO_SYN))
//       return FALSE;

return TRUE;
}

VOID PacketHandler::ParsePacket_Actor( CScence * pScence, GamePackHeader * pMsg, WORD wSize )
{
if( 0xff == pMsg->m_byCategory )
{
}

FUNC_GZ * pFuncInfo = (FUNC_GZ *)m_FunctionMap_GZ.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) );

if( NULL == pFuncInfo )
{
   //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_GZ] PacketType Error GZ!!"); 
   return ;
}

pFuncInfo->m_fnHandler( pScence, pMsg, wSize );
}
VOID PacketHandler::ParsePacket_DG( CScence * pScence, GamePackHeader * pMsg, WORD wSize )
{
if( 0xff == pMsg->m_byCategory )
{
}

FUNC_DG * pFuncInfo = (FUNC_DG *)m_FunctionMap_DG.Find( MAKEWORD( pMsg->wType,pMsg->m_byProtocol ) );

if( NULL == pFuncInfo )
{
   //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_DG] PacketType Error DG!!"); 
   return ;
}

pFuncInfo->m_fnHandler( pScence, pMsg, wSize );
}

VOID PacketHandler::ParsePacket_GM( CScence * pScence, GamePackHeader * pMsg, WORD wSize )
{
if( 0xff == pMsg->m_byCategory )
{
}

FUNC_GM * pFuncInfo = (FUNC_GM *)m_FunctionMap_GM.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) );

if( NULL == pFuncInfo )
{
   //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_GM] PacketType Error!! GM");
   return ;
}

pFuncInfo->m_fnHandler( pScence, pMsg, wSize );
}

VOID PacketHandler::ParsePacket_AG( CScence * pScence, GamePackHeader * pMsg, WORD wSize )
{
if( 0xff == pMsg->m_byCategory )
{
}

FUNC_AG * pFuncInfo = (FUNC_AG *)m_FunctionMap_AG.Find( MAKEWORD( pMsg->m_byCategory,pMsg->m_byProtocol ) );

if( NULL == pFuncInfo )
{
   //SUNLOG( eCRITICAL_LOG, "[PacketHandler::ParsePacket_AG] PacketType Error!! AG Category[%d] Protocol[%d] ", pMsg->m_byCategory,pMsg->m_byProtocol);
   return ;
}

pFuncInfo->m_fnHandler( pScence, pMsg, wSize );
}

BOOL PacketHandler::AddHandler_Acotr( BYTE category, BYTE protocol, fnHandler fnHandler)
{
FUNC_ACTOR * pFuncInfo    = new FUNC_ACTOR;
pFuncInfo->m_dwFunctionKey   = MAKEWORD( category, protocol );
pFuncInfo->m_fnHandler    = fnHandler;
return m_FunctionMap_Actor.Add( pFuncInfo );
}

BOOL PacketHandler::AddHandler_DG( BYTE category, BYTE protocol, fnHandler fnHandler)
{
FUNC_DG * pFuncInfo     = new FUNC_DG;
pFuncInfo->m_dwFunctionKey   = MAKEWORD( category, protocol );
pFuncInfo->m_fnHandler    = fnHandler;
return m_FunctionMap_DG.Add( pFuncInfo );
}

BOOL PacketHandler::AddHandler_GM( BYTE category, BYTE protocol, fnHandler fnHandler)
{
FUNC_GM * pFuncInfo     = new FUNC_GM;
pFuncInfo->m_dwFunctionKey   = MAKEWORD( category, protocol );
pFuncInfo->m_fnHandler    = fnHandler;
return m_FunctionMap_GM.Add( pFuncInfo );
}
BOOL PacketHandler::AddHandler_AG( BYTE category, BYTE protocol, fnHandler fnHandler)
{
FUNC_AG * pFuncInfo     = new FUNC_AG;
pFuncInfo->m_dwFunctionKey   = MAKEWORD( category, protocol );
pFuncInfo->m_fnHandler    = fnHandler;
return m_FunctionMap_AG.Add( pFuncInfo );
}

值得注意的是此类是singleton,这样只能实例化一次,带来的好处就是没有多个实例造成的代码泛滥

b、代码实现

上次已经绘制过基本图元了, 这次只不过要贴张图而已.....

本来我想用Graphics的Model渲染流程来做, 不过这一层太高级了, 都是什么场景管理资源映射之类的

做低级的事情, 就要用低级的API嘛

图形渲染的底层是CoreGraphics, 这个层我不打算再单独写(翻译)一篇了, 因为都是Direct3D概念的一些抽象. 也就是说D3D用熟了基本上一看就明白(用GL的我就不清楚啦, 嘿嘿, N3的作者都放弃用GL去实现@_@).

还记得D3D Tutorial中的Textured例子不? 需要的东西有带纹理坐标的点, 纹理. N3中也一样, 不过, 这里没法用固定管线了.

N3的设计的时候就放弃了固定管线(多么明智呀, 别喷我-_-, 我只会shader.......), 所以在这之前我们要先写一个shader来进行绘制.

因为我们只是进行简单的演示, 就尽量简单了, 写一个2D的纹理绘制, 你可以用来做UI:

  1. //------------------------------------------------------------------------------
  2. //  texture2d.fx
  3. //  texture shader for 2D(UI)
  4. //  (C) xoyojank
  5. //------------------------------------------------------------------------------
  6. float2 halfWidthHeight  : HalfWidthHeight;
  7. texture diffMap     : DiffMap0;
  8. sampler diffMapSampler = sampler_state
  9. {
  10.     Texture = <diffMap>;
  11.     AddressU = Clamp;
  12.     AddressV = Clamp;
  13.     MinFilter = Point;
  14.     MagFilter = Point;
  15.     MipFilter = None;
  16. };
  17. struct VS_INPUT
  18. {
  19.     float3 pos  : POSITION;
  20.     float2 uv       : TEXCOORD;
  21. };
  22. struct VS_OUTPUT
  23. {
  24.     float4 pos  : POSITION;
  25.     float2 uv       : TEXCOORD;
  26. };
  27. //------------------------------------------------------------------------------
  28. /**
  29. */
  30. VS_OUTPUT
  31. VertexShaderFunc(VS_INPUT input)
  32. {
  33.     VS_OUTPUT output;
  34.     output.pos.xy = float2(input.pos.x - halfWidthHeight.x, halfWidthHeight.y - input.pos.y) / halfWidthHeight;
  35.     output.pos.zw = float2(input.pos.z, 1.0f);
  36.     output.uv = input.uv;
  37. return output;
  38. }
  39. //------------------------------------------------------------------------------
  40. /**
  41. */
  42. float4
  43. PixelShaderFunc(float2 uv : TEXCOORD0) : COLOR
  44. {
  45. return tex2D(diffMapSampler, uv);
  46. }
  47. //------------------------------------------------------------------------------
  48. /**
  49. */
  50. technique Default
  51. {
  52.     pass p0
  53.     {
  54.         ColorWriteEnable  = RED|GREEN|BLUE|ALPHA;
  55.         ZEnable           = False;
  56.         ZWriteEnable      = False;
  57.         StencilEnable     = False;
  58.         FogEnable         = False;
  59.         AlphaBlendEnable  = True;
  60.         SrcBlend          = SrcAlpha;
  61.         DestBlend         = InvSrcAlpha;
  62.         AlphaTestEnable   = False;
  63.         ScissorTestEnable = False;
  64.         CullMode          = CW;        
  65.         VertexShader = compile vs_3_0 VertexShaderFunc();
  66.         PixelShader = compile ps_3_0 PixelShaderFunc();
  67.     }
  68. }

值得一提的是CullMode = CW, 为什么? 因为N3用的右手坐标系, 这点又跟D3D不一样了........为什么呢? 难道写MAYA跟MAX的插件的时候比较省事?

还是要跟上一次一样设置顶点格式并载入VertexBuffer:

  1. // vertex
  2.             Array<VertexComponent> vertexComponents;
  3.             vertexComponents.Append(VertexComponent(VertexComponent::Position, 0, VertexComponent::Float3));
  4.             vertexComponents.Append(VertexComponent(VertexComponent::TexCoord, 0, VertexComponent::Float2));
  5. float vertex[4][5] = {
  6.                 {0.0f,  0.0f,   0.0f,   0.0f, 0.0f},
  7.                 {0.0f,  256.0f, 0.0f,   0.0f, 1.0f}, 
  8.                 {256.0f,0.0f,   0.0f,   1.0f, 0.0f}, 
  9.                 {256.0f,256.0f, 0.0f,   1.0f, 1.0f}
  10.             };
  11.             vertexBuffer = VertexBuffer::Create();
  12.             Ptr<MemoryVertexBufferLoader> vbLoader = MemoryVertexBufferLoader::Create();
  13.             vbLoader->Setup(vertexComponents, 4, vertex, 4 * 5 * sizeof(float));
  14.             vertexBuffer->SetLoader(vbLoader.upcast<ResourceLoader>());
  15.             vertexBuffer->Load();
  16.             vertexBuffer->SetLoader(NULL);

纹理的创建其实跟顶点差不多, 因为它都是属于资源的一种, 详见Nebula3资源子系统

  1. // texture
  2.             texture = Texture::Create();
  3.             texture->SetResourceId(ResourceId("bin:razor.jpg"));
  4.             texture->SetLoader(StreamTextureLoader::Create());
  5.             texture->Load();
  6.             texture->SetLoader(NULL);

shader的加载跟上一次一样, 只是参数不同:

  1. // shader
  2. this->shaderInstance = this->shaderServer->CreateShaderInstance(ResourceId("shd:texture2d"));
  3.             Ptr<ShaderVariable> halfWidthHeight = this->shaderInstance->GetVariableBySemantic(ShaderVariable::Semantic("HalfWidthHeight"));
  4.             float2 halfWH = float2(this->renderDevice->GetDefaultRenderTarget()->GetWidth(), this->renderDevice->GetDefaultRenderTarget()->GetHeight()) * 0.5f;
  5.             halfWidthHeight->SetFloatArray(&halfWH.x(), 2);
  6.             Ptr<ShaderVariable> diffMap = this->shaderInstance->GetVariableBySemantic(ShaderVariable::Semantic("DiffMap0"));
  7.             diffMap->SetTexture(texture);

绘制嘛, 当然改成矩形了, 图片可贴不到一跟线上:

  1. this->renderDevice->BeginFrame();
  2. this->renderDevice->BeginPass(this->renderDevice->GetDefaultRenderTarget(), this->shaderInstance);
  3.         PrimitiveGroup primGroup;
  4.         primGroup.SetBaseVertex(0);
  5.         primGroup.SetNumVertices(4);
  6.         primGroup.SetPrimitiveTopology(PrimitiveTopology::TriangleStrip);
  7. this->renderDevice->SetVertexBuffer(this->vertexBuffer);
  8. this->renderDevice->SetPrimitiveGroup(primGroup);
  9. this->renderDevice->Draw();
  10. this->renderDevice->EndPass();
  11. this->renderDevice->EndFrame();
  12. this->renderDevice->Present();

上图:

图形子系统是渲染层中图形相关子系统的最高层. 它基本上是Mangalore图形子系统的下一个版本, 但是现在整合进了Nebula, 并且与低层的渲染代码结合得更加紧密. 最基本的思想是实现一个完全自治的图形”世界”, 它包含模型, 灯光, 还有摄像机实体, 而且只需要与外部世界进行最少的通信. 图形世界的最主要操作是加入和删除实体, 还有更新它们的位置.
因为Mangalore的图形子系统跟Nebula2的完全分界线从Nebula3中移除了, 很多设想都可以用更少的代码和交互来实现.
图形子系统也会为了异步渲染而多线程化, 它和所有的底层渲染子系统都会生存在它们自己的fat-thread中. 这本应是Nebula3层次结构中更高级的东西, 但是我选择了这个位置, 因为这是游戏跟渲染相关通信最少的一部分代码. 正是因为图形代码有了更多的”自治权”, 游戏相关的代码可以跟图形以完全不同的帧率来运行, 不过这需要实践来证明一下. 但是我一定会尝试, 因为完全没有必要让游戏逻辑代码运行在10帧以上(格斗游戏迷们可能会反对吧).
图形子系统中最重要的公有类有:

  • ModelEntity
  • CameraEntity
  • LightEntity
  • Stage
  • View

一个ModelEnity表示了一个可见的图形对象, 它包括位置, 包围体和内嵌的Model资源. 一个Model资源是一个完全的3D模型, 包括几何体, 材质, 动画, 层级变换等…(后面会提到).
一个CameraEntity描述了图形世界中的一个视景体, 为渲染提供View和Project矩阵.
一个LightEntity描述了一个动态光源. Nebula3的光源属性还没有最终确定, 但是我的目标是一个相对灵活地近似(最后一个光源不会超过几个shader参数).
Stage和View是Nebula3图形子系统新增的内容. 在Mangalore中, 图形实体是生存在一个单独的图形Level类里, 任何时候只能有一个Level和一个摄像机. 这对于只需要渲染一个世界到帧缓存(frame buffer)的情况来说还是不错的. 但许多游戏程序需要更复杂的渲染, 如在GUI中渲染一个使用单独灯光的3D对象, 而它又跟其它的图形世界是隔离的. 还有反射或像监视器之类的东西都需要一个额外的视口, 诸如此类. 在Mangalore中, 这个问题通过OffscreenRenderer类得到解决, 虽说比较容易使用, 但是具有一些使用限制并且需要更多事后的思考.
Nebula3提供了一个基于State和View的更加简洁的解决方案. 一个Stage就是一个图形实体的容器, 表示一个图形世界. 同一时间可能存在多个Stage, 但是它们之间是互相隔绝的. 每个实体在一个时刻只连接到了一个Stage(虽说克隆一个已有实体是一件很简单的事情). 除了简单地把实体组织到一起外, Stage的主要工作是根据它们之间的关系来加速可见性查询. 应用程序可以派生Stage的子类来实现完全不同的可见性查询方案.
一个View对象通过一个CameraEnity渲染stage到一个RenderTarget. 任何stage都可以连接任意数量的View对象. View对象可能会互相依赖(也可能是连接到不同stage的View), 所以更新一个View会首先强制更新另一个View的RenderTarget(这在一个View渲染需要使用另一个View的RenderTarget做为纹理时很方便). View对象完全实现了自己的渲染循环. 应用程序可以在View的子类中方便地实现它自己的渲染策略(如每个light一个pass VS 每个pass多个light, 渲染到cubemap, 等等).
总而言之, 一个Stage完全控制了可见性查询流程, 而一个View则完全控制了渲染流程.
图形子系统的一个最主要的工作就是根据可见性查询的结果来决定哪些实体需要被渲染. 一个可见性查询在实体间建立了一个双向的链接, 它有两种形式: 摄像机链接和灯光链接. 摄像机链接把一个摄像机和在它视景体内的模型连接到了一起. 因为链接是双向的, 所以摄像机知道所有的在它视景体范围内的模型, 而模型也知道所有可以看到它的摄像机. 灯光链接在灯光与模型之间建立了相似的关系, 一个灯光具有所有受它影响的模型的链接, 一个模型也知道所有影响它的灯光.
加速可见性查询最重要的类就是Cell类. 一个Cell是一个图形实体和子Cell的可见性容器, 它必须遵循2条简单的规则:

  1. 如果一个Cell是完全可见的, 那么它所有的图形实体和子Cell都必须可见.
  2. 如果一个Cell是完全不可见的, 那么它所有的图形实体和子Cell都必须不可见.

Cell是附属于Stage的, 它们形成了一棵有根Cell的树形层次结构. 标准的Cell支持简单的空间划分方案, 如四叉树和八叉树, 但如果像其它的可见性方案, 如portal, 就需要派生Cell的子类来实现了. 子类唯一的功能限制就是上面标出的那两条规则.
当一个图形体连接到一个Stage时, 它会被插入”接受” (通常仅仅是容纳)它的最低级的Cell中. 当更新图形实体的变换信息或改变包围体时, 它会根据需要改变在Cell层次中的位置.
Stage居住在StageBuilder类当中, 应用程序应当派生StageBuilder来创建一个Stage的初始状态(通过加入Cell和实体). Nebula3会提供一些标准的StageBuilder集合, 这应该能够满足大多数应用程序的需要了.
这只是图形子系统的一个粗略的概述. 因为当前只有一个最基本的实现, 很多细节接下来可能会有所更改.

Nebula3的代码运行在两种根本不同的方案中. 第一种方案我称之为”Fat Thread”. 一个Fat Thread在一个线程中运行一个完整的子系统(如渲染, 音频, AI, 物理, 资源管理), 并且基本上锁定在一个特定的核心上.

第二种类型的线程我叫它”Job”. 一个job是一些数据和用于处理这些数据的包装成C++对象的代码. 工作调度程序掌管了Job对象, 并且把工作分配给低负载的核心来保持它们一直处于忙碌状态.

显然, 挑战就是设计一个经过全面考虑的系统, 以保持所有的核心一直均匀地忙碌着. 这不但意味着连续的活动需要在游戏每帧的空闲时期内轮流交替, 而且要求job对象不得不事先(如每帧前)创建好, 这样才能在各种Fat Thread空闲时填充当前帧的空白.

这是我希望进行更多试验和调整的地方.

第二个挑战就是让程序员的工作尽量的简单. 一个游戏应用程序员(逻辑程序员)在任何时候都不应该关心他运行在一个多线程的环境中, 不应该担心会产生死锁或改写了其它线程的数据, 也不应该瞎搞一些临界区, 事件和信号量. 同样, 整个引擎的架构也不应该是”脆弱的”. 大部分传统的多线程代码在一定程度上都会发生紊乱, 或者忘记了临界区而打乱数据.

当线程间需要进行数据共享和通信时, 多线程就变得很棘手. 像两个临界区这样的解决方案也会导致脆弱代码问题.

从大的角度来说, Nebula3通过一个”并行Nebula”的概念解决了这个两个问题. 其思想就是运行了一个完整子系统的”Fat Thread”都有自己的最小Nebula运行库, 这个最小运行库刚好包含了这个子系统需要的部分. 因此, 如果这个运行在它自己线程中的子系统需要进行文件访问, 它会有一个跟其它Fat Thread完全分离的文件服务器(file server). 这个解决方案的优点是, 大部分Nebula中的代码都不需要知道它运行在一个多线程的环境中, 因为在fat thread之间没有数据进行共享. 运行着的每个最小Nebula内核是跟其它Nebula内核完全隔离的. 缺点就是, 重复的数据会浪费一些内存, 但是我们只是占用几KB, 而不是MB.

这些数据冗余消除了细密的锁定, 并且解决把程序员从思考每一行代码的多线程安全性中解放了出来.

当然, 从某种意义上说Fat Thread间的通信是肯定会发生的, 要不然这整个思想就没有意义了. 方法就是建立一个且只有一个的标准通信系统, 并且保证这个通信系统是可靠而快速的. 这就是消息系统的由来. 要跟一个Fat Thread通信的话只有发送一个消息给它. 消息是一个简单的C++对象, 它包含了一些带有get/set方法的数据. 通过这个标准的通信手段, 实际上只有消息子系统才需要是线程安全的(同样, 访问跟消息相关的资源时, 如内存缓冲区, 必须受到约束, 因们它们代表了共享数据). (xoyojank: 我说咋那么多Message…)

这样虽然解决了Fat Thread方案中大多数的多线程问题, 但没有解决Job对象的任何事情. Nebula3很有可能需要约束一个Job对象能做什么和不能做什么. 最直接的行为就是限制job做内存缓冲区的计算. 那样的话, job中就不能存在复杂的运行库(不能文件I/O, 不能访问渲染等等). 如果这样还不够的话, 必须定义一个”job运行时环境”, 就像Fat Thread中的那样. 因为一个job不会发起它自己的线程, 而且还会被调度到一个已经存在的线程池中. 就这个方面来说, 这不存在什么问题.

到现在为止(xoyojank: 2007/01/21, 最新版本已经实现了多数子系统的多线程化), 只有IO子系统作为概念证明在Fat Thread中得到实现, 并且它运行得很今人满意. 在做传统的同步IO工作时, 一个Nebula3程序可以直接调用本地线程的IO子系统. 所以像列出文件夹的内容或删除一个文件, 只会调用一个简单的C++方法. 对于异步IO工作, 定义了一些常见的IO操作消息(如ReadStream, WriteStream, CopyFile, DeleteFile, 等等). 进行异步IO只需要几行代码: 创建一个消息对象, 填充数据, 并发送这个消息到一个IOInterface单件. 如果必要的话, 这可能会需要等待和轮询异步操作.

这样的好处就是, 整个IO子系统没有一行多线程意义上的代码, 因为各个在不同的Fat Thread中的IO子系统是完全隔离的(当然, 同步肯定会发生在一些IO操作上, 但那都留给操作系统了).