#PS:
其实这篇文件是2013.10.12写完的,一直没发布,因为从那天起,我又跑回去折腾客户端的东西了(打算用Cocos2d-x3.0做下一个游戏),以及我的老游戏的维护和更新。总之各种借口(小若:不要这么直白好吧!你好歹也要说是‘原因’啊什么的,直接就说是‘借口’,是闹哪样!)。
然后现在客户端网络的基础模块写好,要通信了,于是我又回来折腾FireFly了,祝我好运吧= =
然后今天看了一下官方的《视频教程3》,刚好里面介绍的部分内容和我这篇文件有点关系,来个互补好了~顺便吐槽一下,官方视频教程的视频,没声音倒还是能接受的,可是…画质不是一般的模糊,我根本看不清,只能看文档和代码O_O!
#PS 结束
这几天真是各种坏事接踪而来,先是我的游戏玩家下载广告的概率太大,被广告平台判断为作弊,被坑了2周的收入。于是我下定决心,不再以广告作为重点盈利手段了。游戏做得好,他们说我作弊,游戏做得不好,赚不到钱= = 专心做网游吧
然后是VS2012环境出了问题,导致一系列问题出现…
然后一直在调试FireFly的源码了解通信流程看得头晕(小若:这它喵的算哪门子的坏事啊?)
O_O!总之,我就是个不幸的人。
倾诉完毕,开始吧~
声明:
本教程基于FireFly1.2.2版本、Python2.7版本。
本教程面向Python和FireFly初学者中的初学者(比如我)
本教程由笨木头花心贡献,花心?不,是用心~!
转载请注明原文地址:http://www.benmutou.com/blog/archives/735
之前两篇文章的介绍,仅仅是客户端和服务端的连接,以及客户端单方面发送数据给服务端,更严重的是,客户端用的是Python写的,所以,这次我们的客户端就…还是用Python写吧(小若:摔!)。
这次我们就来试试客户端发送数据给服务端,服务端再发回数据给客户端吧。
1. 服务端接收数据的浅层次的流程
这里我先介绍服务端,回忆上篇的介绍,我们客户端发送数据给服务端时,最先到达的是LiberateFactory协议工厂(当然,还有更先到达的,比如socket什么的,就忽略了)。
于是,从协议工厂开始,服务端接收数据的流程如下:
1)数据到达dataReceived函数(在FireFly源码的protol.py文件里)
2)数据继续传递给dataHandleCoroutine函数(依旧在FireFly源码的protol.py文件里)
好,就这样,本篇教程结束…(小若:这又发生什么事..你有解释了什么吗?)
2. 最重要的函数——dataHandleCoroutine
开玩笑的,我哪有这么坏,还没开始讲呢就结束。
接收数据时,在dataHandleCoroutine函数里做了很多重要的处理,我们一个个来看。
2.1 解析数据头部信息
协议头、服务器版本号那些头部信息,大家还记得吧?如果这些头部信息解析失败,或者版本不对,那就不会继续往下执行。
2.2 获取头部信息以外的数据内容
头部信息只是一些标注,最重要的是头部信息之后的内容,这是服务端和客户端通信,或者说交流的重要数据。比如登录请求,那用户名和密码就会包含在这部分数据里。
FireFly用Json来做通信协议结构(也不一定,反正我看的部分是Json),所以发送的数据都是字符串,这倒是挺好的,免去了大端小端处理的麻烦。
2.3 重点来了
看看dataHandleCoroutine函数里的下面三句代码:
d = self.factory.doDataReceived(self,command,request) d.addCallback(self.safeToWriteData,command) d.addErrback(DefferedErrorHandle)
如果大家没有忘记的话,协议工厂解析完数据后,就会交给Service服务来处理具体的逻辑。doDataReceived函数就专门做这件事情。
返回值d是一个很厉害的东西(twisted的Deferred,有兴趣的可以百度一下,我掌握的程度不足以和大家讲解),我就是在这里调试了很久= = 不懂Twisted的流氓就是特别不幸。我不打算介绍这个返回值d是什么,我只是说说,它能干什么。
doDataReceived里有可能执行的是同步操作,也有能看是异步操作,这个我们不管,总之最后会返回一个Deferred值(也就是变量d)。
Deferred对象有一个函数:addCallback。
doDataReceived很有可能执行了阻塞的操作。于是,我们可以让这些操作在非主线程里执行(至于如何在非主线程里执行,就不管了,函数里已经处理了)。
但是,什么时候执行完呢?没错,就是addCallback函数,指定一个回调函数,当函数执行完毕,返回结果时,就调用指定的回调函数(在这里是回调safeToWriteData函数)。
总之,目的就是避免服务端在接收并处理数据时发生阻塞。
有点乱,没关系,理理。
1)doDataReceived很有可能执行了阻塞的操作
2)那些阻塞的操作可以放到另外一个线程里,所以,不一定会立即返回结果
3)Deffered有个addCallback函数,这个函数的作用就是,当doDataReceived有返回结果时,就能调用addCallback绑定的回调函数,通知我们结果已经有了。
4)于是,safeToWriteData就会被调用。
5)addErrback是错误处理,当有错误发生时,就回调错误处理函数。
2.4 safeToWriteData函数
其实,说了这么多,safeToWriteData函数才是重点,前面说的那些都可以忽略。(小若:你喵大爷的= =)
在这个函数里,服务端将处理请求的结果打包,然后发送给客户端。但要注意,这里调用了一个callFromThread函数,这个函数就是让发送数据的操作回到主线程里执行。
2.5 服务端返回的数据在哪里添加?
还记得Service是处理客户端请求逻辑的吧?不同的命令码添加不同的处理函数进去,上一篇我们添加的处理函数是这个,再回忆一下:
def command_1(_conn, data): print '我跟你说,别以为你的命令码正确了,我只是不想让你错误而已~!' print data return "hello client"
这个是命令码为1的处理函数,我在最后添加了一条返回语句,这就是服务端最后返回给客户端的数据。我看暗黑世界的通信用的是Json,所以这里是字符串就很合理了。Json格式最终都以字符串发送,就不需要什么byte类型、int类型这些麻烦的东西了。
3. 服务端代码
好吧,看看服务端的代码,没有任何变化,和上一篇的一样,就只是在command_1函数里多了一条返回语句:
#coding:utf8 ''' Created on 2013-10-12 @author: 笨木头_钟迪龙 www.benmutou.com ''' import os import sys from firefly.netconnect.protoc import LiberateFactory from firefly.utils import services from twisted.internet import reactor from twisted.python import log if os.name!='nt':#对系统的类型的判断,如果不是NT系统的话使用epoll from twisted.internet import epollreactor epollreactor.install() def command_1(_conn, data): print '我跟你说,别以为你的命令码正确了,我只是不想让你错误而已~!' print data return "hello client" if __name__ == '__main__': # 有了它,就能看到日志的输出 log.startLogging(sys.stdout) # 服务,我个人理解为对客户端数据的逻辑处理 service = services.CommandService("testService") # 添加一个命令码处理 service.mapTarget(command_1) # 处理数据封装、协议头封装、分包、粘包处理的类 factory = LiberateFactory(); # 关于twisted的知识,暂时忽略吧,我也还没研究,是一个Python的网络框架 reactor = reactor #添加服务通道 factory.addServiceChannel(service) # 开始监听端口 reactor.listenTCP(1000, factory); reactor.run()
4.客户端代码
客户端也没有什么改动,我直接拿FireFly的小demo里的函数来测试了:
#coding:utf8 ''' Created on 2013-10-12 @author: 笨木头_钟迪龙 www.benmutou.com ''' from socket import AF_INET, SOCK_STREAM, socket import struct import time def sendData(sendstr, commandId): HEAD_0 = chr(0) # 协议头0 HEAD_1 = chr(0) # 协议头1 HEAD_2 = chr(0) # 协议头2 HEAD_3 = chr(0) # 协议头3 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] print data return data if __name__ == '__main__': HOST = "localhost" # 服务端地址 PORT = 1000 # 服务端端口 ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM) # 创建socket,TCP client.connect(ADDR) # 连接服务器 client.sendall(sendData('hello server', 1))# 发送数据给服务器 while True: try: buf = client.recv(2048) #接收数据 resolveRecvdata(buf); break except socket.error, e: print 'Error receiving data:%s' % e
客户端改了两个地方:
1) 新增了resolveRecvdata函数,这个就是直接拿FireFly的例子来用的,用来接收服务端回应的数据
2) 加了个while循环去读取socket数据,反正是测试的,就随便写了,会阻塞。
5. 测试一下
好了,先运行服务端,然后运行客户端,如果你能看到客户端输出以下的日志,那就恭喜你了:
hello client
6.唠叨唠叨
这篇文章写得有点乱,抱歉了,可能这几天脑子混乱了。
总之,熟悉Python以及各种Python第三方库的人学起FireFly来应该很轻松吧。像我这样的Python新手,很折腾。我每天都在等待FireFly出更多官方的教程,嘿嘿。
——2013.10.12 By mutou