-
程序基本結(jié)構(gòu)
由上圖可知趁冈,基本的程序結(jié)構(gòu)為:
master
gate
net
還有若干service模塊(也就是圖中的game1茧球,game2谨读,包括后面提到的admin...)
而連接這些模塊的則是twisted內(nèi)置的pb(透明代理協(xié)議)這部分的內(nèi)容雏搂,如果有興趣椒袍,可以從twisted的手冊上自行查看驼唱,這里就不做詳細(xì)分析,基本就是驹暑,連接一旦建立玫恳,雙方可以互相調(diào)用協(xié)議內(nèi)部商定的函數(shù)。
-
程序基本流程
暗黑世界版 firfly 基本程序流程如上所示
首先通過master分別模塊啟動
gate(網(wǎng)關(guān))优俘,db(數(shù)據(jù)庫相關(guān))京办,net(網(wǎng)絡(luò)),chat(聊天)帆焕,game(游戲邏輯) 模塊惭婿。
然后各個模塊分別調(diào)用initconfig進(jìn)行初始化并運(yùn)行。
-
模塊分析
在 暗黑世界 firefly 版中,程序主要分以下幾大模塊
1审孽、master 模塊(在分布式節(jié)點(diǎn)中也叫做master節(jié)點(diǎn)):
管理模塊
主要功能:
- 調(diào)用各個節(jié)點(diǎn)的 stop (模塊停止)
- 調(diào)用各個節(jié)點(diǎn)的 reload (模塊重載)
- 調(diào)用各個節(jié)點(diǎn)的 remoteconnect(模塊連接其他節(jié)點(diǎn))
該模塊同時啟動一個webserver
簡單的通過監(jiān)聽本機(jī)9998端口
管理員可以通過用get方法來獲取用戶管理命令
目前默認(rèn)的是2條命令县袱,stop和reload
負(fù)責(zé)其它模塊的stop,reload功能佑力。
示例:只要在本機(jī)瀏覽器輸入:http://localhost:9998/stop
或者
http://localhost:9998/reload即可式散。
2、dbfront:數(shù)據(jù)庫前端模塊
該模塊為獨(dú)立模塊打颤,本身和各個節(jié)點(diǎn)之間不進(jìn)行連接
負(fù)責(zé)管理DB和Memcache暴拄。
比如load用戶信息到memcache中
定期(系統(tǒng)寫死了1800秒)刷新并同步memcache.
4、net:網(wǎng)絡(luò)模塊(直面客戶端的節(jié)點(diǎn)编饺,負(fù)責(zé)與客戶端通信)
負(fù)責(zé)監(jiān)聽客戶端的網(wǎng)絡(luò)連接乖篷,將客戶端發(fā)送過來的數(shù)據(jù)驗證并解析成指令,然后將指令轉(zhuǎn)送至gate節(jié)點(diǎn)交于gate節(jié)點(diǎn)處理透且,并根據(jù)返回的數(shù)據(jù)回復(fù)客戶端撕蔼。
5、gate:網(wǎng)關(guān)節(jié)點(diǎn)(調(diào)度秽誊,分配各個節(jié)點(diǎn)工作的root節(jié)點(diǎn))
其它模塊(除了dbfront)都會和這個模塊掛接鲸沮。
- 將net節(jié)點(diǎn)解析好的數(shù)據(jù),通過游戲邏輯去調(diào)用 service 節(jié)點(diǎn)工作锅论,并將返回的數(shù)據(jù)返回給net節(jié)點(diǎn)讼溺。
5、game1(service節(jié)點(diǎn)最易,該節(jié)點(diǎn)一般為一個以上怒坯,布局在不同內(nèi)核,甚至不同主機(jī)中來均衡負(fù)載):
暗黑世界的游戲模塊
這個模塊主要提供所有的游戲邏輯計算服務(wù)
比如角色升級的經(jīng)驗等級藻懒,各種npc信息剔猿,各種掉落信息,各種戰(zhàn)斗陣型...
6嬉荆、admin:系統(tǒng)管理員模塊
其實這個模塊對于游戲本身來說艳馒,可有可無。
主要作用就是導(dǎo)出游戲統(tǒng)計數(shù) 據(jù)员寇,比如在線人數(shù)弄慰,每天充值數(shù)量等等。
總的來說蝶锋,這個模塊可有可無陆爽,并不重要。
-
配置信息
該應(yīng)用的所有配置都存放于config.json文件中扳缕,是整個游戲系統(tǒng)以及firefly模塊的核心之一慌闭,這個文件的配置直接決定了服務(wù)器以什么樣的方式運(yùn)行
//config.json
{
#master節(jié)點(diǎn)的運(yùn)行信息别威,包含host,port
"master":{
"roothost":"localhost",
"rootport":9999,
"webport":9998
},
#servers節(jié)點(diǎn)下運(yùn)行的就是真正工作的節(jié)點(diǎn)
"servers":{
#gate節(jié)點(diǎn)的配置信息
"gate":{
"rootport":10000, # gate節(jié)點(diǎn)的port
"name":"gate", # gate節(jié)點(diǎn)名字(分布式布局中驴剔,名字是辨別節(jié)點(diǎn)的重要標(biāo)志省古,不能重復(fù))
"db":true, # 是否使用數(shù)據(jù)庫
"mem":true, # 是否是用memcache
"app":"app.gateserver", # import 的 文件路徑
"log":"app/logs/gate.log" # 文件日志路徑
},
# 數(shù)據(jù)庫服務(wù)
"dbfront": {
"name":"dbfront",
"db":true,
"mem":true,
"app":"app.dbfrontserver",
"log":"app/logs/dbfront.log"},
# 網(wǎng)絡(luò)服務(wù)
"net":{
"netport":11009, #網(wǎng)絡(luò)運(yùn)行的端口
"name":"net", # 網(wǎng)絡(luò)節(jié)點(diǎn)的名稱
"remoteport":[{ # 網(wǎng)絡(luò)節(jié)點(diǎn)需要連接的遠(yuǎn)程節(jié)點(diǎn)信息
# 在這里沒有配置host信息,但是實際上pb協(xié)議是支持遠(yuǎn)程部署的丧失,但是firefly源碼上并不支持豺妓,如果需要支持遠(yuǎn)程部署,則需要自動動手修改firefly源碼
"rootport":10000, # 遠(yuǎn)程節(jié)點(diǎn)端口
"rootname":"gate" # 遠(yuǎn)程節(jié)點(diǎn)名稱
}],
"app":"app.netserver", # net 節(jié)點(diǎn)模塊 import 路徑
"log":"app/logs/net.log" # net 節(jié)點(diǎn)日志路徑
},
# game模塊
"game1":{
# 同理布讹,這里保存的是需要連接的遠(yuǎn)程gate對象信息
"remoteport":[{
"rootport":10000,
"rootname":"gate"
}],
"name":"game1", # 節(jié)點(diǎn)名稱
"db":true,
"mem":true,
"app":"app.gameserver",
"reload":"app.game.doreload", # 模塊重載的方法路徑
"log":"app/logs/game1.log"},
# admin節(jié)點(diǎn)信息
"admin":{
"remoteport":[{
"rootport":10000,
"rootname":"gate"
}],
"webport":2012,
"name":"admin",
"db":true,
"mem":true,
"app":"app.adminserver",
"log":"app/logs/admin.log"
}
},
# 數(shù)據(jù)庫配置信息
"db":{
"host":"localhost",
"user":"root",
"passwd":"111",
"port":3306,
"db":"anheisg",
"charset":"utf8"
},
# memcached 配置信息
"memcached":{
"urls":["127.0.0.1:11211"],
"hostname":"manman"
}
}
暗黑世界游戲源碼基本文件結(jié)構(gòu)
-app: 服務(wù)進(jìn)程
-gate
-dbfront
-net
-game
-admin(系統(tǒng)管理員模塊,對于框架本身沒有太大意義)
看完配置文件琳拭,我們了解了基本文件結(jié)構(gòu)后,我們再來看看軟件是怎么運(yùn)行的描验。
-
程序入口
#startmaster.py 文件
if __name__ == "__main__":
from master import Master
master = Master('config.json')
master.start()
從上面可以看出白嘁,軟件通過startmaster文件創(chuàng)建一個master對象
master對象內(nèi)通過start來啟動程序
-
master
既然是通過master開啟的服務(wù),那么我們就深入到master文件一探究竟
-master文件目錄如下:
- __init__.py
- master.py
- rootapp.py
- webapp.py
# 在firefly中膘流,每個節(jié)點(diǎn)下面都會有xxxapp命名的文件絮缅,該文件基本是用于提供該節(jié)點(diǎn)下服務(wù)的(有些用于給自己調(diào)用,有些給其他進(jìn)程調(diào)用呼股,這個后期會具體說到)
- 首先進(jìn)入的master.py 文件
# master.py
# coding:utf8
'''
Created on 2013-8-2
@author: lan (www.9miao.com)
'''
import subprocess,json,sys
from twisted.internet import reactor
from firefly.utils import services
from firefly.distributed.root import PBRoot,BilateralFactory
from firefly.server.globalobject import GlobalObject
from twisted.web import vhost
from firefly.web.delayrequest import DelaySite
from twisted.python import log
from firefly.server.logobj import loogoo
reactor = reactor
MULTI_SERVER_MODE = 1
SINGLE_SERVER_MODE = 2
MASTER_SERVER_MODE = 3
class Master:
"""
"""
def __init__(self):
"""
"""
self.configpath = None
self.mainpath = None
self.root = None
self.webroot = None
def config(self,configpath,mainpath):
"""
"""
self.configpath = configpath
self.mainpath = mainpath
def masterapp(self):
"""
"""
config = json.load(open(self.configpath,'r'))
GlobalObject().json_config = config #這里的GlobalObject()是一個單例模式的對象耕魄,如果對于這一開發(fā)模式不了解的同學(xué)可以直接把GlobalObject()當(dāng)做一個全局變量
mastercnf = config.get('master')
rootport = mastercnf.get('rootport')
webport = mastercnf.get('webport')
masterlog = mastercnf.get('log')
self.root = PBRoot() #創(chuàng)建分布式根節(jié)點(diǎn)
rootservice = services.Service("rootservice")#創(chuàng)建根節(jié)點(diǎn)服務(wù)
self.root.addServiceChannel(rootservice)#將服務(wù)添加進(jìn)入根節(jié)點(diǎn)
self.webroot = vhost.NameVirtualHost()# 創(chuàng)建web服務(wù)
self.webroot.addHost('0.0.0.0', './')#添加web服務(wù)器ip
GlobalObject().root = self.root #將節(jié)點(diǎn)信息放入全局變量中
GlobalObject().webroot = self.webroot #將web節(jié)點(diǎn)放入全局變量
if masterlog:
log.addObserver(loogoo(masterlog))#添加日志觀察者
log.startLogging(sys.stdout)
import webapp
import rootapp
reactor.listenTCP(webport, DelaySite(self.webroot)) #web服務(wù)監(jiān)聽端口
reactor.listenTCP(rootport, BilateralFactory(self.root)) #master服務(wù)監(jiān)聽端口
def start(self):
'''
'''
sys_args = sys.argv
if len(sys_args)>2 and sys_args[1] == "single":
server_name = sys_args[2]
if server_name == "master":
mode = MASTER_SERVER_MODE
else:
mode = SINGLE_SERVER_MODE
else:
mode = MULTI_SERVER_MODE
server_name = ""
if mode == MULTI_SERVER_MODE:
self.masterapp()
config = json.load(open(self.configpath,'r'))
sersconf = config.get('servers')
for sername in sersconf.keys():
cmds = 'python %s %s %s'%(self.mainpath,sername,self.configpath)
subprocess.Popen(cmds,shell=True)
reactor.run()
elif mode == SINGLE_SERVER_MODE:
config = json.load(open(self.configpath,'r'))
sername = server_name
cmds = 'python %s %s %s'%(self.mainpath,sername,self.configpath)
subprocess.Popen(cmds,shell=True)
else:
self.masterapp()
reactor.run()
通過master源碼可以看出,軟件的運(yùn)行順序
1 創(chuàng)建分布式根節(jié)點(diǎn)服務(wù)
2 創(chuàng)建masterweb服務(wù)
3 通過多進(jìn)程的調(diào)用python ***.py 的方式 直接運(yùn)行分布式服務(wù)
-
rootapp
#coding:utf8
'''
Created on 2013-8-7
@author: lan (www.9miao.com)
'''
from firefly.server.globalobject import GlobalObject
from twisted.python import log
def _doChildConnect(name,transport):
"""
當(dāng)server節(jié)點(diǎn)連接到master節(jié)點(diǎn)時觸發(fā)
"""
server_config = GlobalObject().json_config.get('servers',{}).get(name,{})# 獲取名為name的server配置信息
child_host = transport.broker.transport.client[0] # 保存連接進(jìn)來的子節(jié)點(diǎn)ip地址卖怜,若是服務(wù)器有固定ip屎开,則可直接寫入配置文件內(nèi)阐枣,無需保存
remoteport = server_config.get('remoteport',[]) # 獲取配置信息內(nèi)遠(yuǎn)程端口信息(一般為gate節(jié)點(diǎn)信息)
root_list = [rootport.get('rootname') for rootport in remoteport]# 從配置文件內(nèi)提取遠(yuǎn)程端口host信息
GlobalObject().remote_map[name] = {"host":child_host,"root_list":root_list}
# 保存root信息到 節(jié)點(diǎn)連接池中這是一個全局變量(這里實際是一個單例模式實現(xiàn)的全局變量)
# 到了這里马靠,我們目標(biāo)節(jié)點(diǎn)連接master節(jié)點(diǎn)部分已經(jīng)基本完成
# 但是我們看過前面框架圖的都知道,master節(jié)點(diǎn)本身不參與工作蔼两,所以無論是gate甩鳄,game,還是net節(jié)點(diǎn)额划,只連入master本身是無法讓軟件正常運(yùn)行起來的妙啃,所以有了下面那兩部分代碼
# GlobalObject().remote_map.items() 節(jié)點(diǎn)連接池,保存已經(jīng)連入的節(jié)點(diǎn)信息
# 從節(jié)點(diǎn)連接池中選擇需要連接本次連入節(jié)點(diǎn)的節(jié)點(diǎn)俊戳,告知他連接 root節(jié)點(diǎn)揖赴。
for servername,remote_list in GlobalObject().remote_map.items():
remote_host = remote_list.get("host","")
remote_name_host = remote_list.get("root_list","")
if name in remote_name_host:
GlobalObject().root.callChild(servername,"remote_connect",name,remote_host)
# 查看本次連入的節(jié)點(diǎn) 是否有需要連接的root節(jié)點(diǎn)存在于 節(jié)點(diǎn)連接池中,如果有抑胎,通知他連接該節(jié)點(diǎn)
master_node_list = GlobalObject().remote_map.keys()
for root_name in root_list:
if root_name in master_node_list:
root_host = GlobalObject().remote_map[root_name]['host']
GlobalObject().root.callChild(name,"remote_connect",root_name,root_host)
def _doChildLostConnect(childId):
"""
# 當(dāng)節(jié)點(diǎn)連接斷開時觸發(fā)
"""
try:
# 將節(jié)點(diǎn)移出節(jié)點(diǎn)連接池
del GlobalObject().remote_map[childId]
except Exception,e:
log.msg(str(e))
# 將以上方法綁定到 master 對象的 doChildConnect 以及doChildLostConnect 方法中
# GlobalObject().root 實際是一個工廠模型燥滑,他實現(xiàn)了兩個空方法
# doChildConnect
# doChildLostConnect
# 這兩個方法會在 連接產(chǎn)生,連接斷開時調(diào)用阿逃,單因為是空的铭拧,所以什么也不會做
# 所以需要在外部實現(xiàn)對應(yīng)的方法赃蛛,并替換工廠內(nèi)部的空方法,這樣搀菩,你的函數(shù)就會在指定時間內(nèi)觸發(fā)
GlobalObject().root.doChildConnect = _doChildConnect
GlobalObject().root.doChildLostConnect = _doChildLostConnect
這里的代碼看上去非常抽象呕臂,基本上可以理解成當(dāng) 子節(jié)點(diǎn)x 連接上master節(jié)點(diǎn)后,master節(jié)點(diǎn)通知子節(jié)點(diǎn)x連接他們需要連接的root節(jié)點(diǎn)肪跋,以及通知需要連接該 子節(jié)點(diǎn)x 的節(jié)點(diǎn)歧蒋,讓他們連接 子節(jié)點(diǎn)x ,這里有點(diǎn)像 爸爸到家了澎嚣,碰到了媽媽做好飯疏尿,媽媽通知爸爸叫爺爺來吃飯,同時讓兒子也來吃飯易桃。