人一生这一辈子,能记住多少事?还是写下来的好。 注册 | 登陆
浏览模式: 标准 | 列表分类:服务器相关

【转】百万用户级游戏服务器架构设计

服务器结构探讨 -- 最简单的结构 

  所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求。所以,结构本无所谓正确与错误;当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可维护性也有更大的帮助。 

  好的结构不是一蹴而就的,而且每个设计者心中的那把尺都不相同,所以这个优秀结构的定义也就没有定论。在这里,我们不打算对现有游戏结构做评价,而是试着从头开始搭建一个我们需要的MMOG结构。 

  对于一个最简单的游戏服务器来说,它只需要能够接受来自客户端的连接请求,然后处理客户端在游戏世界中的移动及交互,也即游戏逻辑处理即可。如果我们把这两项功能集成到一个服务进程中,则最终的结构很简单: 

  client ----- server 

  嗯,太简单了点,这样也敢叫服务器结构?好吧,现在我们来往里面稍稍加点东西,让它看起来更像是服务器结构一些。 

  一般来说,我们在接入游戏服务器的时候都会要提供一个帐号和密码,验证通过后才能进入。关于为什么要提供用户名和密码才能进入的问题我们这里不打算做过多讨论,云风曾对此也提出过类似的疑问,并给出了只用一个标识串就能进入的设想,有兴趣的可以去看看他们的讨论。但不管是采用何种方式进入,照目前看来我们的服务器起码得提供一个帐号验证的功能。 

  我们把观察点先集中在一个大区内。在大多数情况下,一个大区内都会有多组游戏服,也就是多个游戏世界可供选择。简单点来实现,我们完全可以抛弃这个大区的概念,认为一个大区也就是放在同一个机房的多台服务器组,各服务器组间没有什么关系。这样,我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样: 

  loginServer   gameServer 
     |           / 
     |         / 
     client 

  该结构下的玩家操作流程为,先选择大区,再选择大区下的某台服务器,即某个游戏世界,点击进入后开始帐号验证过程,验证成功则进入了该游戏世界。但是,如果玩家想要切换游戏世界,他只能先退出当前游戏世界,然后进入新的游戏世界重新进行帐号验证。 

  早期的游戏大都采用的是这种结构,有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号,但整体结构还是未做改变。 

  该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单,就是将玩家提交的帐号和密码送到数据库进行验证,和生成会话密钥发送给游戏服和客户端,操作完成后连接就会立即断开,而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较空闲的,但是在某些时候,由于请求比较密集,比如开新服的时候,登录服的负载又会比较大,甚至会处理不过来。 

  另外在实际的游戏运营中,有些游戏世界很火爆,而有些游戏世界却非常冷清,甚至没有多少人玩的情况也是很常见的。所以,我们能否更合理地配置登录服资源,使得整个大区内的登录服可以共享就成了下一步改进的目标。 

» 阅读全文

Tags: 游戏服务器

【转】网游服务端结构设计

LoginServer <-----> GameServer

服务端主体分为LoginServerGameServer, LoginServer做帐户认证, GameServer做游戏主逻辑,

中间也可以加一个CharServer, 做人物管理, 新建删除人物之类的, 也可以并到GameServer

, LoginServerCharServer都比较简单, 略过.

通过LoginServer的验证后将分配给Client一个SessionID, 然后与GameServerCharServer的通信,

都以此SessionID为认证码. Client只有发送正确的SessionID才能与GameServer建立连接.

 

2.GameServer层次结构

GameServer分为三层, 网络层<--->逻辑处理层<--->数据库层

每层都有一个消息处理队列, 存放待处理的消息. 消息队列可以采用先进先出队列的方式, 也可以

采用堆或者优先队列的方式, 按优先级对待处理消息进行简单的排序, 嘿嘿, 是不是有点类似QoS

的思想.

每层采用线程池技术, 预先建立一定数量的空闲线程,  不够时建立新线程, 过多时则销毁线程,

保证线程池中有指定数量的空闲线程(Min/Max), 主线程不断检查处理队列是否有待处理消息,

有则从线程池中分配一空闲线程处理之.

偶在Linux下线程池是用pthread_cond_waitpthread_cond_signal实现的.

 

2.1.网络层

本层根据操作系统不同可以有多种实现, 主要功能是与客户端建立TCP连接, TCP流分割成一个个封包,

如果有加密就解密, 如果有压缩就解压缩, 加入事务层的处理队列, 同时把处理队列中待发送的消息发

送出去, 如果要加密就加密, 如果要压缩就压缩.

Windows下采用IOCP模型, Unix-like系统下可采用select/poll(epoll)/kqueue

偶在偶的服务端中采用了select方式, Linux单个端口的连接数有限制, 所以偶开了多个线程监听一组端口,

LoginServer做负载均衡, 从而保证不会出现某个端口连接数过多的情况. 每当有新客户端要登录时,

LoginServer判断每个端口的连接数量, 选最小发送给客户端. 偶想这里也可以做成动态方式,

当每个端口平均连接数超过XXXX, 就开新线程监听新端口, 并通知LoginServer.

 

2.2.逻辑处理层

本层是GameServer的核心.

根据操作码(OPcode)把消息分配到每个子模块里面处理. 最简单的方法就是用从0开始的连续的OPcode, 建立

一个与Opcode对应的处理函数的数组, Opcode作为数组的下标, 这样只需要O(1)的时间就可以调用到所需的函数

Hash都省了, 又简单又高效.

子模块详见第7部分

 

2.3.数据库层

本层用于数据存储, 本质上就是把内存里的数据存到硬盘上, 要是你够拽的话, 可以不用现有的数据库,

自己写算法存储文本文件, 但为了方便起见, 也为了提高效率, 还是用数据库比较好.

windows下用MSSQL, 或者用MYSQL,

Unix-like系统下可以用的就多了, 能多兼容几种数据库最好

MYSQL的性能优异, 功能上稍差一点, 如果不需要用到存储过程的话, MYSQL还是首选的.

数据库层一般用单线程已经足够, 可以不需要做对象互斥, 编程方面也会简单一点. 但是需要注意的是,

数据库操作方面一定要用Transaction, 可以有效防止复制现象发生, 比如:交易操作一旦发生错误,

rollback到交易之前, 不会发生钱已交出, 东西却没拿到的情况.

 

3.消息格式定义

3.1.网络层<-->逻辑层消息格式(网络封包格式)

3.2.逻辑层<-->数据库层消息格式

 

4.游戏对象定义(Object)

object

  |-------> item

  |           |----> container(容器类对象,如仓库、背包等)

  |

  |-------> unit

  |           |-----> player

  |           |-----> monster

  |           |-----> npc

  |           |-----> corpse(尸体对象)

  |

  |-------> gameobj

              |-----> dynamicobj(如技能产生的临时对象)

 

5.地图场景管理

6.脚本系统

7.逻辑层模块化设计  

 

对于地图场景管理打算采用这种方式

在服务器上把场景划分为小区域(视野大小)。每个区域对应一个list,场景中的所有对象按他们的位置加入到对应区域的list,那么每次行走只需要把消息发送给几个相临区域的Player

 

一个建议:

可以把GameServer的网络层剥离出来做成一个应用级网关(Application-level Gateway),因为这部分虽然逻辑简单但消耗资源确是比较大的。做成单独的可以使你的系统伸缩性更好,GameServerGate可以是1:N

 

 

对于一个分布式服务器架构来说要考虑的问题就很多:

首先要有一套设计良好的应用服务器框架和通讯中间件,应用服务器是上层应用的重要基础设施,用来实现诸如服务定位、名字注册、负载平衡、服务集群、故障重起、时间服务、LOG服务等等功能,通讯中间件也是不能少的,如果服务器之间通讯用手工写socket通讯的话,那很快你就会被协议处理、各种微妙的时序、同步等问题所困扰。可以说,你的服务器架构能实现到多复杂能堆多高,关键就看这两样实现的有多坚固耐用。

其次对象状态的序列化很重要,这关系到对象同步、对象persistent、状态迁移、故障恢复等等诸多问题。问题远不是把对象状态写到一个流里就完事那么简单的,做好这一块那就等于解决了一半的服务器逻辑问题了。

然后再谈谈分布的问题,基本上有两种思路,一种是按逻辑功能划分计算资源,一种是按容量划分计算资源。按逻辑功能划分是比较容易想到一种方式,简单的说就是将不同的功能模块实现到不同的进程里,这种方案看似不错,实则问题多多。首先是各模块之间必然要进行交互,这样就会引起很多细粒度的远过程通讯,一来造成性能损失二来增加了实现难度,其次是各模块负载并不均衡,很容易产生性能瓶颈,也很难实现负载平衡,另外这样的服务器程序调式起来将是一场噩梦。当然,凡事都没有绝对,一切设计都要根据实际情况来决定,有时候该按功能划分的还是要按功能划分,比如小高建议的划分一个应用级网关出来在某些情况下就很有效。现在已有的一些我们所能见到的超大无缝连接世界的分布式实现,其基本的思想是将世界划分成一系列相连的块,为每个块分配计算资源来实现并行分布运算。关键的问题在于处理相临块边界之间的同步问题,如果处理不好就可能造成同步失调、物品复制等等BUG,这需要一个设计良好的同步算法。关于计算资源的分配我们也 可以有两种选择,一种是使用性能强劲的多CPU服务器,在单进程里用多线程并行计算,即所谓的SMP架构,这样的优点是简单、高效,缺点是成本高,伸缩性不高。另一个就是服务器集群架构了,这样实现起来更复杂、而且服务器的瓶颈大多出在I/O而非计算上,使用集群以后增大了服务器之间的通讯量,设计不好反而造成性能损失,不过优点也很明显,降低成本,提高伸缩性,通过良好的设计还能实现故障恢复(也就是集群中任何一台服务器当机都不会影响游戏的运行)

最后再谈谈游戏逻辑的处理,一种设计是让服务器处于被动状态,只有客户请求到达再进行逻辑处理,刷新对象状态,这样可以降低一些计算量,但是因为服务器不进行对象状态的模拟,缺乏一些必要的信息,会限制一些功能(特别是真三维世界运动)的实现。我现在更倾向于传统游戏的做法,让服务器运行一个游戏逻辑循环,定时的模拟对象状态。

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bisword/archive/2009/02/03/3859667.aspx

【转】一种经典的网络游戏服务器架构

这个图是一个区的架构图,所有区的架构是一样的。上面虚线框的ServerGroup和旁边方框内的架构一样。图上的所有x N的服务器,都是多台一起的。红线,绿线,和蓝线图上也有图示,这里就不多介绍了。关于Agent Server大家也能看出来,其实就是Gate。
这里主要介绍下图上的标记了号码的位置的数据连接的内容和意义。

» 阅读全文

Tags: 网络游戏, 服务器, 架构

基于 OpenSSL 的 CA 建立及证书签发

前段时间研究了一下 SSL/TLS ,看的是 Eric Rescorla 的 SSL and TLS - Designing and Building Secure Systems 的中文版(关于该中文版的恶劣程度,我在之前的一篇 Blog 中已做了严厉的批判)。本书的作者沿袭了 Stevens 在其神作 TCP/IP Illustrated 中的思想:使用网络嗅探进行协议演示。不同的是,作者并没有使用 tcpdump ,而是使用了自己编写的专用于嗅探 SSL/TLS 通讯的 ssldump 。为了对书中的一些内容进行试验确认,我决定使用 ssldump 进行一些实验。然而,进行 SSL/TLS 通讯,至少需要一份 CA 签发的证书才可以得以完成,仅仅是做个实验,我自然不会花天价去买个证书,所以决定自己建 CA 签发证书。

» 阅读全文

Tags: ssl, ca证书

【转】bash: useradd: command not found?

为什么useradd不能用,我刚才明明su root了?
问题说明:
我刚才使用普通用户登录linux的,后来我想添加一个新的用户,因为只有root才有添加新用户的权利,所以使用su root。然后再使用useradd newuser,接着就出现上面的问题

通过上网查找资料知道。
首先从环境变量说起,在unix系统里面, 每个系统用户都有自己的环境变量来定义自己登陆上来的的SHELL,终端类型,路径等等,
在LINUX下,BSHELL的用户登陆后会执行主目录下的.bash_profile文件,
CSHELL的用户会执行.cshrc_profile文件,这些文件里定义了你这个用户的环境变量。
出现这个问题,有可能是以普通用户登陆主机,
而此用户的环境里面没有定义系统命令所在的一些路径,
比如/usr/bin,/usr/sbin等(就象WINDOWS里面的PATH一样),
或者在一些情况下TELNET上主机后也会遗失环境变量,这时候你可以做的是:
1. 确定需要此用户执行系统命令,那么可以把系统路径加到该用户的.bash_profile/.cshrc_profile的PATH里面。
2. 还是用ROOT用户执行命令,那么用命令su - 可以取得ROOT用户的权限和环境。
(注意,是su -而不是su。因为su是只取得ROOT的权限,
    su - 是取得ROOT的权限后还执行ROOT的PROFILE来取得ROOT的环境变量)
我这里出现问题就是如下这个原因:
su root只是获得root用户的权限;
su - root 不仅获得root用户的权限,而且还执行root的profile来
执行root的环境变量。

Tags: linux, su, root, useradd

让putty不掉线

使用putty连接服务器的时候,默认情况下如果一段时间不动就自动断线了。我是这样设置了一下,用起来感觉还方便,拿出来和大家共享,也供大家批判:-)

1 登录进去,然后右键点击putty的标题栏,选择change settings.
2 进入window->translation,选择utf-8编码
3 进入window->colors,选中“use system colors”,这样就是白底黑字,比较清楚
4 进入Connection设置,将Seconds between keeplives(0 to turn off)设置为30
5 选择session,将当前session起个名字,点save保存即可。

下次登录的时候,直接点击保存好的session名称,上面的设置就会生效了。

Tags: putty

【转】bad interpreter:No such file or directory的原因

今天在编译完Fortran的一个程序之后,却用原来的调用脚本怎么也没法执行,问题如标题,最好找到这篇文章,恍然大悟。
Linux下面一个脚本死活也运行不了, 我检查了数遍,不可能有错。快Insane啦!

提示:bad interpreter:No such file or directory

上网上找了好久,总算发现原来是文件格式的问题。这个文件是我在Windows下编写的。

换行的方式与Unix不一样,但是在VI下面如果不Set一下又完全看不出来。

气晕过去了~~~希望不会有人跟我一样倒楣,花了好几个小时in vain!!

解决方法:

1、程序是在一个网站上看到的,我保存下来,大致修改了一下。

 

2、上传到linux主机运行

     chmod +x back

     ./back

    错误提示如下:
    bash: ./back : bad interpreter:No such file or directory

3、错误分析:

因为操作系统是windows,我在windows下编辑的脚本,所以有可能有不可见字符。

从你的脚本及报告的错误看来, 很有可能是你的脚本文件是DOS格式的, 即每一行的行尾以\r\n来标识, 其ASCII码分别是0x0D, 0x0A.
可以有很多种办法看这个文件是DOS格式的还是UNIX格式的, 还是MAC格式的
(1). vi filename
然后用命令
:set ff?
可以看到dos或unix的字样. 如果的确是dos格式的, 那么你可以用set ff=unix把它强制为unix格式的, 然后存盘退出. 再运行一遍看.
(2). 用joe filename
如果是DOS格式的, 那么行尾会有很多绿色的^M字样出现. 你也可以用上述办法把它转为UNIX格式的.
(3). 用od -t x1 filename
如果你看到有0d 0a 这样的字符, 那么它是dos格式的, 如果只有0a而没有0d, 那么它是UNIX格式的, 同样可以用上述方法把它转为UNIX格式的.

转换不同平台的文本文件格式可以用
1. unix2dos或dos2unix这两个小程序来做. 很简单. 在djgpp中这两个程序的名字叫dtou和utod, u代表unix, d代表dos
2. 也可以用sed 这样的工具来做:
sed 's/^M//' filename > tmp_filename
mv -f tmp_filename filename
来做
特别说明:^M并不是按键shift + 6产生的^和字母M, 它是一个字符, 其ASCII是0x0D, 生成它的办法是先按CTRL+V, 然后再回车(或CTRL+M)

另外, 当SHELL程序报告command not found时, 总是去检查一下你的PATH里面有没有程序要用到的每一个命令(没指定绝对路径的那种). 你这么小的程序, 可以一行一行核对.

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/joliny/archive/2008/11/23/3357041.aspx

如果使用SSH进行数据库的备份与恢复

SSH/Shell 操作之如何备份,恢复MySQL数据库
前提,首先是要支持SSH(shell)登陆的空间了。
用SSH工具登陆空间,CD到你想要备份到的目录,
加入我们想把bak.sql这个数据恢复到以下的数据库里面:
MySQL地址 localhost
MySQL名称 sql_name
MySQL帐号 sql_user
MySQL密码 123456

备份命令:复制内容到剪贴板代码:

 

XML/HTML代码
  1. mysqldump -hlocalhost -udb_user -p123456 db_name >bak.sql  

 

你没看错: -h,-u,-p后面没空格,直接加的都是,加入空格后会出错

回车后输入密码bak.sql就开始备份到当前目录下了。

恢复命令:复制内容到剪贴板代码:

XML/HTML代码
  1. mysql -hlocalhost -usql_user -p123456 sql_name  

 

回车后输入密码后,在提示符下输入要恢复的数据库文件名(文件要保存在当前目录下),复制内容到剪贴板代码:

 

XML/HTML代码
  1. source bak.sql  

Tags: mysql, mysqldump

Records:1412