farfetch 购物网站,网站写好了怎么做后台管理,蝶恋直播视频在线观看,公司展厅设计图片谈这个话题之前#xff0c;首先要让大家知道#xff0c;什么是服务器。在网络游戏中#xff0c;服务器所扮演的角色是同步#xff0c;广播和服务器主动的一些行为#xff0c;比如说天气#xff0c;NPC AI之类的#xff0c;之所以现在的很多网络游戏服务器都需要负担一些…
谈这个话题之前首先要让大家知道什么是服务器。在网络游戏中服务器所扮演的角色是同步广播和服务器主动的一些行为比如说天气NPC AI之类的之所以现在的很多网络游戏服务器都需要负担一些游戏逻辑上的运算是因为为了防止客户端的作弊行为。了解到这一点那么本系列的文章将分为两部分来谈谈网络游戏服务器的设计一部分是讲如何做好服务器的网络连接同步广播以及NPC的设置另一部分则将着重谈谈哪些逻辑放在服务器比较合适并且用什么样的结构来安排这些逻辑。 服务器的网络连接 大多数的网络游戏的服务器都会选择非阻塞select这种结构为什么呢因为网络游戏的服务器需要处理的连接非常之多并且大部分会选择在Linux/Unix下运行那么为每个用户开一个线程实际上是很不划算的一方面因为在Linux/Unix下的线程是用进程这么一个概念模拟出来的比较消耗系统资源另外除了I/O之外每个线程基本上没有什么多余的需要并行的任务而且网络游戏是互交性非常强的所以线程间的同步会成为很麻烦的问题。由此一来对于这种含有大量网络连接的单线程服务器用阻塞显然是不现实的。对于网络连接需要用一个结构来储存其中需要包含一个向客户端写消息的缓冲还需要一个从客户端读消息的缓冲具体的大小根据具体的消息结构来定了。另外对于同步需要一些时间校对的值还需要一些各种不同的值来记录当前状态下面给出一个初步的连接的结构 typedef connection_s { user_t *ob; /* 指向处理服务器端逻辑的结构 */ int fd; /* socket连接 */ struct sockaddr_in addr; /* 连接的地址信息 */ char text[MAX_TEXT]; /* 接收的消息缓冲 */ int text_end; /* 接收消息缓冲的尾指针 */ int text_start; /* 接收消息缓冲的头指针 */ int last_time; /* 上一条消息是什么时候接收到的 */ struct timeval latency; /* 客户端本地时间和服务器本地时间的差值 */ struct timeval last_confirm_time; /* 上一次验证的时间 */ short is_confirmed; /* 该连接是否通过验证过 */ int ping_num; /* 该客户端到服务器端的ping值 */ int ping_ticker; /* 多少个IO周期处理更新一次ping值 */ int message_length; /* 发送缓冲消息长度 */ char message_buf[MAX_TEXT]; /* 发送缓冲区 */ int iflags; /* 该连接的状态 */ } connection_t; 服务器循环的处理所有连接是一个死循环过程每次循环都用select检查是否有新连接到达然后循环所有连接看哪个连接可以写或者可以读就处理该连接的读写。由于所有的处理都是非阻塞的所以所有的Socket IO都可以用一个线程来完成。 由于网络传输的关系每次recv()到的数据可能不止包含一条消息或者不到一条消息那么怎么处理呢所以对于接收消息缓冲用了两个指针每次接收都从text_start开始读起因为里面残留的可能是上次接收到的多余的半条消息然后text_end指向消息缓冲的结尾。这样用两个指针就可以很方便的处理这种情况另外有一点值得注意的是解析消息的过程是一个循环的过程可能一次接收到两条以上的消息在消息缓冲里面这个时候就应该执行到消息缓冲里面只有一条都不到的消息为止大体流程如下 while ( text_end – text_start 一条完整的消息长度 ) { 从text_start处开始处理; text_start 该消息长度; } memcpy ( text, text text_start, text_end – text_start ); 对于消息的处理这里首先就需要知道你的游戏总共有哪些消息所有的消息都有哪些才能设计出比较合理的消息头。一般来说消息大概可分为主角消息场景消息同步消息和界面消息四个部分。其中主角消息包括客户端所控制的角色的所有动作包括走路跑步战斗之类的。场景消息包括天气变化一定的时间在场景里出现一些东西等等之类的这类消息的特点是所有消息的发起者都是服务器广播对象则是场景里的所有玩家。而同步消息则是针对发起对象是某个玩家经过服务器广播给所有看得见他的玩家该消息也是包括所有的动作和主角消息不同的是该种消息是服务器广播给客户端的而主角消息一般是客户端主动发给服务器的。最后是界面消息界面消息包括是服务器发给客户端的聊天消息和各种属性及状态信息。 下面来谈谈消息的组成。一般来说一个消息由消息头和消息体两部分组成其中消息头的长度是不变的而消息体的长度是可变的在消息体中需要保存消息体的长度。由于要给每条消息一个很明显的区分所以需要定义一个消息头特有的标志然后需要消息的类型以及消息ID。消息头大体结构如下 type struct message_s { unsigned short message_sign; unsigned char message_type; unsigned short message_id unsigned char message_len }message_t; 服务器的广播 服务器的广播的重点就在于如何计算出广播的对象。很显然在一张很大的地图里面某个玩家在最东边的一个动作一个在最西边的玩家是应该看不到的那么怎么来计算广播的对象呢最简单的办法就是把地图分块分成大小合适的小块然后每次只象周围几个小块的玩家进行广播。那么究竟切到多大比较合适呢一般来说切得块大了内存的消耗会增大切得块小了CPU的消耗会增大原因会在后面提到。个人觉得切成一屏左右的小块比较合适每次广播广播周围九个小块的玩家由于广播的操作非常频繁那么遍利周围九块的操作就会变得相当的频繁所以如果块分得小了那么遍利的范围就会扩大CPU的资源会很快的被吃完。 切好块以后怎么让玩家在各个块之间走来走去呢让我们来想想在切换一次块的时候要做哪些工作。首先要算出下个块的周围九块的玩家有哪些是现在当前块没有的把自己的信息广播给那些玩家同时也要算出下个块周围九块里面有哪些物件是现在没有的把那些物件的信息广播给自己然后把下个块的周围九快里没有的而现在的块周围九块里面有的物件的消失信息广播给自己同时也把自己消失的消息广播给那些物件。这个操作不仅烦琐而且会吃掉不少CPU资源那么有什么办法可以很快的算出这些物件呢一个个做比较显然看起来就不是个好办法这里可以参照二维矩阵碰撞检测的一些思路以自己周围九块为一个矩阵目标块周围九块为另一个矩阵检测这两个矩阵是否碰撞如果两个矩阵相交那么没相交的那些块怎么算。这里可以把相交的块的坐标转换成内部坐标然后再进行运算。 对于广播还有另外一种解决方法实施起来不如切块来的简单这种方法需要客户端来协助进行运算。首先在服务器端的连接结构里面需要增加一个广播对象的队列该队列在客户端登陆服务器的时候由服务器传给客户端然后客户端自己来维护这个队列当有人走出客户端视野的时候由客户端主动要求服务器给那个物件发送消失的消息。而对于有人总进视野的情况则比较麻烦了。 首先需要客户端在每次给服务器发送update position的消息的时候服务器都给该连接算出一个视野范围然后在需要广播的时候循环整张地图上的玩家找到坐标在其视野范围内的玩家。使用这种方法的好处在于不存在转换块的时候需要一次性广播大量的消息缺点就是在计算广播对象的时候需要遍历整个地图上的玩家如果当一个地图上的玩家多得比较离谱的时候该操作就会比较的慢。 服务器的同步 同步在网络游戏中是非常重要的它保证了每个玩家在屏幕上看到的东西大体是一样的。其实呢解决同步问题的最简单的方法就是把每个玩家的动作都向其他玩家广播一遍这里其实就存在两个问题1向哪些玩家广播广播哪些消息。2如果网络延迟怎么办。事实上呢第一个问题是个非常简单的问题不过之所以我提出这个问题来是提醒大家在设计自己的消息结构的时候需要把这个因素考虑进去。而对于第二个问题则是一个挺麻烦的问题大家可以来看这么个例子 比如有一个玩家A向服务器发了条指令说我现在在P1点要去P2点。指令发出的时间是T0服务器收到指令的时间是T1然后向周围的玩家广播这条消息消息的内容是“玩家A从P1到P2”有一个在A附近的玩家B收到服务器的这则广播的消息的时间是T2然后开始在客户端上画图A从P1到P2点。这个时候就存在一个不同步的问题玩家A和玩家B的屏幕上显示的画面相差了T2-T1的时间。这个时候怎么办呢 有个解决方案我给它取名叫 预测拉扯虽然有些怪异了点不过基本上大家也能从字面上来理解它的意思。要解决这个问题首先要定义一个值叫预测误差。然后需要在服务器端每个玩家连接的类里面加一项属性叫latency然后在玩家登陆的时候对客户端的时间和服务器的时间进行比较得出来的差值保存在latency里面。还是上面的那个例子服务器广播消息的时候就根据要广播对象的latency计算出一个客户端的CurrentTime然后在消息头里面包含这个CurrentTime然后再进行广播。并且同时在玩家A的客户端本地建立一个队列保存该条消息只到获得服务器验证就从未被验证的消息队列里面将该消息删除如果验证失败则会被拉扯回P1点。然后当玩家B收到了服务器发过来的消息“玩家A从P1到P2”这个时候就检查消息里面服务器发出的时间和本地时间做比较如果大于定义的预测误差就算出在T2这个时间玩家A的屏幕上走到的地点P3然后把玩家B屏幕上的玩家A直接拉扯到P3再继续走下去这样就能保证同步。更进一步为了保证客户端运行起来更加smooth我并不推荐直接把玩家拉扯过去而是算出P3偏后的一点P4然后用(P4-P1)/T(P4-P3)来算出一个很快的速度S然后让玩家A用速度S快速移动到P4这样的处理方法是比较合理的这种解决方案的原形在国际上被称为Full plesiochronous当然该原形被我篡改了很多来适应网络游戏的同步所以而变成所谓的预测拉扯。 另外一个解决方案我给它取名叫 验证同步听名字也知道大体的意思就是每条指令在经过服务器验证通过了以后再执行动作。具体的思路如下首先也需要在每个玩家连接类型里面定义一个latency然后在客户端响应玩家鼠标行走的同时客户端并不会先行走动而是发一条走路的指令给服务器然后等待服务器的验证。服务器接受到这条消息以后进行逻辑层的验证然后计算出需要广播的范围包括玩家A在内根据各个客户端不同的latency生成不同的消息头开始广播这个时候这个玩家的走路信息就是完全同步的了。这个方法的优点是能保证各个客户端之间绝对的同步缺点是当网络延迟比较大的时候玩家的客户端的行为会变得比较不流畅给玩家带来很不爽的感觉。该种解决方案的原形在国际上被称为Hierarchical master-slave synchronization80年代以后被广泛应用于网络的各个领域。 最后一种解决方案是一种理想化的解决方案在国际上被称为Mutual synchronization是一种对未来网络的前景的良好预测出来的解决方案。这里之所以要提这个方案并不是说我们已经完全的实现了这种方案而只是在网络游戏领域的某些方面应用到这种方案的某些思想。我对该种方案取名为半服务器同步。大体的设计思路如下 首先客户端需要在登陆世界的时候建立很多张广播列表这些列表在客户端后台和服务器要进行不及时同步之所以要建立多张列表是因为要广播的类型是不止一种的比如说有local message,有remote message,还有global message 等等这些列表都需要在客户端登陆的时候根据服务器发过来的消息建立好。在建立列表的同时还需要获得每个列表中广播对象的latency并且要维护一张完整的用户状态列表在后台也是不及时的和服务器进行同步根据本地的用户状态表可以做到一部分决策由客户端自己来决定当客户端发送这部分决策的时候则直接将最终决策发送到各个广播列表里面的客户端并对其时间进行校对保证每个客户端在收到的消息的时间是和根据本地时间进行校对过的。那么再采用预测拉扯中提到过的计算提前量提高速度行走过去的方法将会使同步变得非常的smooth。该方案的优点是不通过服务器客户端自己之间进行同步大大的降低了由于网络延迟而带来的误差并且由于大部分决策都可以由客户端来做也大大的降低了服务器的资源。由此带来的弊端就是由于消息和决策权都放在客户端本地所以给外挂提供了很大的可乘之机。