#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/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函数里的下面三句代码:cce_[python]
d = self.factory.doDataReceived(self,command,request)
d.addCallback(self.safeToWriteData,command)
d.addErrback(DefferedErrorHandle)
[/cce_python]
如果大家没有忘记的话,协议工厂解析完数据后,就会交给
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是处理客户端请求逻辑的吧?不同的命令码添加不同的处理函数进去,上一篇我们添加的处理函数是这个,再回忆一下:[cce_python]
def command_1(_conn, data):
print '我跟你说,别以为你的命令码正确了,我只是不想让你错误而已~!'
print data
return "hello client"
[/cce_python]
这个是命令码为
1的处理函数,我在最后添加了一条返回语句,这就是服务端最后返回给客户端的数据。我看暗黑世界的通信用的是Json,所以这里是字符串就很合理了。Json格式最终都以字符串发送,就不需要什么byte类型、int类型这些麻烦的东西了。 3. 服务端代码好吧,看看服务端的代码,没有任何变化,和上一篇的一样,就只是在
command_1函数里多了一条返回语句:[cce_python]
#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()
[/cce_python]
4.客户端代码客户端也没有什么改动,我直接拿
FireFly的小demo里的函数来测试了:[cce_python]
#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
[/cce_python]
客户端改了两个地方:
1) 新增了resolveRecvdata
函数,这个就是直接拿FireFly的例子来用的,用来接收服务端回应的数据2) 加了个
while循环去读取socket数据,反正是测试的,就随便写了,会阻塞。 5. 测试一下好了,先运行服务端,然后运行客户端,如果你能看到客户端输出以下的日志,那就恭喜你了:
hello client
6.唠叨唠叨这篇文章写得有点乱,抱歉了,可能这几天脑子混乱了。
总之,熟悉
Python以及各种Python第三方库的人学起FireFly来应该很轻松吧。像我这样的Python新手,很折腾。我每天都在等待FireFly出更多官方的教程,嘿嘿。 ——2013.10.12 By mutou