[gfirefly深入解析]--總體架構及demo講解


分類: python 96人閱讀 評論(0) 收藏 舉報

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:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. {  
  2. "master":{"rootport":9999,"webport":9998},  
  3. "servers":{  
  4. "gate":{"rootport":10000,"name":"gate","app":"app.gateserver"},  
  5. "dbfront":{"name":"dbfront","db":true,"mem":true,"app":"app.dbfrontserver"},  
  6. "net":{"netport":1000,"name":"net","remoteport":[{"rootport":10000,"rootname":"gate"}],"app":"app.netserver"},  
  7. "game1":{"remoteport":[{"rootport":10000,"rootname":"gate"}],"name":"game1","app":"app.game1server"}  
  8. },  
  9. "db":{  
  10. "host":"localhost",  
  11. "user":"root",  
  12. "passwd":"",  
  13. "port":3306,  
  14. "db":"anheisg",  
  15. "charset":"utf8"  
  16. },  
  17. "memcached":{  
  18. "urls":["127.0.0.1:11211"],  
  19. "hostname":"anheisg"  
  20. }  
  21. }  

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:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #coding:utf8  
  2. ''''' 
  3. Created on 2014-8-11 
  4.  
  5. @author: [email protected] 
  6. '''  
  7. from gfirefly.server.globalobject import GlobalObject  
  8.   
  9. def doWhenStop():  
  10.     """服務器關閉前的處理 
  11.     """  
  12.     print '############'  
  13.     print 'server stop'  
  14.   
  15. GlobalObject().stophandler = doWhenStop  

在所有的節點中我們都都可以給stophandler定義一個服務器關閉的處理方法。GlobalObject是個單例模式,翻看源碼一看就懂。在這裏其實什麼事都沒有做,由於涉及到

gfirefly的dbentrest的使用,所以後期再具體看看。


net前端監聽節點:netserver.py:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #coding:utf8  
  2. ''''' 
  3. Created on 2014-8-11 
  4.  
  5. @author: [email protected] 
  6. '''  
  7.   
  8. from gfirefly.server.globalobject import GlobalObject, netserviceHandle  
  9.   
  10.   
  11. """ 
  12. net默認service是CommandService(文件server/server.py) 
  13.     netservice = services.CommandService("netservice") 
  14. 所以通過'_'分隔命令號 
  15.  
  16. 參數選項是通過函數doDataReceived傳過來的(文件netconnect/protoc.py) 
  17.     def doDataReceived(self,conn,commandID,data): 
  18.         '''數據到達時的處理''' 
  19.         response = self.service.callTarget(commandID,conn,data) 
  20.         return response 
  21. """  
  22.  
  23. @netserviceHandle  
  24. def nethandle_100(_conn, data):  
  25.     """ 
  26.     conn是LiberateProtocol的實例(netconnect/protoc.py) 
  27.     """  
  28.     print "handle_100:",data  
  29.     return "nethandle_100 ok"  
  30.  
  31. @netserviceHandle  
  32. def nethandle_200(_conn, data):  
  33.     """200消息請求轉發給gateserver處理 
  34.     remote['gate']是RemoteObject的實例(distributed/node.py) 
  35.     """  
  36.     return GlobalObject().remote['gate'].callRemote("gatehandle",data)  
  37.  
  38. @netserviceHandle  
  39. def nethandle_300(_conn, data):  
  40.     """300消息請求轉發給gateserver處理,gate再調用game1 
  41.     remote['gate']是RemoteObject的實例(distributed/node.py) 
  42.     """  
  43.     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:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #coding:utf8  
  2. ''''' 
  3. Created on 2014-8-11 
  4.  
  5. @author: [email protected] 
  6. '''  
  7. from gfirefly.server.globalobject import GlobalObject, rootserviceHandle  
  8.  
  9. @rootserviceHandle  
  10. def gatehandle(data):  
  11.     print "gatehandle:",data  
  12.     return "gate ok"  
  13.  
  14. @rootserviceHandle  
  15. def game1handle(data):  
  16.     print "gate forward to game1"  
  17.     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:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #coding:utf8  
  2. ''''' 
  3. Created on 2014-8-11 
  4.  
  5. @author: [email protected] 
  6. '''  
  7. from gfirefly.server.globalobject import GlobalObject, remoteserviceHandle  
  8.   
  9. """ 
  10. """  
  11. @remoteserviceHandle("gate")  
  12. def game1end(data):  
  13.     print "game1end handle",data  
  14.     return "game1end ok"  

因爲game1是遠程節點,所有通過remoteserviceHandle裝飾器註冊,參數“gate"就是父節點的名字,因爲可能不止一個父節點,所以要通過名字來唯一確定。game1end處理的是net的300請求,到game1已經是葉子節點了,所以需要返回數據。


客戶端參數:clienttest.py:

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #coding:utf8  
  2.   
  3. import time  
  4.   
  5. from socket import AF_INET,SOCK_STREAM,socket  
  6. import struct  
  7. HOST='localhost'  
  8. PORT=1000  
  9. BUFSIZE=1024  
  10. ADDR=(HOST , PORT)  
  11. client = socket(AF_INET,SOCK_STREAM)  
  12. client.connect(ADDR)  
  13.   
  14. def sendData(sendstr,commandId):  
  15.     HEAD_0 = chr(0)  
  16.     HEAD_1 = chr(0)  
  17.     HEAD_2 = chr(0)  
  18.     HEAD_3 = chr(0)  
  19.     ProtoVersion = chr(0)  
  20.     ServerVersion = 0  
  21.     sendstr = sendstr  
  22.     data = struct.pack('!sssss3I',HEAD_0,HEAD_1,HEAD_2,\  
  23.                        HEAD_3,ProtoVersion,ServerVersion,\  
  24.                        len(sendstr)+4,commandId)  
  25.     senddata = data+sendstr  
  26.     return senddata  
  27.   
  28. def resolveRecvdata(data):  
  29.     head = struct.unpack('!sssss3I',data[:17])  
  30.     lenght = head[6]  
  31.     data = data[17:17+lenght]  
  32.     return data  
  33.   
  34. s1 = time.time()  
  35.   
  36. def start():  
  37.     for commandId in (100,200,300):  
  38.         print "----------------"  
  39.         print "send commandId:",commandId  
  40.         client.sendall(sendData('asdfe',commandId))  
  41.         print resolveRecvdata(client.recv(BUFSIZE))  
  42.   
  43. start()  

通過客戶端,我們向net發送commandID分別爲100,200,300的請求,然後打印返回結果。


啓動服務端:



啓動客戶端:



結果是完美的,我們看到gfirefly就是這麼簡單實現了分佈式架構,通過裝飾器定義各個節點如何處理數據,完全透明,我們可以完全不懂內部原理,只需要理解幾個裝飾器的作用,就可以完成複雜的分佈式控制。

我想讀到這裏的你肯定既爲gfirefly的強大和簡單感到折服,心裏肯定也很好奇這些到底是怎麼實現的。

預知gfirefly原理,敬請期待[gfirefly深入解析]--gfirefly的基石gtwisted

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章