net顾名思义,就是网络模块,负责接受客户端的连接,处理客户端发送过来的数据,解包转发给其它模块。整个firefly系统里面,和用户打交道的也只有这个模块(admin和master虽然提供web操作接口,但是都是服务管理员的)。
我们前面提到,子模块的功能是由config.json来配置驱动的。那么我们看看这个模块的json文件定义了哪些功能。
20 "servers": {
21 "net": {
22 "app":"app.netserver", #具有游戏功能模块,在框架启动完毕后需要import app/netserver.py文件
23 "log":"app/logs/net.log", #日志
24 "name":"net", #该模块的名称
25 "netport":11009, #!!重要!! 定义了netport,则firefly会为其启动一个网络服务,来监听客户端连接。
26 "remoteport": [ #需要连接其它node,这里的其它node定义的是gate。实际上我们可以有不止一个“gate”来实现分布式逻辑。
27 {
28 "rootname":"gate",
29 "rootport":10000
30 }
31 ]
32 }
33 }
那么对应的启动代码如下:(init 和 start函数省略,只列出关键部分)
40 defconfig(self, config, dbconfig =None,memconfig =None,masterconf=None):
41 """配置服务器
42 """
43 netport= config.get('netport')#客户端连接
44 gatelist = config.get('remoteport',[])#remote节点配置列表
45 servername = config.get('name')#服务器名称
46 logpath = config.get('log')#日志
47 app = config.get('app')#入口模块名称
48 self.servername = servername
49
50 if masterconf:
51 masterport = masterconf.get('rootport')
52 addr = ('localhost', masterport)
53 leafnode = leafNode(servername)
54 serviceControl.initControl(leafnode.getServiceChannel())
55 leafnode.connect(addr)
56 GlobalObject().leafNode = leafnode
57
58 if netport:
59 self.netfactory = LiberateFactory()
60 netservice =services.CommandService("netservice")
61 self.netfactory.addServiceChannel(netservice)
62 reactor.listenTCP(netport,self.netfactory)
63 GlobalObject().netfactory =self.netfactory
64
65 for cnfin gatelist:
66 rname = cnf.get('rootname')
67 rport = cnf.get('rootport')
68 self.gates[rname] =leafNode(servername)
69 addr = ('localhost', rport)
70 self.gates[rname].connect(addr)
71
72 GlobalObject().remote = self.gates
73
74 if logpath:
75 log.addObserver(loogoo(logpath)) #日志处理
76 log.startLogging(sys.stdout)
77
78 if app:
79 reactor.callLater(0.1,__import__, app)
80
可以看出主要启动3个功能:
1、masterconfig部分,是说明本模块作为master模块的leafnode要连接master模块,这个和gate模块一样,可以参考前面PB的介绍章节。
2、连接gate模块,这里代码假定了不止一个需要连接的gate;但是实际上我们config.json里面只设定了一个。 这个步骤和连接master一样。
3、真正新鲜的东西是netport部分,这里 LiberateFactory实际上是protocol.ServerFactory的子类。它使用的协议是LiberateProtocol,这个是protocol.Protocol的子类。
如果大家了解twisted,那么我提到ServerFactory,和Protocol,大家就已经明白了netport部分的原理,压根不需要我废话。所以对于新人,不了解twisted,我们的主要任务是学习,并熟悉它。如下:
1、官网的介绍,这个比较简单,但是对于有经验的开发者来说,已经足够了解了(官网上面其它samples,也有不少是用protocol的)
ht空格t空格ps://twistedmatrix.com/documents/current/core/howto/servers.html
2、专门介绍twisted的书籍。有中文版的,内容我记得好像是比较浅显易懂。
h空格ttp://www.amazon.com/exec/obidos/ASIN/1449326110/jpcalsjou-20
第二章专门介绍protocol我就不多说了。
到这里我们假设大家已经熟悉twisted.protocol,那么我们看看firefly的实现部分。
大家知道twisted是事件驱动的,所以整个框架看起来很简单,我们即使对整体不熟悉,只要关注我们关心的事件处理函数部分也是ok的。
首先,
reactor.listenTCP(netport,self.netfactory)
这里建立一个服务,等待客户端connect。 每次接收到一个client连接,Factory都会调用 Factory.protocol 也就是 LiberateProtocol来处理,类似fork的概念。Factory充当了一个LiberatePtotocol的管理者的角色,干活的还是LibrateProtocol。所以我们主要关心LiberatePtotocol,代码如下:
class LiberateProtocol(protocol.Protocol):
"""协议"""
buff = ""
defconnectionMade(self):
"""连接建立处理
"""
log.msg('Client %dlogin in.[%s,%d]' % (self.transport.sessionno,
self.transport.client[0], self.transport.client[1]))
self.factory.connmanager.addConnection(self)
self.factory.doConnectionMade(self)
self.datahandler =self.dataHandleCoroutine()
self.datahandler.next()
defconnectionLost(self, reason):
"""连接断开处理
"""
log.msg('Client %dlogin out.'%(self.transport.sessionno))
self.factory.doConnectionLost(self)
self.factory.connmanager.dropConnectionByID(self.transport.sessionno)
defsafeToWriteData(self,data,command):
"""线程安全的向客户端发送数据
@param data: str 要向客户端写的数据
"""
if notself.transport.connected or data is None:
return
senddata =self.factory.produceResult(data,command)
reactor.callFromThread(self.transport.write,senddata)
defdataHandleCoroutine(self):
"""
"""
length =self.factory.dataprotocl.getHeadLenght()#获取协议头的长度
while True:
data = yield
self.buff += data
whileself.buff.__len__() >= length:
unpackdata= self.factory.dataprotocl.unpack(self.buff[:length])
if notunpackdata.get('result'):
log.msg('illegal data package --')
self.transport.loseConnection()
break
command =unpackdata.get('command')
rlength =unpackdata.get('lenght')
request =self.buff[length:length+rlength]
if request.__len__() < rlength:
log.msg('some data lose')
break
self.buff= self.buff[length+rlength:]
d =self.factory.doDataReceived(self, command, request)
if not d:
continue
d.addCallback(self.safeToWriteData, command)
d.addErrback(DefferedErrorHandle)
def dataReceived(self,data):
"""数据到达处理
@param data: str 客户端传送过来的数据
"""
self.datahandler.send(data)
可以看到主要处理了3个事件。
connectionMade #客户端连接我们
connectionLost(self, reason) #连接丢失
dataReceived(self, data) #数据到达
当客户端连接的时候,我们会调用Factory的connectmanager(也是简单的一个管理类)来保存当前连接信息。
整个系统只有一个Factory类的实例,但是每当一个连接来的时候,都会fork一个Protocol的实例。所以信息都会保存在Factory的属性里面,比如Factory.connectionManager。
同理,当连接丢失的时候,我们调用connectionManager处理一下即可。没有什么复杂的。
我们主要关注的是数据到来的处理逻辑:
1、数据到达,我们简单的发送给处理函数: self.datahandler.send(data)
2、这个处理函数包含了yield 语句所以它是一个生成器,generate。具体大家可以google python yield,会有很详细的介绍文档。
这里只是告诉大家
2.1、datahandler函数,每次调用到yield的地方就会暂停,等待下次被next,或者send唤醒,然后从yield的地方继续执行。
2.2、data = yield,这条语句在被唤醒后 data就被赋值为 send传递进来的参数。
2.3 那么这个函数改写成这样,大家就很容易理解了:
def dataHandleCoroutine(self,sendData): ########改动地方
"""
"""
length =self.factory.dataprotocl.getHeadLenght()#获取协议头的长度
data = sendData ########改动地方
self.buff +=data
whileself.buff.__len__() >= length:
unpackdata= self.factory.dataprotocl.unpack(self.buff[:length])
if notunpackdata.get('result'):
log.msg('illegal data package --')
self.transport.loseConnection()
break
command =unpackdata.get('command')
rlength =unpackdata.get('lenght')
request =self.buff[length:length+rlength]
ifrequest.__len__() < rlength:
log.msg('some data lose')
break
self.buff= self.buff[length+rlength:]
d = self.factory.doDataReceived(self, command,request)
if not d:
return ########改动地方
d.addCallback(self.safeToWriteData, command)
d.addErrback(DefferedErrorHandle)
3、剩下了就简单了,利用python struct库来解包数据,
ud =struct.unpack('!sssss3I',dpack)
然后调用自己的services来处理解包后的command和参数。至于如何处理命令,就要看这个services挂什么样的处理函数了。
启动流程介绍完毕,下面就是app里面,暗黑游戏部分的具体处理内容了。
调用流程和前面一样,我们直接到关键函数:
defloadModule():
netapp.initNetApp()
import gatenodeapp
这里有些代码冗余和重复,但是没有什么太大影响,我们不提了。
这个函数的第一行代码是我改动过的(原先是import+修饰符方式),大家跟进去看,其实就是初始化一个services的子类:NetCommandService,然后给它挂上Forwarding_0 这个处理函数。
而我们仔细看NetCommandServices发现它override了原先的callTargetSingle,主要是这句改动:
target = self.getTarget(0)
说明不论啥命令过来,它都写死了调用Forwarding_0来处理。
而Forwarding_0这个函数就一句话,功能是转发前面解析的command和参数给gate模块。
OK到这里结合前面的gate模块,我们应该就可以理出一条client登录的主线。
0、暗黑客户端一启动,就会连接net模块,net模块建立一个对应的connection,并由connectionManager保存。
1、暗黑客户端发送用户名、密码和登陆命令号101: (会封包,这里忽略)
Json::FastWriter writer;
Json::Valueperson;
person["username"]=userName;
person["password"]=password;
std::string json_file=writer.write(person);
CCLog("%s",json_file.c_str());
SocketManager::getInstance()->sendMessage(json_file.c_str(),101);
2、net模块接收并解析出命令号 101,参数{name:xxx,pwd:xxx}。然后调用自己的services(实际是NetCommandServices)处理。
3、NetCommandServices,不管37 21,直接写死调用Forwarding_0函数。
4、Fowarding_0函数写死了,用命令fowarding转发给gate模块。
4、gate模块收到它leafNode的调用请求,所以调用自己作为root的services。而这个services在gate/app/gate/rootservice/rservices.py里面注册了forwarding函数,所以就调用它。
5、md,fowarding函数发现这个命令号注册在loacalservices里面,见
gate/app/gate/localservice/lservices.py中:
def init():
initLocalService()
addToLocalService(loginToServer_101) ##############这里
addToLocalService(activeNewPlayer_102)
addToLocalService(roleLogin_103)
于是调用loginToServer_101来处理这个命令
6、这个命令就不展开了,很简单,就是取数据,比对是否匹配,check是否有效。然后逐层返回,最后送给客户端
对于登录部分,流程压根没有跑到db、game1和admin模块,所以我们可以直接利用master,gate,net这3个模块做测试
顺序启动
master
gate
net
然后启动tool/clienttest.py
可以看到net模块的终端会有信息打印。
我自己也写了一个测试程序。已经放在github上面,tool/ clientTestLogin.py;
用的是twisted,很简单。如下:
1 from twisted.internet.protocolimport Protocol, ClientFactory
2
3 classEcho(Protocol):
4 defconnectionMade(self):
5 a= 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n'
6 self.transport.write(a)
7
8 defdataReceived(self, data):
9 print data
10 #self.transport.loseConnection()
11
12
13 classEchoClientFactory(ClientFactory):
14 defstartedConnecting(self, connector):
15 print'Started toconnect.'
16
17 defbuildProtocol(self, addr):
18 print'Connected.'
19 return Echo()
20
21 defclientConnectionLost(self, connector,reason):
22 print'Lostconnection. Reason:', reason
23
24 defclientConnectionFailed(self, connector,reason):
25 print'Connectionfailed. Reason:', reason
26
27 from twisted.internetimport reactor
28 reactor.connectTCP("localhost",11009,EchoClientFactory())
29 reactor.run()
很简单,就30行代码,而且都是框架 。就是在连接上server后,事件connectionMade里面往server写一条命令。这条命令是我直接copy暗黑客户端的打印,就是一条封装好的数据包。
5 a= 'N%&0\t\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00e{"password":"chenee","username":"chenee"}\n'
运行后结果如下:
bash-3.2$ pythonclientTestLogin.py
Started to connect.
Connected.
N%&0 We{"data": {"userId":1915, "characterId": 1000001, "hasRole": true},"result": true}
可以看出,server成功处理,并且返回我们登录后的角色信息。
OK,这章介绍结束,下面介绍game1模块。