-
程序基本结构
由上图可知,基本的程序结构为:
master
gate
net
还有若干service模块(也就是图中的game1,game2,包括后面提到的admin...)
而连接这些模块的则是twisted内置的pb(透明代理协议)这部分的内容,如果有兴趣,可以从twisted的手册上自行查看,这里就不做详细分析,基本就是,连接一旦建立,双方可以互相调用协议内部商定的函数。
-
程序基本流程
暗黑世界版 firfly 基本程序流程如上所示
首先通过master分别模块启动
gate(网关),db(数据库相关),net(网络),chat(聊天),game(游戏逻辑) 模块。
然后各个模块分别调用initconfig进行初始化并运行。
-
模块分析
在 暗黑世界 firefly 版中,程序主要分以下几大模块
1、master 模块(在分布式节点中也叫做master节点):
管理模块
主要功能:
- 调用各个节点的 stop (模块停止)
- 调用各个节点的 reload (模块重载)
- 调用各个节点的 remoteconnect(模块连接其他节点)
该模块同时启动一个webserver
简单的通过监听本机9998端口
管理员可以通过用get方法来获取用户管理命令
目前默认的是2条命令,stop和reload
负责其它模块的stop,reload功能。
示例:只要在本机浏览器输入:http://localhost:9998/stop
或者
http://localhost:9998/reload即可。
2、dbfront:数据库前端模块
该模块为独立模块,本身和各个节点之间不进行连接
负责管理DB和Memcache。
比如load用户信息到memcache中
定期(系统写死了1800秒)刷新并同步memcache.
4、net:网络模块(直面客户端的节点,负责与客户端通信)
负责监听客户端的网络连接,将客户端发送过来的数据验证并解析成指令,然后将指令转送至gate节点交于gate节点处理,并根据返回的数据回复客户端。
5、gate:网关节点(调度,分配各个节点工作的root节点)
其它模块(除了dbfront)都会和这个模块挂接。
- 将net节点解析好的数据,通过游戏逻辑去调用 service 节点工作,并将返回的数据返回给net节点。
5、game1(service节点,该节点一般为一个以上,布局在不同内核,甚至不同主机中来均衡负载):
暗黑世界的游戏模块
这个模块主要提供所有的游戏逻辑计算服务
比如角色升级的经验等级,各种npc信息,各种掉落信息,各种战斗阵型...
6、admin:系统管理员模块
其实这个模块对于游戏本身来说,可有可无。
主要作用就是导出游戏统计数 据,比如在线人数,每天充值数量等等。
总的来说,这个模块可有可无,并不重要。
-
配置信息
该应用的所有配置都存放于config.json文件中,是整个游戏系统以及firefly模块的核心之一,这个文件的配置直接决定了服务器以什么样的方式运行
//config.json
{
#master节点的运行信息,包含host,port
"master":{
"roothost":"localhost",
"rootport":9999,
"webport":9998
},
#servers节点下运行的就是真正工作的节点
"servers":{
#gate节点的配置信息
"gate":{
"rootport":10000, # gate节点的port
"name":"gate", # gate节点名字(分布式布局中,名字是辨别节点的重要标志,不能重复)
"db":true, # 是否使用数据库
"mem":true, # 是否是用memcache
"app":"app.gateserver", # import 的 文件路径
"log":"app/logs/gate.log" # 文件日志路径
},
# 数据库服务
"dbfront": {
"name":"dbfront",
"db":true,
"mem":true,
"app":"app.dbfrontserver",
"log":"app/logs/dbfront.log"},
# 网络服务
"net":{
"netport":11009, #网络运行的端口
"name":"net", # 网络节点的名称
"remoteport":[{ # 网络节点需要连接的远程节点信息
# 在这里没有配置host信息,但是实际上pb协议是支持远程部署的,但是firefly源码上并不支持,如果需要支持远程部署,则需要自动动手修改firefly源码
"rootport":10000, # 远程节点端口
"rootname":"gate" # 远程节点名称
}],
"app":"app.netserver", # net 节点模块 import 路径
"log":"app/logs/net.log" # net 节点日志路径
},
# game模块
"game1":{
# 同理,这里保存的是需要连接的远程gate对象信息
"remoteport":[{
"rootport":10000,
"rootname":"gate"
}],
"name":"game1", # 节点名称
"db":true,
"mem":true,
"app":"app.gameserver",
"reload":"app.game.doreload", # 模块重载的方法路径
"log":"app/logs/game1.log"},
# admin节点信息
"admin":{
"remoteport":[{
"rootport":10000,
"rootname":"gate"
}],
"webport":2012,
"name":"admin",
"db":true,
"mem":true,
"app":"app.adminserver",
"log":"app/logs/admin.log"
}
},
# 数据库配置信息
"db":{
"host":"localhost",
"user":"root",
"passwd":"111",
"port":3306,
"db":"anheisg",
"charset":"utf8"
},
# memcached 配置信息
"memcached":{
"urls":["127.0.0.1:11211"],
"hostname":"manman"
}
}
暗黑世界游戏源码基本文件结构
-app: 服务进程
-gate
-dbfront
-net
-game
-admin(系统管理员模块,对于框架本身没有太大意义)
看完配置文件,我们了解了基本文件结构后,我们再来看看软件是怎么运行的。
-
程序入口
#startmaster.py 文件
if __name__ == "__main__":
from master import Master
master = Master('config.json')
master.start()
从上面可以看出,软件通过startmaster文件创建一个master对象
master对象内通过start来启动程序
-
master
既然是通过master开启的服务,那么我们就深入到master文件一探究竟
-master文件目录如下:
- __init__.py
- master.py
- rootapp.py
- webapp.py
# 在firefly中,每个节点下面都会有xxxapp命名的文件,该文件基本是用于提供该节点下服务的(有些用于给自己调用,有些给其他进程调用,这个后期会具体说到)
- 首先进入的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()是一个单例模式的对象,如果对于这一开发模式不了解的同学可以直接把GlobalObject()当做一个全局变量
mastercnf = config.get('master')
rootport = mastercnf.get('rootport')
webport = mastercnf.get('webport')
masterlog = mastercnf.get('log')
self.root = PBRoot() #创建分布式根节点
rootservice = services.Service("rootservice")#创建根节点服务
self.root.addServiceChannel(rootservice)#将服务添加进入根节点
self.webroot = vhost.NameVirtualHost()# 创建web服务
self.webroot.addHost('0.0.0.0', './')#添加web服务器ip
GlobalObject().root = self.root #将节点信息放入全局变量中
GlobalObject().webroot = self.webroot #将web节点放入全局变量
if masterlog:
log.addObserver(loogoo(masterlog))#添加日志观察者
log.startLogging(sys.stdout)
import webapp
import rootapp
reactor.listenTCP(webport, DelaySite(self.webroot)) #web服务监听端口
reactor.listenTCP(rootport, BilateralFactory(self.root)) #master服务监听端口
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源码可以看出,软件的运行顺序
1 创建分布式根节点服务
2 创建masterweb服务
3 通过多进程的调用python ***.py 的方式 直接运行分布式服务
-
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):
"""
当server节点连接到master节点时触发
"""
server_config = GlobalObject().json_config.get('servers',{}).get(name,{})# 获取名为name的server配置信息
child_host = transport.broker.transport.client[0] # 保存连接进来的子节点ip地址,若是服务器有固定ip,则可直接写入配置文件内,无需保存
remoteport = server_config.get('remoteport',[]) # 获取配置信息内远程端口信息(一般为gate节点信息)
root_list = [rootport.get('rootname') for rootport in remoteport]# 从配置文件内提取远程端口host信息
GlobalObject().remote_map[name] = {"host":child_host,"root_list":root_list}
# 保存root信息到 节点连接池中这是一个全局变量(这里实际是一个单例模式实现的全局变量)
# 到了这里,我们目标节点连接master节点部分已经基本完成
# 但是我们看过前面框架图的都知道,master节点本身不参与工作,所以无论是gate,game,还是net节点,只连入master本身是无法让软件正常运行起来的,所以有了下面那两部分代码
# GlobalObject().remote_map.items() 节点连接池,保存已经连入的节点信息
# 从节点连接池中选择需要连接本次连入节点的节点,告知他连接 root节点。
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)
# 查看本次连入的节点 是否有需要连接的root节点存在于 节点连接池中,如果有,通知他连接该节点
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):
"""
# 当节点连接断开时触发
"""
try:
# 将节点移出节点连接池
del GlobalObject().remote_map[childId]
except Exception,e:
log.msg(str(e))
# 将以上方法绑定到 master 对象的 doChildConnect 以及doChildLostConnect 方法中
# GlobalObject().root 实际是一个工厂模型,他实现了两个空方法
# doChildConnect
# doChildLostConnect
# 这两个方法会在 连接产生,连接断开时调用,单因为是空的,所以什么也不会做
# 所以需要在外部实现对应的方法,并替换工厂内部的空方法,这样,你的函数就会在指定时间内触发
GlobalObject().root.doChildConnect = _doChildConnect
GlobalObject().root.doChildLostConnect = _doChildLostConnect
这里的代码看上去非常抽象,基本上可以理解成当 子节点x 连接上master节点后,master节点通知子节点x连接他们需要连接的root节点,以及通知需要连接该 子节点x 的节点,让他们连接 子节点x ,这里有点像 爸爸到家了,碰到了妈妈做好饭,妈妈通知爸爸叫爷爷来吃饭,同时让儿子也来吃饭。