[笨木头FireFly 03]完整的服务端和客户端通信

#PS:

其实这篇文件是2013.10.12写完的,一直没发布,因为从那天起,我又跑回去折腾客户端的东西了(打算用Cocos2d-x3.0做下一个游戏),以及我的老游戏的维护和更新。总之各种借口(小若:不要这么直白好吧!你好歹也要说是‘原因’啊什么的,直接就说是‘借口’,是闹哪样!)

然后现在客户端网络的基础模块写好,要通信了,于是我又回来折腾FireFly了,祝我好运吧= =

然后今天看了一下官方的《视频教程3》,刚好里面介绍的部分内容和我这篇文件有点关系,来个互补好了~顺便吐槽一下,官方视频教程的视频,没声音倒还是能接受的,可是画质不是一般的模糊,我根本看不清,只能看文档和代码O_O!

#PS 结束

 

这几天真是各种坏事接踪而来,先是我的游戏玩家下载广告的概率太大,被广告平台判断为作弊,被坑了2周的收入。于是我下定决心,不再以广告作为重点盈利手段了。游戏做得好,他们说我作弊,游戏做得不好,赚不到钱= = 专心做网游吧

然后是VS2012环境出了问题,导致一系列问题出现

然后一直在调试FireFly的源码了解通信流程看得头晕(小若:这它喵的算哪门子的坏事啊?)

O_O!总之,我就是个不幸的人。

倾诉完毕,开始吧~

 

声明:

本教程基于FireFly1.2.2版本、Python2.7版本。

本教程面向PythonFireFly初学者中的初学者(比如我)

本教程由笨木头花心贡献,花心?不,是用心~

转载请注明原文地址: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 获取头部信息以外的数据内容

头部信息只是一些标注,最重要的是头部信息之后的内容,这是服务端和客户端通信,或者说交流的重要数据。比如登录请求,那用户名和密码就会包含在这部分数据里。

FireFlyJson来做通信协议结构(也不一定,反正我看的部分是Json),所以发送的数据都是字符串,这倒是挺好的,免去了大端小端处理的麻烦。

 

2.3 重点来了

看看dataHandleCoroutine函数里的下面三句代码:

d = self.factory.doDataReceived(self,command,request)

d.addCallback(self.safeToWriteData,command)

d.addErrback(DefferedErrorHandle)

如果大家没有忘记的话,协议工厂解析完数据后,就会交给Service服务来处理具体的逻辑。doDataReceived函数就专门做这件事情。

 

返回值d是一个很厉害的东西(twistedDeferred,有兴趣的可以百度一下,我掌握的程度不足以和大家讲解),我就是在这里调试了很久= = 不懂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


你可能感兴趣的:(通信,服务端,客户端,Firefly)