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