Firefly游戏服务器学习笔记 8 ———— net模块

net顾名思义,就是网络模块,负责接受客户端的连接,处理客户端发送过来的数据,解包转发给其它模块。整个firefly系统里面,和用户打交道的也只有这个模块(admin和master虽然提供web操作接口,但是都是服务管理员的)。

 

我们前面提到,子模块的功能是由config.json来配置驱动的。那么我们看看这个模块的json文件定义了哪些功能。

20     "servers": {

 21         "net": {

 22             "app":"app.netserver", #具有游戏功能模块,在框架启动完毕后需要import app/netserver.py文件

 23             "log":"app/logs/net.log", #日志

 24             "name":"net", #该模块的名称

 25             "netport":11009, #!!重要!! 定义了netport,则firefly会为其启动一个网络服务,来监听客户端连接。

 26             "remoteport": [  #需要连接其它node,这里的其它node定义的是gate。实际上我们可以有不止一个“gate”来实现分布式逻辑。

 27                 {

 28                     "rootname":"gate",

 29                     "rootport":10000

 30                 }

 31             ]

 32         }                                                                                                     

 33     }

 

 

那么对应的启动代码如下:(init 和 start函数省略,只列出关键部分)

 

40     defconfig(self, config, dbconfig =None,memconfig =None,masterconf=None):

 41         """配置服

 42         """

 43         netport= config.get('netport')#

 44         gatelist = config.get('remoteport',[])#remote点配置列表

 45         servername = config.get('name')#器名称

 46         logpath = config.get('log')#日志

 47         app = config.get('app')#入口模名称

 48         self.servername = servername

 49

 50         if masterconf:

 51             masterport = masterconf.get('rootport')

 52             addr = ('localhost', masterport)

 53             leafnode = leafNode(servername)

 54             serviceControl.initControl(leafnode.getServiceChannel())

 55             leafnode.connect(addr)

 56             GlobalObject().leafNode = leafnode

 57

 58         if netport:

 59             self.netfactory = LiberateFactory()

 60             netservice =services.CommandService("netservice")

 61             self.netfactory.addServiceChannel(netservice)

 62             reactor.listenTCP(netport,self.netfactory)                                                      

 63             GlobalObject().netfactory =self.netfactory

 64

 65         for cnfin gatelist:

 66             rname = cnf.get('rootname')

 67             rport = cnf.get('rootport')

 

68             self.gates[rname] =leafNode(servername)

 69             addr = ('localhost', rport)

 70             self.gates[rname].connect(addr)

 71

 72         GlobalObject().remote = self.gates

 73

 74         if logpath:

 75             log.addObserver(loogoo(logpath)) #日志

 76         log.startLogging(sys.stdout)

 77

 78         if app:

 79             reactor.callLater(0.1,__import__, app)

 80                                            

 

可以看出主要启动3个功能:

1、masterconfig部分,是说明本模块作为master模块的leafnode要连接master模块,这个和gate模块一样,可以参考前面PB的介绍章节。

 

2、连接gate模块,这里代码假定了不止一个需要连接的gate;但是实际上我们config.json里面只设定了一个。 这个步骤和连接master一样。

 

3、真正新鲜的东西是netport部分,这里      LiberateFactory实际上是protocol.ServerFactory的子类。它使用的协议是LiberateProtocol,这个是protocol.Protocol的子类。

 

如果大家了解twisted,那么我提到ServerFactory,和Protocol,大家就已经明白了netport部分的原理,压根不需要我废话。所以对于新人,不了解twisted,我们的主要任务是学习,并熟悉它。如下:

 

1、官网的介绍,这个比较简单,但是对于有经验的开发者来说,已经足够了解了(官网上面其它samples,也有不少是用protocol的)

ht空格t空格ps://twistedmatrix.com/documents/current/core/howto/servers.html

 

2、专门介绍twisted的书籍。有中文版的,内容我记得好像是比较浅显易懂。

h空格ttp://www.amazon.com/exec/obidos/ASIN/1449326110/jpcalsjou-20

第二章专门介绍protocol我就不多说了。

 

到这里我们假设大家已经熟悉twisted.protocol,那么我们看看firefly的实现部分。

大家知道twisted是事件驱动的,所以整个框架看起来很简单,我们即使对整体不熟悉,只要关注我们关心的事件处理函数部分也是ok的。

 

首先,

      reactor.listenTCP(netport,self.netfactory)

这里建立一个服务,等待客户端connect。 每次接收到一个client连接,Factory都会调用    Factory.protocol 也就是 LiberateProtocol来处理,类似fork的概念。Factory充当了一个LiberatePtotocol的管理者的角色,干活的还是LibrateProtocol。所以我们主要关心LiberatePtotocol,代码如下:

class LiberateProtocol(protocol.Protocol):

    """协议"""

 

    buff = ""

 

    defconnectionMade(self):

        """连接建立处理

        """

        log.msg('Client %dlogin in.[%s,%d]' % (self.transport.sessionno,

               self.transport.client[0], self.transport.client[1]))

       self.factory.connmanager.addConnection(self)

       self.factory.doConnectionMade(self)

        self.datahandler =self.dataHandleCoroutine()

        self.datahandler.next()

 

    defconnectionLost(self, reason):

        """连接断开处理

        """

        log.msg('Client %dlogin out.'%(self.transport.sessionno))

       self.factory.doConnectionLost(self)

       self.factory.connmanager.dropConnectionByID(self.transport.sessionno)

 

    defsafeToWriteData(self,data,command):

        """线程安全的向客户端发送数据

        @param data: str 要向客户端写的数据

        """

        if notself.transport.connected or data is None:

            return

        senddata =self.factory.produceResult(data,command)

       reactor.callFromThread(self.transport.write,senddata)

 

    defdataHandleCoroutine(self):

        """

        """

        length =self.factory.dataprotocl.getHeadLenght()#获取协议头的长度

        while True:

            data = yield

            self.buff += data

            whileself.buff.__len__() >= length:

                unpackdata= self.factory.dataprotocl.unpack(self.buff[:length])

                if notunpackdata.get('result'):

                   log.msg('illegal data package --')

                   self.transport.loseConnection()

                    break

                command =unpackdata.get('command')

                rlength =unpackdata.get('lenght')

                request =self.buff[length:length+rlength]

                if request.__len__() < rlength:

                   log.msg('some data lose')

                    break

                self.buff= self.buff[length+rlength:]

                d =self.factory.doDataReceived(self, command, request)

                if not d:

                   continue

               d.addCallback(self.safeToWriteData, command)

               d.addErrback(DefferedErrorHandle)

 

    def dataReceived(self,data):

        """数据到达处理

        @param data: str 客户端传送过来的数据

        """

        self.datahandler.send(data)

 

 

可以看到主要处理了3个事件。

connectionMade #客户端连接我们

connectionLost(self, reason) #连接丢失

dataReceived(self, data) #数据到达

当客户端连接的时候,我们会调用Factory的connectmanager(也是简单的一个管理类)来保存当前连接信息。

整个系统只有一个Factory类的实例,但是每当一个连接来的时候,都会fork一个Protocol的实例。所以信息都会保存在Factory的属性里面,比如Factory.connectionManager。

同理,当连接丢失的时候,我们调用connectionManager处理一下即可。没有什么复杂的。

 

我们主要关注的是数据到来的处理逻辑:

1、数据到达,我们简单的发送给处理函数:        self.datahandler.send(data)

2、这个处理函数包含了yield 语句所以它是一个生成器,generate。具体大家可以google  python yield,会有很详细的介绍文档。

这里只是告诉大家

2.1、datahandler函数,每次调用到yield的地方就会暂停,等待下次被next,或者send唤醒,然后从yield的地方继续执行。

2.2、data = yield,这条语句在被唤醒后 data就被赋值为 send传递进来的参数。

2.3 那么这个函数改写成这样,大家就很容易理解了:

def dataHandleCoroutine(self,sendData):  ########改动地方

        """

        """

        length =self.factory.dataprotocl.getHeadLenght()#获取协议头的长度

            data = sendData   ########改动地方

            self.buff +=data

            whileself.buff.__len__() >= length:

                unpackdata= self.factory.dataprotocl.unpack(self.buff[:length])

                if notunpackdata.get('result'):

                   log.msg('illegal data package --')

                   self.transport.loseConnection()

                    break

                command =unpackdata.get('command')

                rlength =unpackdata.get('lenght')

                request =self.buff[length:length+rlength]

                ifrequest.__len__() < rlength:

                   log.msg('some data lose')

                    break

                self.buff= self.buff[length+rlength:]

                d = self.factory.doDataReceived(self, command,request)

                if not d:

                    return    ########改动地方

               d.addCallback(self.safeToWriteData, command)

               d.addErrback(DefferedErrorHandle)

3、剩下了就简单了,利用python struct库来解包数据,           

ud =struct.unpack('!sssss3I',dpack)

然后调用自己的services来处理解包后的command和参数。至于如何处理命令,就要看这个services挂什么样的处理函数了。

 

启动流程介绍完毕,下面就是app里面,暗黑游戏部分的具体处理内容了。

 

调用流程和前面一样,我们直接到关键函数:

defloadModule():

    netapp.initNetApp()

    import gatenodeapp

这里有些代码冗余和重复,但是没有什么太大影响,我们不提了。

这个函数的第一行代码是我改动过的(原先是import+修饰符方式),大家跟进去看,其实就是初始化一个services的子类:NetCommandService,然后给它挂上Forwarding_0 这个处理函数。

 

而我们仔细看NetCommandServices发现它override了原先的callTargetSingle,主要是这句改动:

           target = self.getTarget(0)

说明不论啥命令过来,它都写死了调用Forwarding_0来处理。

而Forwarding_0这个函数就一句话,功能是转发前面解析的command和参数给gate模块。

 

OK到这里结合前面的gate模块,我们应该就可以理出一条client登录的主线。

0、暗黑客户端一启动,就会连接net模块,net模块建立一个对应的connection,并由connectionManager保存。

1、暗黑客户端发送用户名、密码和登陆命令号101: (会封包,这里忽略)

        Json::FastWriter  writer;

        Json::Valueperson;

        person["username"]=userName;

        person["password"]=password;

        std::string  json_file=writer.write(person);

        CCLog("%s",json_file.c_str());

        SocketManager::getInstance()->sendMessage(json_file.c_str(),101);

 

2、net模块接收并解析出命令号 101,参数{name:xxx,pwd:xxx}。然后调用自己的services(实际是NetCommandServices)处理。

3、NetCommandServices,不管37 21,直接写死调用Forwarding_0函数。

4、Fowarding_0函数写死了,用命令fowarding转发给gate模块。

4、gate模块收到它leafNode的调用请求,所以调用自己作为root的services。而这个services在gate/app/gate/rootservice/rservices.py里面注册了forwarding函数,所以就调用它。

5、md,fowarding函数发现这个命令号注册在loacalservices里面,见

gate/app/gate/localservice/lservices.py中:

def init():

    initLocalService()

 

   addToLocalService(loginToServer_101) ##############这里

    addToLocalService(activeNewPlayer_102)

   addToLocalService(roleLogin_103)

于是调用loginToServer_101来处理这个命令

6、这个命令就不展开了,很简单,就是取数据,比对是否匹配,check是否有效。然后逐层返回,最后送给客户端

 

 

对于登录部分,流程压根没有跑到db、game1和admin模块,所以我们可以直接利用master,gate,net这3个模块做测试

顺序启动

master

gate

net

然后启动tool/clienttest.py

可以看到net模块的终端会有信息打印。

 

我自己也写了一个测试程序。已经放在github上面,tool/ clientTestLogin.py;

用的是twisted,很简单。如下:

 

  1 from twisted.internet.protocolimport Protocol, ClientFactory                                                

  2

  3 classEcho(Protocol):

  4     defconnectionMade(self):

  5         a= 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n'

  6         self.transport.write(a)

  7

  8     defdataReceived(self, data):

  9         print data

 10         #self.transport.loseConnection()

 11

 12

 13 classEchoClientFactory(ClientFactory):

 14     defstartedConnecting(self, connector):

 15         print'Started toconnect.'

 16

 17     defbuildProtocol(self, addr):

 18         print'Connected.'

 19         return Echo()

 20

 21     defclientConnectionLost(self, connector,reason):

 22         print'Lostconnection.  Reason:', reason

 23

 24     defclientConnectionFailed(self, connector,reason):

 25         print'Connectionfailed. Reason:', reason

 26

 27 from twisted.internetimport reactor

 28 reactor.connectTCP("localhost",11009,EchoClientFactory())

29 reactor.run()                                                                                                

 

很简单,就30行代码,而且都是框架 。就是在连接上server后,事件connectionMade里面往server写一条命令。这条命令是我直接copy暗黑客户端的打印,就是一条封装好的数据包。

  5         a= 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n'

 

运行后结果如下:

 

bash-3.2$ pythonclientTestLogin.py

Started to connect.

Connected.

N%&0    We{"data": {"userId":1915, "characterId": 1000001, "hasRole": true},"result": true}

可以看出,server成功处理,并且返回我们登录后的角色信息。

 

 

OK,这章介绍结束,下面介绍game1模块。

你可能感兴趣的:(游戏,server,服务器,game,cocos2d-x,Firefly)