zoukankan      html  css  js  c++  java
  • <转>浅谈网络游戏的设计——服务器端编程(2)

    浅谈网络游戏的设计——服务器端编程(2)


      非常感谢大家对上一篇文章的支持,在大家的支持下,我决定推出浅谈网络游戏开发(2)这篇文章。
     

    首先,再次强调一下,网络游戏开发极为困难,技术含量相当的高,其编程知识涉及网络编程,操作系统进程、线程编程,图形图像编程(DirectX / OpenGL),WIN32 API编程(Windows下开发),以及各种算法和数据结构,同时对设计人员策划能力要求也颇高,如不能构思出一个吸引玩家的游戏世界,也必将导致开发失败
     

    目前,国内的网络游戏市场被韩国游戏霸占,情形让人心寒。在国内网络游戏编程资源奇缺的环境下,我希望把自己的一些经验和想法说出来,供大家参考,起一个抛砖引玉的作用。
     

    对于我在浅(1)中提出的架构,如果大家有更好的修改建议,欢迎大家共同探讨修改,把我国的网络游戏开发水品提高到世界级标准。最起码,也要在国内市场是立足!
     

    好了,费话就不多说了,正文开始。
     

    在浅(1)中,关于游戏世界管理模块和通讯模块我没有详细说明,本篇中将补充介绍。

    浅谈网络游戏的设计——服务器端编程(1)


      本游戏服务器端操作系统采用UNIX,因为UNIX是标准的服务器操作系统,可保证网络游戏的稳定性。因此,以下所有的编程都将针对UNIX进行。

      服务器端的整体构架如下:通讯模块,消息传递模块,游戏规则模块,线程管理模块,游戏世界管理模块。
     

    通讯模块:

      通讯模块主要实现与客户端的通讯功能,实际上,通讯模块就是对套结字Socket的封装。Socket是UNIX下的网络通讯基础,对于一个Socket我们可以对其进行读写操作,读入的数据来自客户端,写入的数据可供客户端读取。
      Socket主 要有阻塞套接字和无阻塞套接字两种,对于无阻塞套结字,每次读写后,不管读写的字节数是否达到要求,都立即返回;而对于阻塞套结字,若读写字节数不够,函 数将被阻塞,直到所有待处理的数据都处理完毕才返回。可以看出,若采用无阻塞套结字,则将使网络传输变得很不稳定,在网络环境不好时很难控制传输。因此, 对于我们的系统,将采用阻塞模式。
      我们下一个面临的问题是怎么知道何时读入,如果在不合适的时候从阻塞套接字读入数据,那线程很可能将被阻塞,这里采用了select——多路复用技术,原理是对我们的socket进行监视,如果socket上有读事件发生,将调用消息模块发送消息给socket的携带对象,对其进行读写。

    以下是对Socket的简单封装:

    class SSocket

    {

        fd_set *SockSet;                           //fd_set,也就是我们的select监听集合

        char IsListenSocket;                       //是否是监听套接字

        int ServerPort;                            //监听套接字的监听端口号

        struct sockaddr_in addr;                   //地址信息

     

      public:

        SSocket();

        ~SSocket();

       

        int Socket;                                 //socket

        int CreateListenSocket(fd_set *sset, int Port, char* addr); //初始//化一个监听socket

        int AcceptSocket(int listen_fd, fd_set *sset);              //初始化一个非监听socket

        int CloseSocket();                                          //关闭 socket

     

        int SendBuf(void *buf, int size);          //发送数据

        int RecvBuf(void *buf, int size);          //接收数据

       

        int SetSocketFd();                                          //socket加入到监听集合中

        int ClrSocketFd();                                          //socket从监听集合中清除

     

    };

    通过对socket的封装,我们完成了通讯模块的基本任务。下一步要做的是传输网络上的消息,此时需要对SSocket继续封装,首先定义一个消息结构体,然后就是读写消息,消息结构根据各个游戏不同,在此就不再深入探讨了。
     

     

    线程管理模块

     

    由于我们用了阻塞模式,这就意味着必须要为各个socket创建单独的线程,否则很可能会引起服务器端停止工作。因此我们需要封装线程,封装内容主要包括:线程函数地址,线程开始时间,线程上次阻塞时间,线程最大阻塞时间,线程start方法,线程stop方法。线程stop方法可以通过发送信号给线程来达到杀死线程的目的。

    线程类封装完成后,我们就可以开始编写管理模块代码了。管理模块其实也是一个线程,其第一功能就是监视各个线程是否阻塞超时,通过察看线程上次阻塞时间和最大阻塞时间来完成。一旦发现当前时间超过线程最大阻塞时间加上线程上次阻塞时间,既可断定线程阻塞超时,此时就需要kill该线程。另外,其他一些根线程有关的管理方法都有此模块负责。

     

     

    消息传递模块:

     

    对象、模块之间怎样传递消息,这也是服务器端设计的重点。举一个简化的例子说明如下:若某一玩家对另一玩家发了一条信息,先通过通讯模块接收数据,然后用消息传递模块通知另一玩家,再由另一玩家的线程调用通讯模块把消息发回客户端,这就是消息传递模块的作用。

    要怎样封装消息模块呢?第一步就是做一个MessageBox类,它是一个堆栈,用来装消息,主要由poppush方法,当然这里不能忘了必须实现一个存储消息数据结构。第二步封装就是HandleMessage类,这就是我们的消息模块的主要实现。其中有一个WaitMessage方法,调用此方法后,线程将被阻塞,直到有消息到达。在此可通过无名信号量来实现,也就是UNIX下的sem,它可以增加或减少信号量来实现互斥。然而,有人一定会问,为什么我们要有WaitMesssage呢,这不是将造成线程阻塞吗?其实,服务器端是一个被动驱动的模型,就像没有踩油门汽车就不会走一样,如果没有消息来驱动,服务器端就不会运行下去。

    实现以上封装后,在两个对象之间发消息就变得很简单,我们直接用SendMessage方法就可以,SendMessage的实现也很简单,就是调用MessageBoxPush方法向里边放消息,之后把sem加一,这样接受这就可以收到消息了。

     

     

    其他:

     

    剩下的两个模块,都是与游戏相关的,事实上他们是两个更为复杂的模块,根据要编写的游戏的不同,这两个模块实现也不同。但是,他们究竟是做什么的,下面通过一个例子来说明:

    假设我们现在开发的是一个RPG游 戏,我们的玩家在屏幕上让游戏人物向前走了一步,此时发送移动请求给服务器端,服务器端的通讯模块收到后,便通知游戏世界管理模块,游戏世界管理模块调用 游戏规则模块,判断玩家请求是否符合规则(是否合法),若可以移动,再由游戏管理模块将其坐标改变,最后再通知其相关玩家。也就是说,规则模块实际上是专 门处理游戏业务逻辑的,管理模块实际上是专门处理游戏对象的。

     

     

    小结

      网络游戏的开发入门是挺难的,实际上,本文只能起到抛砖引玉的作用,仅告诉大家一个入门的方法而已,剩下的,还有那烦人的一行一行实现代码。

    非常希望对网络游戏设计感兴趣的与我交流

    sunxing@cosco-logistics-km.com.cn 

      

    游戏世界管理模块:

           本模块专门管理游戏世界里的数据模型,也就意味着,所有游戏里的对象基本上都由他来管理所以,此模块极为复杂,甚至在大型系统里,也可以把它再划分成很多子模块来协同工作。
     

    这个模块该怎么封装呢?首先,自然是需要一个消息处理类,因为游戏世界管理模块同样是需要消息驱动的,此模块每收到一个消息后,就察看消息类型,看是转发 类型还是管理类型的消息,如果是转发类型,就将消息转发给消息目的地模块,如果是管理类型的消息,就察看管理的目标以及管理的方法,然后执行管理方法。因 此,我们还需要的就是一个辨别消息的方法,以及一些数据及操作数据的方法。

     

     

    游戏规则模块

          

           本模块按照游戏策划者制定的规则来进行业务逻辑处理。同样,首先需要封装的也是消息处理类。然后就是辨别消息,按照消息提示进行规则处理,随后就是将处理结果封装成消息,发给管理模块,基本上与游戏世界管理模块模式相同。

     

           以上谈了两个模块的封装思想,但是,实际上,这两个模块是不可能像上面写得那样运用的,很多朋友也谈到这个构架并不适合作大型网络游戏,那么,真正的大型网络究竟是怎样的架构呢?

           就像我们的OSI模型和TCP/IP模 型一样,只有后者能真正运用在工业标准中,前者固然是好的,但是他封装得太细了,太过于复杂了,不适合现在的情况使用。在真正的网络游戏中,以上的两个模 块是合在一起的!我把它们统称为游戏世界模块。请大家注意看下面这个模型(发了几次图片都失败了,所以用文本弄了一个,请谅解)

     

      

    从上图可以看出,实际上,游戏规则模块和游戏管理模块被合并在一起了,也就是说,这两个模块之间不需要消息传递,他们只是简单的函数调用关系。

     

    规则判定要做的主要工作就是辨别消息,把我们的消息翻译成对对象的处理方式。

    我们的游戏世界是有很多对象构成的,一个对象同时也可以携带多个对象,对象也可以不断增加、扩充。每当我们添加或扩充一个新对象,我们可以把它include进来,再在规则模块里加入对他的方法调用。
     

    这里要说明的一点就是,其实,所有的对象都是无差别的。大家都是数据模型,不管你是一个人,还是一棵树,或者一种道具,甚至魔法,他们都只是一些属性而 已。这些属性,统统都存在我们的配置文件中,甚至可以存在数据库中。到时候创建对象的时候把属性带入就可以了。当然,关于游戏世界对象的想法,有很多好的 提议,我甚至想过可以不要对象的方法,只要它的属性,反正我们只是改变对象的属性而已,而把怎么改变这些属性按一定的格式写在文件或数据库中,比如inc XX 0.3表示XX属性+30%之类的,这样就可以很方便的改变规则和对象。
     

    下面,我针对大家的问题提出一些看法:

    首先,可能大家会有疑问,这样的系统架构,似乎很慢啊,怎么维持那么多玩家在线呢?如果有那么多对象要处理的话,怎么可能保持速度?

    1. 服务器不是大家用的PC机,只要你能用一下服务器和PC机,你就知道他们的区别了,用unix开发也是为了确保游戏能在小型机之类的服务器上运行。

    2.  很多人认为网络游戏应该支持数十万人的玩家同时在线,比如说传奇,联众,他们确实是30-40万人在线哪!实际上,大家看仔细了,每台服务器到底是多少人,有的服务器才6XX人就说满了,这就是网络游戏的真实情况。

    3. UDPTCP的争议问题,有的朋友可能认为应该用udp,就因为它快,其实这个问题没什么好争的,我们的ftp为什么不用udp呢?如果用udp,我们就得自己封装一套tcp的确认机制出来,

    4.  对于同步问题,一般来说,客户端确实就是有什么发什么,但是要控制发送的间隔时间,这也是为了防止变速齿轮和解决同步问题。比如客户端一旦发出移动命令 后,客户端自己首先判断是否在间隔时间内,再判断是否能移动,能移动了才发消息给服务器端,同时开始移动,如果服务器端发回消息移动成功,那就成功,否 则,屏幕上的人物就会被拉回来。

     

    连接池技术

           很多朋友都比较关心的一个问题就是:为每个连接分配一个线程,是否太浪费了!实际上,apache大家都知道吧,他为每个连接分配的可是一个进程呢!线程比进程开销要小得多,如果用户数不是很多的话,那是没有问题的。但用户数不是很多要怎么理解呢:linux6.2下测试,每个进程最多能创建1000个线程左右(实际应该是1024),win2000 advance server sp3下测试,每个进程最多能创建2000个线程左右(实际应该是2048),这下大家明白了吧。
     

           不过,再怎么说连接线程只是用在网络通讯上,要支持更多的用户的话,太不划算,因此在这里就介绍一下连接池技术。
     

           所谓连接池,实际上是我们事先创建一些线程,每次通讯模块要发消息时,就找一个空闲的线程,然后把要发的消息给它,让它去发送。这样处理的话,创建很少的线程就够用了。这有点类似于apache的进程预处理,又更像jdbc的数据库连接池。这就要求封装一个连接池控制类。
     

           总之,如果要用连接池技术的话,就会使通讯模块复杂化,但是它能大量节约系统资源,建议大家设计的时候可以让用户在配置时选择是否使用连接池技术,这样根据不同的系统,就可以有不同的配置方案。

          

    小节

           这次探讨就到这里,不知这次又要引出多少反对意见,不过,我真的很希望看到大家的意见,因为任何软件开发本身就是仁者见仁,智者见智的问题,在这里,没有绝对的标准,更没有绝对的真理,我们要的是真正能实现的方法,希望大家在给出意见的同时,最好能给出好的建议。

  • 相关阅读:
    mongodb的sql日志
    mysql – 在WHERE子句中使用substr的SELECT语句
    MySQL视图
    Linux简单查找log
    转 信号量与PV操作
    二进制小数及 IEEE 浮点表示
    转 :原码,反码,补码
    转:C# Delegate委托 1
    C#中Invoke的用法2
    C#中Invoke的用法1
  • 原文地址:https://www.cnblogs.com/simonhaninmelbourne/p/2697969.html
Copyright © 2011-2022 走看看