﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-一路向北-文章分类-游戏开发</title><link>http://www.cppblog.com/deane/category/6971.html</link><description>                    追逐梦想，永不停步......</description><language>zh-cn</language><lastBuildDate>Sun, 23 Jan 2011 08:10:54 GMT</lastBuildDate><pubDate>Sun, 23 Jan 2011 08:10:54 GMT</pubDate><ttl>60</ttl><item><title>游戏服务器中的数据库异步操作技术和游戏数据的保存机制</title><link>http://www.cppblog.com/deane/articles/138958.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 20 Jan 2011 03:29:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/138958.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/138958.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/138958.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/138958.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/138958.html</trackback:ping><description><![CDATA[<h3>&nbsp;</h3>
<br><br>&nbsp;&nbsp;&nbsp;&nbsp;原文：http://www.cppblog.com/jaxe/archive/2010/08/30/125258.html<br><br>　　在游戏服务器中，处理玩家登陆需要向数据库查询玩家的账号和密码，玩家上线和下线需要对玩家的角色数据从数据库中读取和保存。可以说，相对于游戏逻辑处理来说，数据库操作是一种相对很慢的操作，即便你通过使用多个线程多个数据库连接来提高数据库操作的处理能力，但是，在高并发高负载的服务器应用中，这样仍然会是相当的负载瓶颈。设想这样一种设计方案，见下图：<br><br><br><a onfocus=this.blur() href="http://upload.gameres.com/20108/sf_3014720_8498.png" target=_blank><img alt=按此在新窗口浏览图片 src="http://upload.gameres.com/20108/sf_3014720_8498.png" onload="javascript:if(screen.width-333<this.width)this.width=screen.width-333" border=0></a><br><br>　　在大量玩家登陆游戏服务器时，由于有大量的数据库访问请求，即便是有自己实现的CACHE机制，还是会导致服务器耗尽所有的逻辑线程资源，服务器的处理能力将降低成DBMS的处理能力。<br>　　&nbsp;<br>　　&nbsp;&nbsp;为了不阻塞逻辑线程，可以采用异步数据库访问的方式，即数据库操作请求提交给专门的数据库处理线程池，然后逻辑线程不再等待数据库处理结果，继续处理其他，不再阻塞在这里。<br><br>　　&nbsp;&nbsp;抽象的来看，对于一个需要持久化的游戏对象来说，可以考虑它有2个方法，读取和保存。那么我们抽象一个DBO接口：<br>　　&nbsp;<br>struct&nbsp;IDbo<br>{<br>　　&nbsp;virtual&nbsp;bool&nbsp;SaveToDB(DB*)=0;<br>　　&nbsp;virtual&nbsp;bool&nbsp;LoadFromDB(DB*)=0;<br>};<br><br><br><br>　　&nbsp;&nbsp;然后把设计方案改成下面这种：<br><br><br><a onfocus=this.blur() href="http://upload.gameres.com/20108/sf_301483_9785.png" target=_blank><img alt=按此在新窗口浏览图片 src="http://upload.gameres.com/20108/sf_301483_9785.png" onload="javascript:if(screen.width-333<this.width)this.width=screen.width-333" border=0></a><br><br>　　&nbsp;&nbsp;改成数据库异步处理后，在想想现在的游戏数据的保存机制应该是怎样改进的，为了保障数据安全，我们希望不只是玩家下线的时候才会保存玩家数据，而是希望每隔一段时间统一保存所有在线<span style="DISPLAY: none; FONT-SIZE: 0px">[来源：GameRes.com]</span>玩家的数据，那么，可以考虑这样的思路：假设我们有一个GAMEDB服务器，GAMEDB缓存了所有在线玩家的角色数据，每到保存时间，GAMEDB就将所有在线玩家的数据(DBO）的副本都统一提交给DB线程池，让它保存数据，提交的过程很快，提交完后，GAMEDB的逻辑线程仍能继续处理游戏服务器的更新和读取CACHE的请求。为什么要保存副本呢，DB线程的执行保存队列的过程也许很耗时，但是队列中的数据都是GAMEDB提交DBO那个时刻的数据，这样就能保证玩家的游戏数据的完整性。<br><br>　　&nbsp;&nbsp;&nbsp;当然，我这里提的这只是个思路，这里面还有很多细节没有讨论，例如如果DB线程池正在保存九点钟时刻保存的数据，到了十点钟新的保存时刻时，DB线程池还没保存完九点钟时刻的DBO副本队列，这时应该怎么处理；DBO对象的划分粒度的问题；DBO队列的优先级的问题等等。
<img src ="http://www.cppblog.com/deane/aggbug/138958.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2011-01-20 11:29 <a href="http://www.cppblog.com/deane/articles/138958.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简单实用的网游服务器架构</title><link>http://www.cppblog.com/deane/articles/138957.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 20 Jan 2011 03:25:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/138957.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/138957.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/138957.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/138957.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/138957.html</trackback:ping><description><![CDATA[<h3>&nbsp;</h3>
<h3><br>此文并不是聚润堂所在公司当前运营的网游游戏服务器架构，而是在看过了近十个商业网游的架构，在现阶段心目中规划的简单实用的网游服务器架构。<br><br>网游的本质是人和人之间关系，人越多，关系越多，越能把人留住。开服头三天，人数是最多，为了保证一个月后，玩家等级达到一定的等级，策划的玩法都可以完全展开，单组服务器容纳人数越多越好，如果是PVE结构的游戏，最好能容纳1万人左右。这样在一个月后流失率50%的情况下，还有5000人左右在一组服务器内，已经产生比较强大的社会结构，各种高级玩法才有进行的基础。如果是PVP洗用户的游戏，容纳人数越高越好。两款较好的国战游戏，《征途》达到了每组4万人，《成吉思汗》，推测至少也达到了两万人。<br><br>采用完美常用的分线结构，运营到后期，就会出现每线人数较少，线内，线和线之间都互动不起来。但如果不分线，在开服一刹那，用户全部堆积在同一个新手村，基本玩不动。《天龙八部》提出了较好的解决方案，建立多个平行的新手村地图，一主多副，开服时尽可能多的同时容纳新用户的涌入，高等<span style="DISPLAY: none; FONT-SIZE: 0px">[来源：GameRes.com]</span>级玩家从其它地图回新手村只能到达主新手村。<br><br>下图中每个方框表示一个独立的进程APP组件，每个服务进程如果发生宕机会影响部分用户，整体服务但不会全部中断。在宕机进程重启后，又可以并入整体，全部服务得以继续。<br><br>图片附件<br><br><a onfocus=this.blur() href="mhtml:file://E:\doc\简单实用的网游服务器架构.mht!http://upload.gameres.com/20108/sf_17193234_2297.png" target=_blank><img alt=按此在新窗口浏览图片 src="mhtml:file://E:\doc\简单实用的网游服务器架构.mht!http://upload.gameres.com/20108/sf_17193234_2297.png" onload="javascript:if(screen.width-333<this.width)this.width=screen.width-333" border=0></a><br><br>gls：game&nbsp;login&nbsp;server，游戏登录服务器，某种程序上，其不是核心组件，gls调用外部的接口，进行基本的用户名密码认证。此外需要实现很多附属的功能：登录排队（对开服非常有帮助），GM超级登录通道（GM可以不排队进入游戏），封测期间激活用户控制，限制用户登录，控制客户端版本等。<br><br>db：实质上是后台sql的大内存缓冲，隔离了数据库操作，比较内存中的数据，只把改变的数据定时批量写入sql。系统的算法，开发稳定性都要求非常高。<br><br>center：所有组件都要在这里注册，在线玩家的session状态都在这里集中存放，和各组件有心跳连接。所有对外的接口也全部通过这里。<br><br>角色入口：玩家登录游戏后的选择角色<br><br>gs：game&nbsp;server，最核心组件，同一地图，所有游戏逻辑相关的功能，都在这里完成。<br><br>gate：建立和用户的常链接，主要作sockt转发，屏蔽恶意包，对gs进行保护。协议加密解密功能，一个gate共享多个gs，降低跳转地图连接不上的风险。<br><br>IM，关系，寄售：表示其它组件，负责对应的跨地图发生全局的游戏逻辑。<br><br>细节是魔鬼。此架构简单、清晰、明了，和其它网游架构相比其实没有什么本质的区别。团队的代码开发能力，项目管理能力才是关键。一个gs承担能力有限，在现在硬件环境下，一个有经验的开发人员，一个gs应该能达到1500人的上限。整个架构的上限，瓶颈在center调度压力，db的读写压力。<br><br>引用一段深以为然的话做结束：<br>＝＝＝＝＝＝＝＝＝＝<br>http://blog.csdn.net/lfhfut/archive/2010/04/14/5483266.aspx<br><br>4。游戏开发并没有什么高深的技术<br><br>首先需要明确的一点，游戏项目是工程项目，不是科研项目。<br><br>工程项目的目的是在有限的人力跟财力之下实现出既定的需求，而这个需求从前面的分析可以知道，要求并不高，所以，需求的实现过程也就并没有多么高深。<br><br>至少在我经历过的项目里，没有什么惊天地泣鬼神似的英雄人物，没有创造出多么伟大的算法，我们所做的，只是使用现在的技术，现有的方法，拼合成一个软件产品，一个融合了程序、美术、策划劳动力的软件产品。<br><br>游戏开发的过程里，没有，也不需要多厉害的技术高手，需要的仅仅只是有耐心，有责任心的普通技术人员。<br>＝＝＝＝＝＝＝＝＝＝＝<br>最后赞一句：Dia真是跨平台画流程图的好软件，聚润堂的日常使用中已经完全替代了Visio。<br><br>参考：<br><br>白云哥的blog<br>http://blog.csdn.net/lfhfut/archive/2007/09.aspx<br><br>一种经典的网络游戏服务器架构&nbsp;<br>http://www.cppblog.com/johndragon/archive/2008/04/10/46768.html<br><br>传奇3的架构图<br>http://bbs.gameres.com/showthread.asp?threadid=47752&amp;page=10<br>185楼，187楼，分了两部分。聚润堂把其合在一起方便查看<br><br><br><a onfocus=this.blur() href="mhtml:file://E:\doc\简单实用的网游服务器架构.mht!http://upload.gameres.com/20108/sf_17193323_8964.gif" target=_blank><img alt=按此在新窗口浏览图片 src="mhtml:file://E:\doc\简单实用的网游服务器架构.mht!http://upload.gameres.com/20108/sf_17193323_8964.gif" onload="javascript:if(screen.width-333<this.width)this.width=screen.width-333" border=0></a>&nbsp;<br><br></h3>
<img src ="http://www.cppblog.com/deane/aggbug/138957.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2011-01-20 11:25 <a href="http://www.cppblog.com/deane/articles/138957.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>高性能服务器底层网络通信模块的设计方法</title><link>http://www.cppblog.com/deane/articles/76142.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Tue, 10 Mar 2009 11:51:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/76142.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/76142.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/76142.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/76142.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/76142.html</trackback:ping><description><![CDATA[<br><br><br>摘要：在对I/O 完成端口进行底层封装的基础上，提出一种具有高性能的、可扩展性的通用网络通信模块设计方案。该方案采用多种系<br>统性能优化技术，如线程池、对象池和环形缓存区等。该模块在Win32 平台上用C++开发完成，经过严格的压力和性能测试后，实验结果<br>表明该模块能够支持海量并发连接，具有较高的数据吞吐量，在实际项目应用中也取得了良好的表现。<br>关键词：完成端口；服务器；多线程；线程池；对象池；缓存区<br>Design Method of Underlying Module of Network Communication<br>for High Performance Server<br>WANGWen-wu1,2, ZHAOWei-dong1,2,WANG Zhi-cheng1,2, CHEN Yue1,2, HAN Xia-lin1,2<br>(1. State Education Commission Engineering Center for Enterprise Digital Technology, Shanghai 200092;<br>2. Research Center of CAD, Tongji University, Shanghai 201804)<br>【Abstract】On the base of underlying encapsulation for I/O Completion Port(IOCP), this paper presents a design solution with high performance<br>and scalable module of generic network communication, which emploies a variety of optimization techniques of system performance, such as thread<br>pool, object pool and ring buffer. The module is developed in C++ programming language on Win32 platform. Experimental results show that the<br>module can support massive concurrent connections, and has higher data throughput based on severe pressure and performance tests. The proposed<br>solution has also got a good performance in the actual project application.<br>【Key words】completion port; server; multi-threading; thread pool; object pool; ring buffer<br><br>计算机工程<br><br>Computer Engineering<br><br>第35 卷第3 期<br>Vol.35 No.3<br>2009 年2 月<br>February 2009<br><br>1 概述<br>要设计与开发出一款高性能的服务器(如网游服务器、<br>Web 服务器和代理服务器等)，一般都采用高效率的网络I/O<br>模型[1]。Linux 平台上经常会采用epoll 模型，而在Win32 平<br>台上完成端口(以下简称IOCP)模型[2]是设计与开发高性能<br>的、具有可伸缩性的服务器的最佳选择，它可以支持海量并<br>发客户端请求。多线程编程是服务器端开发常用技术，多线<br>程必然涉及线程间的通信与同步。如果使用不当，也会影响<br>到系统的性能，必须谨慎设计才能保证系统良好运行。减少<br>数据拷贝以及小对象频繁创建与销毁是一种很重要的提高系<br>统性能手段，可通过设计内存池或对象池加以解决。这几个<br>问题的提出，说明实际的高性能服务器研发比较复杂，尤其<br>是采用高效I/O 模型来架构服务器时，更是增加了开发的难<br>度，原因是这些模型的机制比较复杂。<br>底层网络通信模块是服务器应用程序的核心模块，也是<br>高性能服务器的最基础模块之一。它主要的功能是接收海量<br>并发连接、接收网络数据包、暂存和发送应用逻辑层的逻辑<br>数据包，所以，它也是上次应用逻辑和底层网络之间通信的<br>媒介。<br>2 IOCP 机制<br>要实现一个并发的网络服务器，比较简单的模型是：每<br>当一个请求到达就创建一个新线程，然后在新线程中为请求<br>服务。这种模型减轻了实际开发的复杂度，在并发连接较少<br>的情况下可以考虑使用，然而在高并发需求下并不适用。高<br>并发环境中，创建和销毁大量线程所花费的时间和消耗的系<br>统资源是巨大的，而且会加重线程调度的负担，同时线程上<br>下文切换(context switch)也会浪费许多宝贵的CPU 时间。<br>为了提高系统性能，首先必须有足够的可运行线程来充<br>分利用CPU 资源，但线程的数量不能太多。事实上，具体工<br>作线程的数量和并发连接数量不是直接相关联的。在Win32<br>平台下开发高效的服务器端应用程序，最理想的模型是IOCP<br>模型，该模型解决了一系列系统性能瓶颈问题。<br>IOCP 提供了最好的可伸缩性，而且其执行效率比较高，<br>采用这种网络模型可能会加大开发的复杂度， 但却是<br>Windows 平台上唯一适用于开发高负载服务器的技术。<br>IOCPWindows 系统的一种内核对象[3]，也是Win32 下最复杂<br>的一种I/O 模型，它通过一定数量的工作线程对重叠I/O 请<br>求进行处理，以便为已经完成的I/O 请求提供服务，相对其<br>他I/O 模型，它可以管理任意数量套接字句柄。它主要由等<br>待线程队列和I/O 完成队列2 个部分组成。一个完成端口对<br>象可以和多个套接字句柄相关联，当针对某个套接字句柄发<br>起的异步I/O 操作完成时，系统向该完成端口的I/O 完成队<br>列加入一个I/O 完成包。于此同时， 工作线程调用<br>GetQueuedCompletionStatus(以下简称GQCS)时，如果I/O 完<br>基金项目：广东省教育部产学研结合基金资助项目&#8220;基于RFID 技<br>术石化产品计量监控管理系统应用研究与开发&#8221;(2007A090302094)<br>作者简介：王文武(1983－)，男，硕士研究生，主研方向：CAD，<br>企业信息化；赵卫东，研究员、博士生导师；王志成，博士；陈悦、<br>韩下林，硕士研究生<br>收稿日期：2008-11-30 E-mail：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#106;&#101;&#114;&#114;&#121;&#46;&#119;&#101;&#110;&#119;&#117;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">jerry.wenwu@gmail.com</a><br>—104—<br>成队列中有完成包，当前调用就会返回，取得数据进行后续<br>的处理。<br>成功创建一个完成端口后，便可开始将套接字句柄与对<br>象关联到一起。但在关联套接字之前，首先必须创建一个或<br>多个工作者线程为完成端口提供服务。<br>3 模块设计方案<br>3.1 架构设计<br>在充分考虑服务器性能和扩展性的基础上，本文提出了<br>基于三层结构的系统设计方案，该模块的架构如图1 所示。<br>#OnConnectionEstablished() : void<br>#OnReadCompleted() : void<br>#OnWriteCompleted() : void<br>CIocpServer<br>#HandleRecvMessage() : void<br>CTestServer<br>#OnConnectionEstablished() : void<br>#OnReadCompleted() : void<br>#OnWriteCompleted() : void<br>#HandleRecvMessage() : void<br>CUserServer<br>图1 模块设计架构<br>图1 看上去并不复杂，但却是一个兼顾可扩展性和高性<br>能的架构，在实际项目应用中也取得了很好的表现，下面是<br>对图1 主要类的功能分析。<br>CIocpServer 类是完成端口服务器基本通信类，它使用<br>Windows 平台特有的IOCP 机制，对网络通信模型进行底层<br>封装。提供了基本的服务器端网络通信功能，这些功能主要<br>有开启服务器、关闭服务器、管理客户端连接列表、管理未<br>决的接受请求列表、发出异步操作等。同时通过多态机制向<br>它的派生类提供以下基本扩展接口：<br>(1)新连接确立的处理接口。<br>(2)客户端断开连接时的处理接口。<br>(3)连接出现错误时的处理接口。<br>(4)从客户端接收完数据后的处理接口。<br>(5)向客户端发送完数据后的处理接口。<br>(6)拼包处理接口。<br>CUserServer 类继承CIocpServer，在CIocpServer 的基础<br>上，CUserServer 加入了一些服务器逻辑处理功能，并且封装<br>了3 类数据队列和3 类处理线程，分别如下：<br>(1)接收数据包队列及接收线程：接收队列用于存放接收<br>到的数据包，此数据包还没有进行逻辑意义上的拼包，接收<br>线程从此队列中取出数据包，并将其拼装成逻辑意义上完整<br>的数据包加入到逻辑数据包队列中。<br>(2)逻辑数据包队列及逻辑处理线程：逻辑队列用于存放<br>已经拼包成了逻辑意义上的数据包，逻辑处理线程对此类数<br>据包进行逻辑解析，这里就是服务器的主要逻辑部分，有的<br>数据包在处理完成后，可能是需要向客户端返回处理结果，<br>此时就需要逻辑线程将处理完成的数据包放入发送数据包队<br>列中。<br>(3)发送数据包队列及发送线程：发送队列存放待发送的<br>数据包，发送线程根据数据包里的客户端套接字发送给特定<br>客户端。<br>CTestServer 类是一个测试类， 主要用于演示如何在<br>CUserServer 的基础上派生一个真正的应用服务器，并用于说<br>明它需要重载实现CUserServer 的哪些重要虚函数。<br>3.2 资源管理<br>用IOCP 开发服务器时，当I/O 发生错误时需要有效地<br>释放与套接字相关的缓存区，如果对同一缓存区释放多次，<br>就会导致内存释放的错误。当投递的异步I/O 请求返回了非<br>WSA_IO_PENDING 错误时，要对此错误进行处理，通常执<br>行2 步操作：释放此次操作使用的缓冲区数据；关闭当前操<br>作所使用的套接字句柄。同时GQCS 调用会返回FALSE，也<br>要做上面2 步相同的操作，这样就可能产生对同一缓存区进<br>行重复释放的错误。解决的办法可以有2 种：<br>(1)通过引用计数机制控制缓存区释放；<br>(2)使缓存区释放操作线性化。<br>该系统的设计采用了第(2)种解决方案，所谓的释放操作<br>线性化是指把可能引起2 次释放同一缓存区的操作合并为一<br>次释放。如果在执行异步I/O 操作过程中发生了非WSA_ IO_<br>PENDING 错误，可以让GQCS 返回时得知这个错误和发生<br>错误时的缓存区指针，而不对该错误进行处理。通知的方式<br>是，使用PostQueuedCompletionStatus(以下简称Post)函数抛<br>出一个特殊标志的消息，这个特殊标志可以通过GQCS 函数<br>的第2 个参数，即传送字节数来表示，可以选择任何一个不<br>可能出现的值，比如一个负数。当然，如果通过单句柄数据<br>或单I/O 数据来传递也是可以的。而发生错误时的缓冲区指<br>针，必须要通过单句柄数据或单I/O 数据来传递。<br>把释放操作全放在GQCS 函数里以后，对释放操作的处<br>理就比较统一了。当然，为了实现真正的线性化和原子化，<br>在释放操作的执行逻辑上需要对释放代码加锁以实现线程互<br>斥(多线程情况下)。<br>3.3 包的乱序解决方案<br>如果在同一个套接字上一次提交多个异步I/O 请求，肯<br>定会按照它们提交的次序完成，但在多线程环境下，完成包<br>处理次序可能和提交次序不一致。该问题的一个简单的解决<br>方法是一次只投递一个异步I/O 请求，当工作线程处理完该<br>请求的完成数据包后，再投递下一个异步I/O 请求。但这样<br>做会降低服务器的处理性能。为了保证完成包处理次序和提<br>交次序相一致，可以为每个连接上投递的请求都分配一个序<br>号，单句柄数据中记录当前需要读取的单I/O 数据的序号，<br>如果工作线程获得的单I/O 数据的序号与单句柄数据中记录<br>的序号一致的话，就处理该数据。如果不相等，则把这个单<br>I/O 数据保存到该连接的pOutOfOrderReads 列表中。<br>4 性能优化<br>在网络服务器的开发过程中，池(Pool)技术已经被广泛应<br>用。使用池技术在一定层度上可以明显优化服务器应用程序<br>的性能，提高程序执行效率和降低系统资源开销。这里所说<br>的池是一种广义上的池，比如数据库连接池、线程池、内存<br>池、对象池等。其中，对象池可以看成保存对象的容器，在<br>进程初始化时创建一定数量的对象，需要时直接从池中取出<br>一个空闲对象，用完后并不直接释放掉对象，而是再放到对<br>象池中以方便下一次对象请求可以直接复用。其他几种池的<br>设计思想也是如此，池技术的优势是，可以消除对象创建所<br>—105—<br>带来的延迟，从而提高系统的性能。<br>4.1 线程池<br>线程池[4]是提高服务器程序性能的一种很好技术，在<br>Win32 平台下开发的网络服务器程序使用的线程池可分为两<br>类：一类是由完成端口对象负责维护的工作线程池，主要负<br>责网络层相关处理(比如投递异步读或写操作等)；另一类是<br>负责逻辑处理的线程池，它是专门提供给应用层来使用的。<br>本文提出了一种逻辑线程池的设计方案，线程池框架结构主<br>要分为以下几个部分：<br>(1)线程池管理器：用于创建并管理线程，往任务队列添<br>加数据包等，并可以动态增加工作线程。<br>(2)工作线程：线程池中的线程，执行实际的逻辑处理。<br>(3)任务接口：每个任务必须实现的接口，以供工作线程<br>调度任务使用。<br>(4)任务队列：提供一种缓存机制，用于存放从网络层接<br>收的数据包。<br>该通信模块使用了上述线程池的设计方案，从测试结果<br>来看，当并发连接数很大时，线程池对服务器的性能改善是<br>显著的。<br>该设计方案有个很好的特性，就是可以创建工作线程数<br>量固定的线程池，也可以创建动态线程池。如果有大量的客<br>户要求服务器为其服务，但由于线程池的工作线程是有限的<br>话，服务器只能为部分客户端服务，客户端提交的任务只能<br>在任务队列中等待处理。动态改变的工作线程数目的线程池，<br>可以以适应突发性的请求。一旦请求变少了将逐步减少线程<br>池中工作线程的数目。当然线程增加可以采用一种超前方式，<br>即批量增加一批工作线程，而不是来一个请求才建立创建一<br>个线程。批量创建是更加有效的方式，而且该方案还限制了<br>线程池中工作线程数目的上限和下限，确保线程池技术能提<br>高系统整体性能。<br>4.2 对象池<br>对象池[5-6]是针对特定应用程序而设计的内存管理方式，<br>在某种场合下内存的分配和释放性能会大大提升。默认的内<br>存管理函数(new/delete 或malloc/free)有其不足之处，如果应<br>用程序频繁地在堆上分配和释放内存，那么就会导致性能损<br>失，并且会使系统中出现大量的内存碎片，降低内存的利<br>用率。<br>所谓对象池就是应用程序可以通过系统的内存分配调用<br>预先一次性申请适当大小的内存块，然后可以根据特定对象<br>的大小，把该块内存分割成一个个大小相同的对象。如果对<br>象池中没有空闲对象使用时，可以再向系统申请同样大小的<br>内存块。如果对象使用完毕后直接放到对象池中，这种内存<br>管理策略能有效地提升程序性能。<br>4.2.1 对象池的应用<br>当服务器接受一个客户端请求后，会创建成功返回一个<br>客户端套接字句柄。如果出现大量并发客户端连接请求时，<br>就会出现频繁地分配和释放对象的情况，这个过程可能会消<br>耗大量的系统资源，有损系统性能。WinSock2 还提供一个接<br>受扩展函数AcceptEx，它允许在接受连接之前就事先创建一<br>个套接字句柄，使之与接受连接相关联。在调用AcceptEx 时，<br>可以直接把该句柄作为参数传递给AcceptEx。有了这个保证，<br>可以通过采用对象池技术来提升系统性能，可以在接受连接<br>之前就创建一定数量的套接字句柄，随着新连接请求的到来<br>将句柄分配出去，当客户端断开连接后，把相应句柄重新放<br>入套接字对象池中。<br>另外需要用到对象池的地方是，在每一次投递WSASend<br>或WSARecv 操作时，都要传进一个重叠结构体参数。可以<br>提前创建一个重叠结构体对象池，当发起异步I/O 操作时，<br>先从池中取一个结构体对象，用完之后并不直接销毁，而是<br>再放回对象池以便以后重复利用。创建的结构体数量取决于<br>完成端口的处理效率，如果处理效率比较高，则数量可能就<br>少些，反之，就需要多创建些对象。<br>该系统所设计的对象池是线程安全的，可以被多个线程<br>共享，在获得和释放对象时都需要加锁，从而保证线程间互<br>斥访问对象池。<br>4.2.2 对象池的优点<br>与系统直接管理内存相比，对象池在系统性能优化方面<br>主要有如下优点：<br>(1)针对特殊情况，例如需要频繁分配和释放固定大小的<br>对象时，不需要复杂的分配算法和线程同步。也不需要维护<br>内存空闲表的额外开销，从而获得较好的性能。<br>(2)由于直接分配一定数量的连续内存空间作为内存块，<br>因此一定程度上提高了程序局部性能，提升了应用程序整体<br>性能。<br>(3)比较容易控制页边界对齐和内存字节对齐，基本没有<br>内存碎片问题。<br>4.3 环形缓存区<br>基于TCP 协议的服务器应用程序，拼包处理过程必不可<br>少。由于要从接收缓存中分解出一个个逻辑数据包，因此一<br>般都要涉及内存拷贝操作，过多的内存拷贝必然降低系统<br>性能。<br>当然，就逻辑数据包的拼装问题而言，也完全可以避免<br>数据拷贝操作，方法是使用环形缓冲区。本文所说的环形缓<br>冲区是具体这种特征的接收缓冲区，在服务器的接收事件里，<br>当处理完了一次从缓冲区里取走所有完整逻辑包的操作后，<br>可能会在缓冲区里遗留下来新的不完整数据包。使用了环形<br>缓冲区后，就可以不将数据重新复制到缓冲区首部以等待后<br>续数据的拼装，可以根据记录下的队列首部和队列尾部指针<br>进行下一次的拼包操作。<br>环形缓冲区在IOCP 的处理中，甚至在其他需要高效率<br>处理数据收发的网络模型的接收事件处理中，是一种被广泛<br>采用的优化方案。<br>5 实验结果<br>为了证明论文中系统优化的方法能获得预期的性能优<br>势，对内存池和系统整体性能进行了实验测试。测试硬件是：<br>CPU：AMD Turion 64，内存：1 024 MB，网络：100 MB 局<br>域网，操作系统：Windows XP Professional SP2。<br>测试1：对象池性能测试<br>由表1 可以看出，由于使用对象池来分配小对象的内存，<br>速度提高了52.48%，使得内存分配获得了显著的效率提升。<br>速度提高的原因可以归结为以下几点：<br>(1)除了偶尔的内存申请和销毁会导致从进程堆中分配<br>和销毁内存块外，绝大多数的内存申请和销毁都由对象池在<br>已经申请到的内存块中进行，而没有直接与进程打交道，而<br>直接与进程打交道是很耗时的操作。<br>(2)这是在单线程环境的对象池，在多线程环境下，由于<br>加锁，因此速度提高的会少些。<br>（下转第114 页）
<img src ="http://www.cppblog.com/deane/aggbug/76142.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2009-03-10 19:51 <a href="http://www.cppblog.com/deane/articles/76142.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>集群式游戏服务器架构方案设计开发</title><link>http://www.cppblog.com/deane/articles/75602.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 05 Mar 2009 04:39:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/75602.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/75602.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/75602.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/75602.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/75602.html</trackback:ping><description><![CDATA[<br><br><strong>转载自</strong>：<a href="http://www.libing.net.cn/post/Cluster-Game-Server-development.php">http://www.libing.net.cn/post/Cluster-Game-Server-development.php</a><br><br><br>&nbsp;
<p><span></span>&nbsp;</p>
&nbsp;
<p><span>自从</span><span>2003</span><span>年开发</span><span>VOIP Radius Server</span><span>以及修改</span><span>Gnugk</span><span>依赖，从事服务器开发已经近五年了，对服务器开发也有一些自己独到的看法以及见解。当摆脱了技术本身的束缚之后，才理解重要的并不是某种技术的运用，而是整体设计的考虑，也慢慢明白了设计是开发的灵魂的道理。</span></p>
<p><span>从技术层面来看，各个平台都有一些自己特有的东西，比如</span><span>Windows </span><span>平台下面的</span><span>IOCP</span><span>技术，可以说为了支持大的并发，</span><span>IOCP</span><span>是一个</span><span>Windows</span><span>平台的必选方案。而在</span><span>Linux</span><span>下面</span><span>Epoll</span><span>又是所有开发人员需要掌握的技术。当然还有</span><span>FreeBSD</span><span>下面</span><span>Kqueue</span><span>的应用了。一些其他平台也有自己独有的</span><span>AIO</span><span>库。</span></p>
<p><span>随着网络开发的进一步理念加深，跨平台库也吸引了越来越多的使用者的眼光。比如行业里面最出名的莫过于</span><span>ACE</span><span>、</span><span>ASIO(Boost</span><span>公司</span><span>)</span><span>两大支持库。新的版本中都对</span><span>IOCP</span><span>支持，使用的是</span><span>Proactor</span><span>设计模式实现的。</span></p>
<p><span>当我们拥有了以上的知识背景后，我们就可以开始着手设计了。而这仅仅是一个必要条件，而不是重复条件。为什么呢？</span></p>
<p><span>我们先来提一下集群式服务器开发的常用几个技术知识。</span></p>
<p><span>1</span><span>：线程</span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>2</span><span>：线程池</span></p>
<p><span>3</span><span>：内存池</span></p>
<p><span>4</span><span>：数据库连接池</span></p>
<p><span>5</span><span>：为了达到</span><span>1:10000</span><span>的连接，可以采用</span><span>Server-Client</span><span>的连接方式，而为了达到</span><span>1:10000*100</span><span>的连接，我们怎么办呢？一般会采用</span><span>Client-&gt; ConnServer -&gt; LogicServer</span><span>。这是技术背景。</span><span>ConnServer</span><span>在接受完</span><span>Client </span><span>的连接后，将</span><span>Logic Server </span><span>暴露给</span><span>Client,</span><span>并立刻断开连接。以后的数据交互就和</span><span>Conn Server</span><span>没有关系了，这种架构有很多的优势。<br></span></p>
<p align=center><img height=512 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/1.jpg" width=815 border=0></p>
<p align=center><span>[</span><span>图一：标准集群</span><span>GameServer</span><span>架构方案</span><span>]</span></p>
<p><span>首先要说的是线程，在服务器开发中，线程是一个非常重要的概念，尤其是现在多核服务器的发展。当然，提到了线程自然应该说到线程之间的互斥。这也是服务器开发者们在开发最初最容易出现的问题。体现在一个资源或者多个资源在多个线程中共享使用如何避免出现脏数据的问题。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>线程池，池，顾名思义，是一个存储容器，一个浅显的比方，我们把水事先存放在水池里面，当我们需要的时候，就去里面取，用完了就还给池（其实这里并不是非常合适的例子，毕竟我们用完了水是丢掉）。这是一个由多个线程组成的一个队列，当有事情发生时候，我们把当前的空闲的线程丢给他，为他服务。当下一个事件发生的时候，我们又从池里面取一个空闲的线程丢给他，为他服务。当服务完毕，把线程丢回池中。起到反复利用的目的。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>内存池，同样也是一个池。这个概念的产生是为了避免服务器频繁的分配内存，而采取预先分配一定数目的对象，并将对象们放到队列中，当需要的时候，从该队列中取出，当用完，就返回池中。比如我们的</span><span>Server</span><span>可能会存在</span><span>10000</span><span>个连接，我们预先开辟</span><span>10000</span><span>个</span><span>Client</span><span>对象，存储在</span><span>list&lt;Client *&gt; pFreeClientsList</span><span>中，当需要的时候，从队列中</span><span>pop</span><span>一个出来，当使用完毕就丢回</span><span>pFreeClientsList</span><span>。这种机制很好的起到了避免频繁开辟内存对象的目的，可以很好的提高系统的性能。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>数据库连接池，同上面一致的道理，在服务器中，数据库访问也是一个很大的瓶颈，所以同样采取上面的道理，使用连接池的概念。当然在数据库连接方面也有一个特殊的问题存在。就是数据库的连接不宜过多，所以传统的来一个处理，就开一个连接是不合理的，必须采用控制适当的连接次数。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>当然另外一些需要提到的是内存数据库。硬盘的访问速度和内存的访问速度不是一个数量级的，而且随着内存的硬件价格越来越低，内存数据库的可行性也越来越高，尤其是实时性要求高的系统，完全可以采用内存数据库和物理数据库想结合的方法来处理。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>当系统的连接数量从万上百万级别的时候，服务器程序就超越了服务器本身，我们需要考虑的问题将从一下几个方面开展：</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>1</span><span>：如何划分系统中功能？</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>2</span><span>：如何保证整个系统的性能可控，直观的说就是系统每一步时候瓶颈在哪里？</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>3</span><span>：如何保证当系统的瓶颈凸显时候，简单的添加一组服务器，就可以达到分压目的？</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>4</span><span>：系统的灾难部分出现的时候，如何保证系统依然可以完整运行？</span></p>
<p><span>第一个问题是如何划分系统中的功能。在软件开发中，我们追求的是每个函数功能尽量简单，易学里面的道理叫做大道至简。软件开发中同样适用，在服务器开发中，同样适用。如何将整个系统中的需求抽象为功能，并如何更好的划分功能，将极大减少系统开发的难度，并能够使得系统的可扩展性非常强。</span></p>
<p><span>第二个问题是瓶颈问题。从物理上面来分析，性能在硬盘，内存，</span><span>CPU</span><span>是三个决定因素的地方。而从软件的角度就包含了数据库系统，操作系统，服务器软件系统三个方面，更细节方面拿游戏服务器来说，</span><span>Conn Server </span><span>的压力，</span><span>Logic Server</span><span>的压力，还是</span><span>DB Server</span><span>的压力了。</span></p>
<p><span>第三个问题还体现在分组方面。比如当</span><span>Conn Server</span><span>出现压力的时候，如何简单的添加一个</span><span>Conn Server</span><span>就达到分压目的。当</span><span>Logic Server</span><span>出现压力，或者</span><span>DB Server</span><span>出现压力。另外就是如果服务器设计以组的方式出现，应该如何管理组以达到分压目的。</span></p>
<p><span>第四个问题是灾难恢复。在重要的系统中，由于涉及到的系统、硬件、软件非常多，很容易某个系统出现故障，这个时候，系统应该具有很好的伸缩性，故障出现后，系统必须依然运行顺利。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>所以在设计服务器时候，应该考虑上面的因素。下面我提出在集群服务器开发中的两种可行的方案。<br></span></p>
<p align=center><img height=522 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/2.jpg" width=943 border=0><br><br></p>
<p align=center><span>[</span><span>图二：基于功能划分的集群</span><span>GameServer</span><span>架构</span><span>]</span></p>
<p align=center>&nbsp;<br></p>
<p align=center><img height=768 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/3.jpg" width=822 border=0></p>
<p align=center><span>[</span><span>图三：组划分的集群服务器架构</span><span>]</span></p>
<p align=center>&nbsp;</p>
<p><span>在图二中，系统按照功能方式划分系统，当压力增加的时候，按照功能方式添加某服务器，可以简单的达到分压的目的。在</span><span>Conn Server</span><span>中保存所有有效</span><span>Hall Server</span><span>的连接，以及当前该</span><span>Hall Server</span><span>的当前连接数。代码示意如下：</span></p>
<p><span>class THallServer&nbsp;</span></p>
<p><span>{</span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>THallServer();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual ~THallServer();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>THallServer(int port);</span></p>
<p>&nbsp;</p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SOCKET _hallServer; //</span><span>保持同</span><span>HallServer</span><span>连接的</span><span>Socket</span><span>对象</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int _maxConn; //</span><span>该</span><span>HallServer</span><span>的最大连接数量</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int _currentConn; //</span><span>当前连接数量</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int GetCurrentConn();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>char _hallServerAddr[32];</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int _hallServerPort;</span></p>
<p>&nbsp;</p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class THallServerList&nbsp;</span></p>
<p><span>{</span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>THallServerList();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual ~THallServerList();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>list&lt;THallServer *&gt; pHallServerList;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SOCKET _listenHallServer;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>HANDLE ListenThread;</span></p>
<p>&nbsp;</p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void Start();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>THallServerList(int port);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//Accept</span><span>线程</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>static unsigned __stdcall ListenThreadFunc(LPVOID lpVoid);</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>上面的代码是该设计方案的类代码。从代码中我们可以理解出思想如下：</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Conn Server</span><span>里面存在一个</span><span>THallServerList</span><span>对象，该对象监听端口，当有</span><span>HallServer</span><span>连接过来，将该</span><span>HallServer</span><span>存入队列，并实时获取该</span><span>Server</span><span>当前的压力情况，可以起到一个负载均衡的作用。而保持的</span><span>HallServer</span><span>队列，当客户端连接过来，</span><span>Conn Server</span><span>则从</span><span>pHallServerList</span><span>中将当前</span><span>currentConn</span><span>最小的服务器发送给客户端，以后客户端将同该</span><span>Hall Server</span><span>发起连接。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>在该系统中，当我们的</span><span>Conn Server</span><span>不够的时候，可以考虑架设多台</span><span>Conn Server</span><span>，当客户端无法连接时候，程序自动连接下一台</span><span>Conn Server.</span><span>比如</span><span>conn1.doserver.net</span><span>、</span><span>conn2.doserver.net</span><span>、</span><span>conn3.doserver.net</span><span>、</span><span>connn.doserver.net</span><span>。</span></p>
<p>&nbsp;</p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>图三中是按照组划分的系统组成。该方案目前来说，我还并没有实施过，只是在方案上面进行过探讨。希望有时间我可以设计一个案例出来再做展示。</span></p>
<p align=center><img height=555 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/4.jpg" width=1049 border=0><br><br></p>
<p align=center><span>[</span><span>图四：改进的功能划分集群</span><span>GameServer</span><span>架构二</span><span>]</span></p>
<p><span>在项目的实施过程中，我发现了</span><span>Hall Server</span><span>其实并不需要同</span><span>Logic Server</span><span>进行交互，如果</span><span>Hall Server</span><span>在保留同</span><span>Client</span><span>的</span><span>1W</span><span>多连接的情况下依然保持过多的同</span><span>Logic Server</span><span>的连接，势必压力非常大，这时候如果在之间使用</span><span>ISServer</span><span>来交互，就可以减少很多的连接数量，也使得系统更加清晰。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Hall Server</span><span>只需要获取所有的</span><span>Logic Server</span><span>的名称，</span><span>Logic Server</span><span>的地址，</span><span>Logic Server</span><span>的端口，以及当前的连接数量。所以通过之间的一个信息服务器作为桥梁，就可以很好的解决这个问题。这种架构就可以达到非常完美的解决上面提到的</span><span>4</span><span>个难点的问题了。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>后记：封闭开发之余，很想把自己的在服务器开发的经验分享一下，所以就借用了</span><span>2</span><span>个小时整理此小文，希望大家喜欢。同时欢迎大家指点，建议。也欢迎转载，但是无比保留版权以及原作者信息。非常感谢。</span></p>
<p>&nbsp;</p>
<p align=center><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>胡章优</span><span> <st1:chsdate w:st="on" Year="2008" Month="12" Day="3" IsLunarDate="False" IsROCDate="False"><span>2008-12-3</span></st1:chsdate><span> </span></span><span>于北京</span></p>
<p>&nbsp;</p>
<p><span>作者：胡章优，吉林大学机械学院教师。长春优狐科技开发有限公司董事长兼总经理。</span></p>
<p><span>Tel: 13596199043</span></p>
<p><span>Mail: <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#117;&#122;&#104;&#97;&#110;&#103;&#121;&#111;&#117;&#50;&#48;&#48;&#50;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">huzhangyou2002@gmail.com</a> (<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#117;&#122;&#104;&#97;&#110;&#103;&#121;&#111;&#117;&#64;&#106;&#108;&#117;&#46;&#101;&#100;&#117;&#46;&#99;&#110;">huzhangyou@jlu.edu.cn</a>)</span></p>
<p><span>Site: <a href="http://doserver.net/">http://doserver.net</a></span></p>
<p><span><a href="msn:huzhangyou2002@gmail.com">MSN:huzhangyou2002@gmail.com</a></span></p>
<p><span>QQ: 3803308</span></p>
<br>
<img src ="http://www.cppblog.com/deane/aggbug/75602.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2009-03-05 12:39 <a href="http://www.cppblog.com/deane/articles/75602.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>总体设计和登陆服务器 [游戏服务器的设计思路 转] </title><link>http://www.cppblog.com/deane/articles/75451.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Tue, 03 Mar 2009 12:10:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/75451.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/75451.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/75451.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/75451.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/75451.html</trackback:ping><description><![CDATA[<div class=textbox-title>
<h4><a href="http://www.libing.net.cn/read.php/873.htm"><u><font color=#0000ff>总体设计和登陆服务器 [游戏服务器的设计思路 转]</font></u></a> </h4>
<div class=textbox-fontsize><img title=字体大小 alt="" src="http://www.libing.net.cn/template/4color/images/toolbar_fontsize.gif" border=0> <a href="javascript: doZoom(16);"><u><font color=#0000ff>大</font></u></a> | <a href="javascript: doZoom(14);"><u><font color=#0000ff>中</font></u></a> | <a href="javascript: doZoom(12);"><u><font color=#0000ff>小</font></u></a> <a href="http://www.libing.net.cn/feed.php?go=entry_873"><img title=订阅本文 alt="" src="http://www.libing.net.cn/template/4color/images/toolbar_rss.gif" border=0></a> <a href="http://www.libing.net.cn/read.php?save_873"><img title=保存本文为文本文档 alt="" src="http://www.libing.net.cn/template/4color/images/toolbar_save.gif" border=0></a> </div>
<div class=textbox-label><img title=发表日期 hspace=0 src="http://www.libing.net.cn/template/4color/images/date.gif" align=absMiddle> 2007/01/29 &nbsp;&nbsp;<img title=发表时间 hspace=0 src="http://www.libing.net.cn/template/4color/images/time.gif" align=absMiddle> 01:41 &nbsp;&nbsp;<a title=点击阅读 href="http://www.libing.net.cn/read.php/873.htm"><img title=阅读数 hspace=0 src="http://www.libing.net.cn/template/4color/images/read.gif" align=absMiddle border=0><u><font color=#0000ff> 6306</font></u></a> &nbsp;&nbsp;<img title=发表作者 hspace=0 src="http://www.libing.net.cn/template/4color/images/writer.gif" align=absMiddle> <a href="http://www.libing.net.cn/go.php/user/1/" target=_blank><u><font color=#0000ff>huzhangyou2002</font></u></a> <span id=starid873><img title=未加星标 alt="" src="http://www.libing.net.cn/template/4color/images/others/unstarred.gif" border=0></span> <a title="查看分类： 服务器开发" href="http://www.libing.net.cn/go.php/category/3/"><u><font color=#0000ff>服务器开发</font></u></a> <img title=不指定 alt=不指定 src="http://www.libing.net.cn/images/weather/blank.gif"> </div>
</div>
<div class=textbox-content id=zoomtext>作者博客：<br>http://blog.csdn.net/yahle<br>大纲：<br>项目的历史背景<br><a class=mykeyword title=http://www.server-development.cn/go.php/tags/服务器/ href="http://www.server-development.cn/go.php/tags/服务器/" target=_blank><u><font color=#0000ff>服务器</font></u></a>的设计思路<br>服务器的技术<br>服务器的设计<br>服务器的改进<br>图形引擎myhoho及UI库的设计<br><br>客户端与服务器的集成<br><a name=entrymore></a><br><br>网络游戏一般采用C\S模式，网络游戏的设计重点，我认为在于Server端,也就是我们说的服务器。在服务器端的设计，我把服务器按照功能分为2个部分，一个负责游戏世界的处理，一个服务器服务器与客户端的通讯。在负责游戏世界的处理的服务器，我又按照功能分为地图服务器和逻辑服务器。这样划分的依据是他们处理的内容不同进行。当初的设计还考虑到系统的集群功能，可以把游戏的地图移动处理和游戏的逻辑处理都分别分摊到其它服务器里面去。但是做到最后，发现这样的设计也不是太好，主要是因为在处理一些游戏事件的时候需要两个服务器之间进行协同，这样势必要创建一定的网络游戏消息，在开始制作游戏的时候，因为需要系统的东西不是很多，所以没有太注意，到项目的后期，想增加一个功能的时候，就发现在处理船只沉没的时候，服务器需要传递很多同步数据，而且服务器各自在设置玩家数据的时候，也有很多重复的地方。如果今后还要再加点什么其它功能，那要同步的地方就实在是太多了，所以按照功能把服务器分为2个部分的设计还是存在缺陷的，如果让我重新再来，我会选择单服务器的设计，当然这个服务器还是要和连接服务器进行分离，因为游戏的逻辑处理和与玩家的通讯还是很好分开的，而且分开的话，也有利于逻辑服务器的设计。<br><br><br><br><br><br><br><br>登陆（连接）服务器的设计：<br><br><br><br>&nbsp; &nbsp;在网络游戏里，其中一个很大的难点就是玩家与服务器的通讯，在Windos的服务器架构下，网络游戏服务器端采用的I/O模型，通常是完成端口。在项目开始时研究完成端口，感觉很难，根本看不懂，因为它在很多地方与以前写网络通讯软件时用的方法不同。但是当我分析过3个完成端口的程序后，基本了解的它的使用方法。而且在懂以后，回过头来看，其它完成端口的概念也不是很复杂，只要能清楚的了解几个函数的使用方法以及基本的处理框架流程，你就会发现它其实非常的简单。<br><br><br><br>&nbsp; &nbsp;完成端口的一些需要理解的地方：<br><br><br><br>1。消息队列<br><br><br><br>2。工作线程<br><br><br><br>3。网络消息返回结构体<br><br><br><br><br><br><br><br>&nbsp; &nbsp;一般我们在设计服务器端的时候，最关键的地方是如何分辩刚刚收到的网络数据是由那个玩家发送过来的，如果是采用消息事件驱动的话，是可以得到一个socket的值，然后再用这个值与系统里存在的socket进行比对，这样就可以得到是那位玩家发送过来的游戏消息。我在还没有使用完成端口的时候，就是使用这个方法。这样的设计有一个缺点就是每次收到数据的时候回浪费很多时间在于确定消息发送者身份上。但是在完成端口的设计里，我们可以采用一个取巧的方法进行设计。所以，这个问题很轻易的就结局了，而且系统开销也不是很大，关于完成端口，可以参考一下的文章：<br><br><br><br>《关于Winsock异步I/O模型中的事件模型》<br><br><br><br>http://search.csdn.net/Expert/topic/166/166227.xml?temp=.4639093<br><br><br><br>《手把手教你玩转SOCKET模型之重叠I/O篇》<br><br><br><br>http://blog.csdn.net/piggyxp/archive/2004/09/23/114883.aspx<br><br><br><br>《学习日记]<a class=mykeyword title=http://www.server-development.cn/go.php/tags/IOCP/ href="http://www.server-development.cn/go.php/tags/IOCP/" target=_blank><u><font color=#0000ff>IOCP</font></u></a>的学习－－初步理解》<br><br><br><br>http://www.gameres.com/bbs/showthread.asp?threadid=25898<br><br><br><br>《用完成端口开发大响应规模的Winsock应用程序》<br><br><br><br>http://www.xiaozhou.net/ReadNews.asp?NewsID=901<br><br><br><br>《理解I/O Completion Port》<br><br><br><br>http://dev.gameres.com/Program/Control/IOCP.htm<br><br><br><br>几个关键函数的说明：<br><br><br><br>http://msdn.microsoft.com/library/en-us/fileio/fs/postqueuedcompletionstatus.asp?frame=true<br><br><br><br>http://msdn.microsoft.com/library/en-us/fileio/fs/createiocompletionport.asp?frame=true<br><br><br><br>http://msdn.microsoft.com/library/en-us/fileio/fs/getqueuedcompletionstatus.asp?frame=true<br><br><br><br>http://msdn.microsoft.com/library/en-us/winsock/winsock/wsarecv_2.asp?frame=true<br><br><br><br><br><br><br><br>如果你能认真的搞清楚上面的东西，我估计你离理解完成端口就只有一步了。剩下的这一步就是自己编码实现一个下了。有些时候，看得懂了不一定会实际应用，不实实在在的写一点程序，验证一下你的想法，是不会真正搞清楚原理的。<br><br><br><br><br><br><br><br>不过除非你想深入的研究网络技术，否则只要知道怎么用就可以了，剩下的就是寻找一个合适的别人封装好的类来使用。这样可以节省你很多的事件，当然拿来的东西最好有源代码，这样如果发生什么问题，你也好确定是在那个地方出错，要改或者扩充功能都会方便很多。当然，还要注意人家的版权，最好在引用别人代码的地方加一些小小的注解，这样用不了多少时间，而且对你，对原作者都有好处^_^。<br><br><br><br><br><br><br><br>不过在完成端口上我还是没有成为拿来主义者，还是自己封装了完成端口的操作，原因找到的源代码代码封装的接口函数我怎么看怎么觉得别扭，所以最后还是自己封装了一个完成端口，有兴趣的可以去看我的源代码，里面有很详细的注解。而且就我看来，要拿我封装的完成端口类使用起来还是很简单的。使用的时候，只要继承我的CIOCP，然后，根据需要覆盖3个虚函数（OnAccept，OnRead，OnClose）就可以了，最多是在连接函数里，需要用一个函数去设置一下完成端口信息。当然，我封装的类稍微简单了一些，如果要拿来响应大规模连接，还是存在很多的问题，但是如果只是针对少量连接，还是可以应付的。<br><br><br><br><br><br><br><br>对于客户端的I/O模型，我就没有那么用心的去寻找什么好的解决方案，采用了一个最简单的，最原始的阻塞线程的方法做。原理很简单：创建一个sockt，把socket设置为阻塞，连接服务器成功后，启动一个线程，在线程里面用recv()等待服务器发过来的消息。在我的代码里，也是把阻塞线程的方法封装成一个类，在使用的时候，先继承TClientSocket，然后覆盖（重载）里面的OnRead()函数，并在里面写入一些处理收到数据后的操作代码。在用的时候，只要connect成功，系统就会自动启动一个接收线程，一旦有数据就触发刚才覆盖的OnRead函数。这个类我也不是完全直接写的，在里面使用了别人的一些代码，主要是让每个类都能把线程封装起来，这样在创建不同的类的实体的时候，每个类的实体自己都会有一个单独的数据接收线程。<br><br><br><br>当然除了阻塞线程的方法，比较常用的还有就是用消息事件的方法收取数据了。我刚开始的时候，也是采用这个方法（以前用过^_^），但是后来发现不太好封装，最后采用阻塞线程的方法，这样做还有一个好处可以让我的代码看起来更加舒服一些。不过就我分析《航海世纪》客户端采用的是消息事件的I/O模型。其它的网络游戏就不太清楚了，我想也应该是采用消息事件方式的吧。。<br><br><br><br>&nbsp; &nbsp;我记得在gameres上看到过某人写的一篇关于完成端口的笔记，他在篇末结束的时候，提出一个思考题：我们在学习完成端口的时候，都知道它是用于server端的操作，而且很多文章也是这样写的，但是不知道有没有考虑过，用完成端口做客户端来使用？<br><br><br><br>&nbsp; &nbsp;其实这个问题很好回答，答案是OK。拿IOCP做客户端也是可行的，就以封装的IOCP为例，只要在继承原来的CIOCP类的基础上，再写一个Connect（char * ip, int port）的函数，就可以实现客户端的要求了。<br>
<div class=dp-highlighter>
<div class=bar>
<div class=tools><a onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>view plain</font></u></a><a onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>copy to clipboard</font></u></a><a onclick="dp.sh.Toolbar.Command('PrintSource',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>print</font></u></a><a onclick="dp.sh.Toolbar.Command('About',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>?</font></u></a></div>
</div>
<ol class=dp-cpp>
    <li class=alt><span><span class=datatypes>bool</span><span>&nbsp;CIOCPClient::Connect(</span><span class=datatypes>char</span><span>&nbsp;*ip,&nbsp;</span><span class=datatypes>int</span><span>&nbsp;port) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>{ &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;连接服务器 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(!bInit) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(!Init()) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;</span><span class=keyword>false</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;初始化连接socket </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;m_socket&nbsp;=&nbsp;socket(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP); &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(m_socket&nbsp;==&nbsp;SOCKET_ERROR) &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;</span><span class=keyword>false</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;填写服务器地址信息 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;sockaddr_in&nbsp;ClientAddr; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;ClientAddr.sin_family&nbsp;=&nbsp;AF_INET; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;ClientAddr.sin_port&nbsp;=&nbsp;htons(port);&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;ClientAddr.sin_addr.s_addr&nbsp;=&nbsp;inet_addr(ip); &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;绑定监听端口 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;bind(m_socket,&nbsp;(SOCKADDR&nbsp;*)&amp;ClientAddr,&nbsp;</span><span class=keyword>sizeof</span><span>(ClientAddr)); &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(connect(m_socket,&nbsp;(SOCKADDR&nbsp;*)&amp;ClientAddr,&nbsp;</span><span class=keyword>sizeof</span><span>(ClientAddr))&nbsp;==&nbsp;SOCKET_ERROR) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;</span><span class=keyword>false</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>this</span><span>-&gt;m_workThread&nbsp;=&nbsp;</span><span class=keyword>true</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;g_hwThread&nbsp;=&nbsp;CreateThread(NULL,&nbsp;0,&nbsp;WorkThread,&nbsp;(</span><span class=datatypes>LPVOID</span><span>)</span><span class=keyword>this</span><span>,&nbsp;0,&nbsp;&amp;m_wthreadID);&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;创建工作线程，用来处理完成端口消息的 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>this</span><span>-&gt;SetIoCompletionPort(m_socket,&nbsp;NULL);&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;设置完成端口监听的socket </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;</span><span class=keyword>true</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>} &nbsp;&nbsp;</span></li>
</ol>
</div>
<textarea class=cpp style="DISPLAY: none" name=code rows=15 cols=100>bool CIOCPClient::Connect(char *ip, int port)
{
&nbsp; &nbsp; &nbsp; &nbsp;// &nbsp;连接服务器
&nbsp; &nbsp;if (!bInit)
&nbsp; &nbsp; &nbsp; &nbsp;if (!Init())
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return false;
&nbsp; &nbsp;// &nbsp;初始化连接socket
&nbsp; &nbsp;SOCKET m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
&nbsp; &nbsp;if (m_socket == SOCKET_ERROR)
&nbsp; &nbsp; &nbsp; &nbsp;return false;
&nbsp; &nbsp;// 填写服务器地址信息
&nbsp; &nbsp;sockaddr_in ClientAddr;
&nbsp; &nbsp;ClientAddr.sin_family = AF_INET;
&nbsp; &nbsp;ClientAddr.sin_port = htons(port); &nbsp; &nbsp;
&nbsp; &nbsp;ClientAddr.sin_addr.s_addr = inet_addr(ip);
&nbsp; &nbsp;// 绑定监听端口
&nbsp; &nbsp;bind(m_socket, (SOCKADDR *)&amp;ClientAddr, sizeof(ClientAddr));
&nbsp; &nbsp;if (connect(m_socket, (SOCKADDR *)&amp;ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
&nbsp; &nbsp; &nbsp; &nbsp;return false;
&nbsp; &nbsp;this-&gt;m_workThread = true;
&nbsp; &nbsp;g_hwThread = CreateThread(NULL, 0, WorkThread, (LPVOID)this, 0, &amp;m_wthreadID); &nbsp;// &nbsp;创建工作线程，用来处理完成端口消息的
&nbsp; &nbsp;this-&gt;SetIoCompletionPort(m_socket, NULL); &nbsp;// &nbsp;设置完成端口监听的socket
&nbsp; &nbsp;return true;
}
</textarea><br>前面一段是用来连接服务器，所有的客户端程序都是要这样做的，当连接成功后，m_socket就是我们想要的用于与服务器端通讯的socket，然后，我们启动工作线程，并使用SetIoCompletionPort来设置完成端口监听的socket。只要在原来的基础上增加一个函数，就可以把用于服务器的ICOP变成用于客户端的IOCP。<br><br><br><br>&nbsp; &nbsp;在收到网络数据以后，下一步就是根据需要，把收到的网络数据包转变为游戏消息数据包。在转换之前，首先是要从收到的网络数据里面提取出有效的消息。这里为什么说是要提取有效部分？其主要原因是，我们创建的游戏消息数据，在进行网络传输的时候，不是以消息的长度来传的，而是根据系统在接收到发送数据请求的时候，根据实际情况来发送的。例如我这里有一条很长的游戏消息，有3k，但是系统一次只能发送1k的数据，所以，我们的游戏消息，只能把我们的游戏消息分为3个包，分3次发送，这样在我们接收消息的时候，就会触发3次OnRead，而这3次OnRead收到的数据都不是一次完整的游戏消息。所以，我们在收到网络数据后，要先和上一次收到的网络数据进行合并，然后再在里面提取出有效的游戏消息，并在提取后，把已经提取的部分删除。我在这里把这一步操作封装到一个类里CBuftoMsg。这里顺便说明一下：一条游戏消息的网络数据包是以0x00EEEE(16进制)为结束标记（《航海世纪》的做法）。<br>
<div class=dp-highlighter>
<div class=bar>
<div class=tools><a onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>view plain</font></u></a><a onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>copy to clipboard</font></u></a><a onclick="dp.sh.Toolbar.Command('PrintSource',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>print</font></u></a><a onclick="dp.sh.Toolbar.Command('About',this);return false;" href="http://www.libing.net.cn/#"><u><font color=#0000ff>?</font></u></a></div>
</div>
<ol class=dp-cpp>
    <li class=alt><span><span class=keyword>struct</span><span>&nbsp;TMessage &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*&nbsp;p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;消息头所在的位置 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>long</span><span>&nbsp;len;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;整个消息的长度 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>}; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span></span><span class=keyword>class</span><span>&nbsp;CBuftoMsg &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span></span><span class=keyword>protected</span><span>: &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>char</span><span>&nbsp;msgbuf[BUF_LEN];&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*&nbsp;buf_end; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*&nbsp;buf_begin; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>int</span><span>&nbsp;buf_len; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span></span><span class=keyword>public</span><span>: &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;CBuftoMsg(</span><span class=keyword>void</span><span>); &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;TMessage&nbsp;getMessage(</span><span class=keyword>void</span><span>); &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>void</span><span>&nbsp;cleanup_buf(</span><span class=keyword>void</span><span>); &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>bool</span><span>&nbsp;AddMsgBuf(</span><span class=keyword>const</span><span>&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*,&nbsp;</span><span class=datatypes>int</span><span>); &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>int</span><span>&nbsp;tag; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>}; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>CBuftoMsg::CBuftoMsg(</span><span class=keyword>void</span><span>) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;buf_begin&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;buf_end&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;buf_len&nbsp;=&nbsp;0; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>TMessage&nbsp;CBuftoMsg::getMessage() &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*&nbsp;p&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;buf_begin; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;TMessage&nbsp;result; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;result.len&nbsp;&nbsp;=&nbsp;0; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;result.p&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;NULL; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>while</span><span>(p&nbsp;&lt;=&nbsp;buf_begin&nbsp;+&nbsp;buf_len&nbsp;-&nbsp;2) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(&nbsp;*p&nbsp;==&nbsp;0x00) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>const</span><span>&nbsp;</span><span class=keyword>static</span><span>&nbsp;</span><span class=datatypes>char</span><span>&nbsp;ce&nbsp;=&nbsp;0xEE; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(*(p&nbsp;+&nbsp;1)&nbsp;==&nbsp;ce) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>(*(p&nbsp;+&nbsp;2)&nbsp;==&nbsp;ce) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;每条消息都是以&nbsp;00&nbsp;EE&nbsp;EE&nbsp;为结束标志 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result.p&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;buf_begin; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result.len&nbsp;&nbsp;=&nbsp;p&nbsp;-&nbsp;buf_begin&nbsp;+&nbsp;3; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_begin&nbsp;&nbsp;&nbsp;=&nbsp;&nbsp;p&nbsp;+&nbsp;3; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_end&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;buf_begin&nbsp;+&nbsp;buf_len; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_len&nbsp;-=&nbsp;result.len; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>break</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p++; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;result; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span></span><span class=keyword>void</span><span>&nbsp;CBuftoMsg::cleanup_buf() &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(buf_len&nbsp;&lt;&nbsp;BUF_LEN) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(buf_len&nbsp;==&nbsp;0) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_begin&nbsp;&nbsp;&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_end&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>else</span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memmove(msgbuf,&nbsp;buf_end&nbsp;-&nbsp;buf_len,&nbsp;buf_len); &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_begin&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_end&nbsp;=&nbsp;buf_end&nbsp;-&nbsp;buf_len; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>else</span><span>&nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;加入缓冲区的数据过多，要抛弃原来的内容 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_begin&nbsp;&nbsp;&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_end&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;msgbuf; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_len&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;0; &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>} &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span></span><span class=datatypes>bool</span><span>&nbsp;CBuftoMsg::AddMsgBuf(</span><span class=keyword>const</span><span>&nbsp;</span><span class=datatypes>char</span><span>&nbsp;*&nbsp;buf,&nbsp;</span><span class=datatypes>int</span><span>&nbsp;len) &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>{ &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(len&nbsp;&lt;&nbsp;1) &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;</span><span class=keyword>false</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=datatypes>bool</span><span>&nbsp;result&nbsp;=&nbsp;</span><span class=keyword>true</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;buf_len&nbsp;+=&nbsp;len; &nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>if</span><span>&nbsp;(buf_len&nbsp;&gt;=&nbsp;BUF_LEN)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=comment>//&nbsp;&nbsp;如果缓冲区装满了则直接把原来的缓冲区清空再重新复制数据 </span><span>&nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>this</span><span>-&gt;cleanup_buf();&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;</span></span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;</span><span class=keyword>false</span><span>; &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;memcpy(buf_begin,&nbsp;buf,&nbsp;len); &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class=keyword>return</span><span>&nbsp;result; &nbsp;&nbsp;</span></span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
    <li class=""><span>} &nbsp;&nbsp;</span></li>
    <li class=alt><span>&nbsp;&nbsp;</span></li>
</ol>
</div>
<textarea class=cpp style="DISPLAY: none" name=code rows=15 cols=100>struct TMessage
{
&nbsp; &nbsp;char * p; &nbsp; &nbsp; &nbsp; // &nbsp;消息头所在的位置
&nbsp; &nbsp;long len; &nbsp; &nbsp; &nbsp; // &nbsp;整个消息的长度
};
class CBuftoMsg
{
protected:
&nbsp; &nbsp;char msgbuf[BUF_LEN]; &nbsp;
&nbsp; &nbsp;char * buf_end;
&nbsp; &nbsp;char * buf_begin;
&nbsp; &nbsp;int buf_len;
public:
&nbsp; &nbsp;CBuftoMsg(void);
&nbsp; &nbsp;TMessage getMessage(void);
&nbsp; &nbsp;void cleanup_buf(void);
&nbsp; &nbsp;bool AddMsgBuf(const char *, int);
&nbsp; &nbsp;int tag;
};
CBuftoMsg::CBuftoMsg(void)
{
&nbsp; &nbsp;buf_begin = msgbuf;
&nbsp; &nbsp;buf_end = msgbuf;
&nbsp; &nbsp;buf_len = 0;
}
TMessage CBuftoMsg::getMessage()
{
&nbsp; &nbsp;char * p &nbsp; &nbsp;= buf_begin;
&nbsp; &nbsp;TMessage result;
&nbsp; &nbsp;result.len &nbsp;= 0;
&nbsp; &nbsp;result.p &nbsp; &nbsp;= NULL;
&nbsp; &nbsp;while(p &lt;= buf_begin + buf_len - 2)
&nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;if ( *p == 0x00)
&nbsp; &nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;const static char ce = 0xEE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (*(p + 1) == ce)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if(*(p + 2) == ce)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// &nbsp;每条消息都是以 00 EE EE 为结束标志
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;result.p &nbsp; &nbsp;= buf_begin;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;result.len &nbsp;= p - buf_begin + 3;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_begin &nbsp; = &nbsp;p + 3;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_end &nbsp; &nbsp; = buf_begin + buf_len;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_len -= result.len;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp;p++;
&nbsp; &nbsp;}
&nbsp; &nbsp;return result;
}
void CBuftoMsg::cleanup_buf()
{
&nbsp; &nbsp;if (buf_len &lt; BUF_LEN)
&nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;if (buf_len == 0)
&nbsp; &nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_begin &nbsp; = msgbuf;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_end &nbsp; &nbsp; = msgbuf;
&nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;memmove(msgbuf, buf_end - buf_len, buf_len);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_begin = msgbuf;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;buf_end = buf_end - buf_len;
&nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp;}
&nbsp; &nbsp;else
&nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;// &nbsp;加入缓冲区的数据过多，要抛弃原来的内容
&nbsp; &nbsp; &nbsp; &nbsp;buf_begin &nbsp; = msgbuf;
&nbsp; &nbsp; &nbsp; &nbsp;buf_end &nbsp; &nbsp; = msgbuf;
&nbsp; &nbsp; &nbsp; &nbsp;buf_len &nbsp; &nbsp; = 0;
&nbsp; &nbsp;}
}
bool CBuftoMsg::AddMsgBuf(const char * buf, int len)
{
&nbsp; &nbsp;if (len &lt; 1)
&nbsp; &nbsp; &nbsp; &nbsp;return false;
&nbsp; &nbsp;bool result = true;
&nbsp; &nbsp;buf_len += len;
&nbsp; &nbsp;if (buf_len &gt;= BUF_LEN) &nbsp; &nbsp; // &nbsp;如果缓冲区装满了则直接把原来的缓冲区清空再重新复制数据
&nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;this-&gt;cleanup_buf(); &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp;result = false;
&nbsp; &nbsp;}
&nbsp; &nbsp;memcpy(buf_begin, buf, len);
&nbsp; &nbsp;return result;
}
</textarea><br><br>我在这里把 CBuftoMsg 的代码贴出来，主要是因为，我在写本文的时候，发现一个惊天动地的bug，有兴趣的读者可以自己去找一下。不过一开始写代码的时候，还不是这样的，当初的代码bug比这个还要多，问题还要严重，严重到经常让服务器程序莫名其妙的崩溃，而且这个问题，一直到5月份，系统在进行集成测试的时候才发现并解决（还没有彻底解决，至少目前我还发现了bug，），以前一直都没有怎么注意到这个问题，而且我们还把因为这个bug造成的问题，归结到线程的互斥上去^_^!<br><br><br><br><br><br><br><br>我的登陆服务器，除了基本的处理网络数据包以外，还负责玩家系统的登陆验证，这部分东西不是很复杂，在我的程序里，只是简单的从ini文件里读取玩家的信息而已，有兴趣的自己去看我的代码（不过这部分远还没有真正的完善，存在很多问题）。<br><br><br><br><br><br><br><br>除了登陆验证以外，在登陆程序还负责进行消息转发，就是把客户端的消息分别发送到不同的服务器。如果当初设计的是一个逻辑服务器，这个功能就可以简单很多，只要发送到一个服务器里就可以了。现在的要发到2个服务器，所以还需要对收到的游戏消息进行分类。为了方便，我对原来定义消息的ID进行了分类，所以，在GameMessageID.h文件里定义的游戏消息对应的ID编号不是顺序编排的。不过也因为这样，在现在看来，这样的设计，有一些不太好。在整个系统里，存在有4个主体，他们之间互相发送，就用了12组的数据，为了方便计算，我把一个变量的范围分为16个不同的区域，这样每个区域只有16个值可以用（我这里是用char类型256/16=16）。在加上用另外一个变量表示逻辑上上的分类（目前按照功能分了12组，有登陆、贸易、银行、船厂等）这样对于贸易这个类型的游戏消息，从客户端发送到逻辑服务器上，只能有16中可能性，如果要发送更多消息，可能要增加另外一个逻辑分类：贸易2^_^!当初这样的设计只是想简化一下系统的处理过程，不过却造成了系统的扩充困难，要解决也不是没有办法，把类型分类的变量由char类型，改为int类型，这样对一个变量分区，在范围上会款很多，而且不会造成逻辑分类上的困扰，但是，这样存在一个弊端就是就是每条网络消息数据包的长度增加了一点点。不要小看这一个字节的变量，现在设计的一条游戏消息头的长度是10个字节，如果把char改为int，无形中就增加了3个字节，在和原来的比较，这样每条消息在消息头部分，就多出23％，也就是我们100M的网络现在只能利用77％而已。<br><br><br><br>&nbsp; &nbsp;^_^呵呵看出什么问题没有？<br><br><br><br>&nbsp; &nbsp;没有，那我告诉你，有一个概念被偷换了，消息头的数据不等于整条游戏的消息数据，所以，消息头部分虽然多出了23％，但是整条游戏消息并不会增加这么多，最多增加17％，最少应该不会操作5％。平均起来，应该在10％左右（游戏消息里，很多消息的实际部分可能就一个int变量而已）。不过，就算是10％，也占用了带宽。<br><br><br><br>&nbsp; &nbsp;^_^呵呵还看出什么问题没有？<br><br><br><br>&nbsp; &nbsp;^_^先去读一下我的代码，再回头看看，上面的论述还有什么问题。<br><br><br><br>&nbsp; &nbsp;实际上，每条游戏消息由：消息头、消息实体、结束标记组成，其中固定的是消息头和结束标记，所以，实际上一条实际上游戏消息的数据包，最多比原来的多15％，平均起来，应该是8％～10％的增量而异。<br><br><br><br>&nbsp; &nbsp;好了，不在这个计算细节上扣太多精力了。要解决这个问题，要么是增加网络数据的发送量，要么，就是调整游戏结构，例如，把两个功能服务器合并为一个服务器，这样服务器的对象实体就由原来的4个分为3个，两两间的通讯，就由原来的12路缩减为6路，只要分8个区域就ok了。这样每个逻辑分类就有32条游戏消息可以使用。当然，如果进一步合并服务器，把服务器端都合并到一个程序，那就不用分类了^_^!<br><br>&nbsp; &nbsp;在登陆服务器目录下，还有一组mynet.h/mynet.cpp的文件，是我当初为服务器端设计的函数，封装的是消息事件网络响应模型。只不过封装得不是怎么好，被抛弃不用了，有兴趣的可以去看看，反正我是不推荐看的。只不过是在这里说明一下整个工程目录的结构而已。<br></div>
<img src ="http://www.cppblog.com/deane/aggbug/75451.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2009-03-03 20:10 <a href="http://www.cppblog.com/deane/articles/75451.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CSV简介</title><link>http://www.cppblog.com/deane/articles/51992.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Tue, 03 Jun 2008 02:33:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/51992.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/51992.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/51992.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/51992.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/51992.html</trackback:ping><description><![CDATA[<p><br>&nbsp;</p>
<p>CSV全称 Comma Separated values，是一种用来存储数据的纯文本<br>文件格式，通常用于电子表格或数据库软件。有简单易用，占用存储字节少的特点。</p>
<p>规则</p>
<p>0 开头是不留空，以行为单位。<br>1 可含或不含列名，含列名则居文件第一行。<br>2 一行数据不垮行，无空行。<br>3 以半角符号（ASCII），作分隔符，列为空也要表达其存在。<br>4 列内容如存在，，则用""包含起来。<br>5 列内容如存在""则用""""包含。<br>6 文件读写时引号，逗号操作规则互逆。<br>7 内码格式不限，可为ASCII、Unicode或者其他。<br><br></p>
<p>&nbsp; XLS是Excel的原生格式，但是可以将之转存为csv，csv的格式非常简单，主要格式就是用逗号隔开每个数据。<br>所以，这是一个很不错的东西。<br>策划可以用Excel编辑文档，转存csv之后，就可以用于程序的读取。<br></p>
<p><br>见下例：<br></p>
<p>&lt;1&gt;. xls 文档：</p>
<p><img height=251 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/as.jpg" width=537 border=0><br></p>
<p><br>&lt;2&gt;.csv文档：<br></p>
<p><img height=211 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/ad.jpg" width=437 border=0><br></p>
<p><br>用程序读取这个csv文档，很easy了！<br></p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/deane/aggbug/51992.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2008-06-03 10:33 <a href="http://www.cppblog.com/deane/articles/51992.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>