gfirefly是开源的分布式游戏服务器端框架,是firefly的gevent版本,想了解更多关于firefly可参考http://www.oschina.net/question/947559_147468,这是firefly的官网http://firefly.9miao.com/。不过我关注的是gfirefly,主要有两个原因。
1.gfirefly性能更好(官方说法)
2.我对twisted不是很熟,但对gevent比较熟悉,想阅读源码可能gfirefly更合适。
不得不说9秒很有才,由于firefly底层使用了twisted,所以他们开发了一个简易版本的gtwisted,封装了twisted中的Protocol,Factory,Transport等概念,所以导致gfirefly代码和firefly保持惊人的一致。
建议大家可以先看看9秒的wiki文档,下载地址http://firefly.9miao.com/down/Firefly_wiki.CHM
完整的gfirefly包含以下几个组件:
下面将介绍上图的节点:
1. master管理节点 这是用来管理所有节点的节点,如可通过http来关闭所有节点(可回调节点注册的关闭方法),其实master节点也可以理解为是分布式root节点,其它节点都是remote节点
2.net前端节点 net节点是client端连接节点,负责数据包的结束,解包,封包,发送。net节点也是gate节点的分布式节点,由于游戏中流量较大,所以一般net节点只负责解包,封包,然后将解包后的数据转发给gate分布式根节点,处理完毕后再有net节点将处理结果发给client
3.gate分布式根节点 net节点将解包的数据发给gate节点后,gate节点可以自己处理数据返回结果,也可以调用remote子节点处理数据。
4.remote子节点 一般remote子节点都是真正干活的节点
5.dbfront节点 这个节点一般是负责管理memcache和数据库交互的节点
通过以上分析,我们可以很清晰的看出gfirefly的确是分布式的游戏服务器框架。
我们看看gfirefly源码的总体结构:
dbentrust/ 主要实现memcache和数据库之间的映射
distributed/ 实现了分布式的节点和管理,root.py主要是分布式根节点,node节点实现了远程调用对象的
management/ 主要提供了命令行创建项目,类似django的createproject等
master/ master节点相关,web管理接口,以及启动其它节点,通过subprocess模块
netconnect/ net节点的封包解包,以及连接管理
server/ gate等其它节点都是通过server/server.py来实现的
utils/ 一些有用工具,如单例metaclass,贯彻节点提供的服务(servers.py)
gfirefly提供了较为完整的分布式控制,可以通过配置文件开启所需的节点。也许你的项目流量不大,并不需要分布式,或者压根没有数据库,那么只开启net节点就好了。当然net节点一般肯定是需要开启的,下面我们来看看gfirefly的配置文件。
新建简单的一个项目,目录结构如下:
项目可以在我的github上下载:https://github.com/Skycrab/gfirefly/tree/0.16/gfirefly/example/ex_all
因为这个项目涉及到所有的节点,所以我叫ext_all(example_allnode)
配置文件config.json:
{ "master":{"rootport":9999,"webport":9998}, "servers":{ "gate":{"rootport":10000,"name":"gate","app":"app.gateserver"}, "dbfront":{"name":"dbfront","db":true,"mem":true,"app":"app.dbfrontserver"}, "net":{"netport":1000,"name":"net","remoteport":[{"rootport":10000,"rootname":"gate"}],"app":"app.netserver"}, "game1":{"remoteport":[{"rootport":10000,"rootname":"gate"}],"name":"game1","app":"app.game1server"} }, "db":{ "host":"localhost", "user":"root", "passwd":"", "port":3306, "db":"anheisg", "charset":"utf8" }, "memcached":{ "urls":["127.0.0.1:11211"], "hostname":"anheisg" } }
1.master定义了两个端口,故名思议,webport就是我们可以通过http端口管理节点,如http://127.0.0.1/stop就是关闭服务器和所有节点。我们上面说过其实master也是其它所有节点的根节点,所以rootport就是监听的节点,其它所有节点会在初始化时连接rootport
2.重点在servers,所谓的servers也就是我们要起的节点。
#gate,因为gate也是其它节点(net节点等)的根节点,所以它需要rootport,也就是说gate将会监听10000端口等待子节点的连接。name就是给gate起个名字,关注一下app,app唯一的作用就是gate节点最后会import app.gateserver,其实也就是运行app.gateserver.py,从上面的项目结构我们看到的确有这个文件。在这个文件里,我们会定义gate将如何处理数据,后面会看到。
#net,我们知道net是client端连接的节点,所以netport也就是net监听的端口,client将向netport这个端口发送数据。重点在remoteport,所谓的remoteport其实就是定义它的父节点,父节点可以有多个,所以是数组。我们看到父节点是10000端口,是gate监听的端口,所以说gate是net的父节点。
#game1,我们看到game1的remoteport中也是有gate节点的,所以gate节点也是game1节点的父节点。因为game1节点并不需要监听其它端口,所以它没有定义自己的rootport。
#defront,前面说过这个节点主要是将表映射到memcache中,所以需要数据库(db:true),需要memcache(mem:true)。其实定义定义db,mem都是说明这个节点需要到数据库和memcache,gfirefly会根据配置文件自动配置全局的memcache client对象,db也是一样。
3.db和mem大家都懂的。
通过以上分析,其实所有的节点关系都是通过配置文件联系的,包括我们所说的gate节点,其实你完全可以定义为其它节点,只不能起gate作用的我们称之为gate而已。
下面我们看一下配置文件中所有的app入口文件。
前端节点:dbfrontserver.py:
#coding:utf8 ''' Created on 2014-8-11 @author: [email protected] ''' from gfirefly.server.globalobject import GlobalObject def doWhenStop(): """服务器关闭前的处理 """ print '############' print 'server stop' GlobalObject().stophandler = doWhenStop
在所有的节点中我们都都可以给stophandler定义一个服务器关闭的处理方法。GlobalObject是个单例模式,翻看源码一看就懂。在这里其实什么事都没有做,由于涉及到
gfirefly的dbentrest的使用,所以后期再具体看看。
#coding:utf8 ''' Created on 2014-8-11 @author: [email protected] ''' from gfirefly.server.globalobject import GlobalObject, netserviceHandle """ net默认service是CommandService(文件server/server.py) netservice = services.CommandService("netservice") 所以通过'_'分隔命令号 参数选项是通过函数doDataReceived传过来的(文件netconnect/protoc.py) def doDataReceived(self,conn,commandID,data): '''数据到达时的处理''' response = self.service.callTarget(commandID,conn,data) return response """ @netserviceHandle def nethandle_100(_conn, data): """ conn是LiberateProtocol的实例(netconnect/protoc.py) """ print "handle_100:",data return "nethandle_100 ok" @netserviceHandle def nethandle_200(_conn, data): """200消息请求转发给gateserver处理 remote['gate']是RemoteObject的实例(distributed/node.py) """ return GlobalObject().remote['gate'].callRemote("gatehandle",data) @netserviceHandle def nethandle_300(_conn, data): """300消息请求转发给gateserver处理,gate再调用game1 remote['gate']是RemoteObject的实例(distributed/node.py) """ return GlobalObject().remote['gate'].callRemote("game1handle",data)
我们通过netserviceHandle装饰器定义net如何处理数据。gfirefly默认通过commandId发送请求,比如nethandle_100就是处理客户端commandID为100的请求。
这里net我们处理100,200,300请求。
100是net直接处理,也对应了我们前面所说的不需要其它server节点,只需要net节点。
200转发给gate处理,GlobalObject().remote是一个字典,其中有net的所有父节点的远程调用对象,我们看到的remote["gate"]其实就是config.json中net节点父节点的rootname。callRemote("gatehandle",data)也就是调用gate节点的gatehandle方法,传递参数data。看下面gateserver.py,你会发现有这个函数。
300也是转发给gate处理的,只不过gate会交给game1处理,看下面gateserver.py,你会发现game1handle这个函数。
gate分布式分发节点:gateserver.py:
#coding:utf8 ''' Created on 2014-8-11 @author: [email protected] ''' from gfirefly.server.globalobject import GlobalObject, rootserviceHandle @rootserviceHandle def gatehandle(data): print "gatehandle:",data return "gate ok" @rootserviceHandle def game1handle(data): print "gate forward to game1" return GlobalObject().root.callChild("game1","game1end",data)
我们通过rootserviceHandle定义gate节点处理的函数,因为gate是根节点,所以用rootserviceHandle很贴切。
gatehandle函数就是处理net发过来的200请求,gate直接自己处理,并将“gate ok"返回给net,net再发给client。
game1handle函数就是处理net的300请求,我们给子节点game1处理,GlobalObject().root保存了分布式根节点的实例,通过callChild调用孩子节点的方法。”game1"是孩子节点的名字,"game1end"就是调用孩子节点的game1end方法,data是传递的参数。
game1孩子节点:game1server.py:
#coding:utf8 ''' Created on 2014-8-11 @author: [email protected] ''' from gfirefly.server.globalobject import GlobalObject, remoteserviceHandle """ """ @remoteserviceHandle("gate") def game1end(data): print "game1end handle",data return "game1end ok"
因为game1是远程节点,所有通过remoteserviceHandle装饰器注册,参数“gate"就是父节点的名字,因为可能不止一个父节点,所以要通过名字来唯一确定。game1end处理的是net的300请求,到game1已经是叶子节点了,所以需要返回数据。
#coding:utf8 import time from socket import AF_INET,SOCK_STREAM,socket import struct HOST='localhost' PORT=1000 BUFSIZE=1024 ADDR=(HOST , PORT) client = socket(AF_INET,SOCK_STREAM) client.connect(ADDR) def sendData(sendstr,commandId): HEAD_0 = chr(0) HEAD_1 = chr(0) HEAD_2 = chr(0) HEAD_3 = chr(0) ProtoVersion = chr(0) ServerVersion = 0 sendstr = sendstr data = struct.pack('!sssss3I',HEAD_0,HEAD_1,HEAD_2,\ HEAD_3,ProtoVersion,ServerVersion,\ len(sendstr)+4,commandId) senddata = data+sendstr return senddata def resolveRecvdata(data): head = struct.unpack('!sssss3I',data[:17]) lenght = head[6] data = data[17:17+lenght] return data s1 = time.time() def start(): for commandId in (100,200,300): print "----------------" print "send commandId:",commandId client.sendall(sendData('asdfe',commandId)) print resolveRecvdata(client.recv(BUFSIZE)) start()
通过客户端,我们向net发送commandID分别为100,200,300的请求,然后打印返回结果。
启动客户端:
结果是完美的,我们看到gfirefly就是这么简单实现了分布式架构,通过装饰器定义各个节点如何处理数据,完全透明,我们可以完全不懂内部原理,只需要理解几个装饰器的作用,就可以完成复杂的分布式控制。
我想读到这里的你肯定既为gfirefly的强大和简单感到折服,心里肯定也很好奇这些到底是怎么实现的。
预知gfirefly原理,敬请期待[gfirefly深入解析]--gfirefly的基石gtwisted
本节代码:https://github.com/Skycrab/gfirefly-example