笨木头  2013-10-08 23:18     其它     阅读(8671)     评论(5)
转载请注明,原文地址: http://www.benmutou.com/archives/727
文章来源:笨木头与游戏开发
好,经过上一篇不权威的讲解,大家已经能轻易地让客户端和服务端连接起来了。

 

但是,仅仅是连接了,可它们俩不说话不交流,那游戏就玩不起来了,玩不起来那我就赚不到钱..啊不是,玩不起来那玩家就不能开心了,钱是...啊不!玩家是最重要的嘛~不能让玩家不开心(小若:好好好,看出来了,钱是最重要的是吧)

 

好~!这次木头就和大家一起见证客户端和服务端的第一次交谈吧~!

 

声明:

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

本教程面向Python和FireFly初学者中的初学者(比如我)

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

转载请注明原文地址:http://www.benmutou.com/archives/727

 

1. Pythone struct模块

Struct模块主要是用来对数据进行打包和解包的,和LiberateFactory不一样,LiberateFactory已经说了,是协议工厂,当然就主要是对协议进行封装和解析。而struct是对更底层的数据操作,是把数据打包成二进制的形式,然后在网络中传输。解包也一样,把二进制形式的数据解包成Pythone需要或者说比较好识别的格式。

反正,总之,struct模块是对数据进行打包和解包的,解释完毕~

 

2. 可以发送请求的客户端(client.py)

我们要修改客户端,以便它可以发送数据给服务端。

[cce_python]
#coding:utf8

''' Created on 2013-10-8

@author: 笨木头_钟迪龙 www.benmutou.com '''

from socket import AF_INET, SOCK_STREAM, socket import struct 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

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:   pass

[/cce_python]
觉得复杂吗?其实就多了一个sendData函数而已。(小若:但是它很复杂!)
 

2.1 协议头部信息

我们先来解释一下协议头、协议头版本号、服务器版本号。我也没有深入了解,但就这么看,我唯一能想到的就是:这些东西是用来检测客户端和服务端是否同步的。

 

经过我“深入”FireFly源码之后,发现了确实有这么一个用途,当服务端的协议工厂接收到数据时,会先判断这些协议头和版本号是否正确,不正确的话,是不会往下继续执行的。

由于这是入门教程,就不一层层地贴这些代码了,也不继续深入了,因为它不是本文的重点。

 

重点是struct的pack函数,大家可以看看这篇文章:

http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html

只看第1、2点就暂时够用了。

 

于是,上面代码里的pack函数就是把4个协议头、协议头版本号、服务器版本号、发送的数据长度、命令码打包。

这样打包后的数据作为一个数据的头部信息,顾名思义,头部信息就是记录一次发送数据的主要信息,比如长度、版本、命令码。(小若:废话!上面都说了把这些东西打包了)

 

然后我们看看这句代码:senddata = data + sendstr。

为什么发送的数据字符串不需要参与打包呢?我也很白痴地试了一下把数据字符串也一起参与打包,结果是一样的。

于是,据我所知,字符串可以直接传输(字节流),不需要再进行什么打包了。

 

2.2 发送数据

客户端要发送数据给服务端很简单:client.sendall(sendData('hello server', 1))

这句代码的意思是,发送字符串‘hello server’给服务端,命令码是1。

结合之前说的,命令码1会参与到数据头部信息一起打包,而字符串’hello server’是直接和打包后的数据连接的,不需要参与打包。

 

2.3 为什么数据长度要+4?

不知道大家会不会有个疑问,就是打包的时候这个参数:len(sendstr) + 4

为什么长度要+4,木头我是弄不明白了,我查看了源码,在解析头部信息的时候,获取数据长度值时,又减去了4。这看起来有点多此一举,据我目前的研究,还没法知道原因,希望高手支招。

 

3. 可以接收请求的服务端(server.py)

服务端的改动也很小,看代码:
[cce_python]
#coding:utf8
'''
Created on 2013-10-8

@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

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]
最明显的是多了一个command_1函数,这个函数就是用来处理命令码为1的请求的,怎么处理?大家还记得service吧?Service就是用来处理这些请求逻辑的。

 

看看下面这两句新增的代码:

    # 服务,我个人理解为对客户端数据的逻辑处理

    service = services.CommandService("testService")

    # 添加一个命令码处理

    service.mapTarget(command_1)

 

首先,我们把Service改为了CommandService,这就说明了两个问题。第一,CommandService是专门用来处理命令码这种类型请求的服务;第二,Service是可以自定义的,这样就可以按我们的需要进行自定义处理,而CommandService就是其中一个自定义的服务。

 

然后,我们只要用service的mapTarget函数就能给service新增一个处理逻辑,在这里我们就说是添加了一个命令码的处理。

这里把函数命名为command_1是有讲究的,mapTarget会把下划线’_’后面的数值作为key值,函数对象作为value值,保存起来。当客户端发来请求时,就会根据命令码找到对应的函数对象。

 

怎么样?服务端就是这么处理的命令码的,很简单吧,具体的代码我就不贴了,因为本文是点到即止的入门教程。

 

4. 试验一下

大家现在就运行服务端,然后运行客户端,如果能看到类似以下的日志,那么就恭喜你了,你成功了:

2013-10-08 22:43:59+0800 [-] Log opened.

2013-10-08 22:43:59+0800 [-] LiberateFactory starting on 1000

2013-10-08 22:43:59+0800 [-] Starting factory <firefly.netconnect.protoc.LiberateFactory instance at 0x0000000002FEA588>

2013-10-08 22:44:02+0800 [firefly.netconnect.protoc.LiberateFactory] Client 0 login in.[127.0.0.1,20708]

2013-10-08 22:44:02+0800 [LiberateProtocol,0,127.0.0.1] call method command_1 on service[single]

2013-10-08 22:44:02+0800 [LiberateProtocol,0,127.0.0.1] 我跟你说,别以为你的命令码正确了,我只是不想让你错误而已~!

2013-10-08 22:44:02+0800 [LiberateProtocol,0,127.0.0.1] hello server

 

5. 再来串一遍

如果你是一个正常的、正宗的、不是假冒的初学者,那么,你现在一定是这样一种状态:代码运行成功了,结果也正确了,好像知道了是怎么回事了,但是又没办法把整个流程顺畅地回忆起来。

 

那就对了,我们把客户端发送数据到服务端接收并处理数据的流程再过一遍,这样就清楚了:

1) 客户端发送数据: client.sendall(sendData('hello server', 1))

2) 发送数据之前先把4个协议头、协议头版本号、服务器版本号、数据长度+4、命令码打包,然后再加上数据字符串’hello server’,整个要发送的数据就准备好了,然后发送,至于怎么发送的,我们不管,这是socket的事情。

3) 经过某些未知的步骤,我们的数据来到了LiberateFactory协议工厂,它负责解析数据,把命令码和数据内容拆分好,然后传给CommandService对象

4) CommandService根据命令码找到对应的函数,然后执行函数

5) 完成。

 

当然,也许中间有某些步骤被我不经意间忽略了(也许是故意的,也许是能力问题),不过,作为初步了解客户端和服务端的通信过程,也已经足够了。

 

再次声明,我也是刚接触FireFly和Python,请带着批判的眼光看待本文,本文为木头个人学习记录,仅供参考。

 
5 条评论
  • Fancy 2014-10-18 14:49:25

    为什么复制你的代码会显示出错0.0
    0回复
    • 博主 糟糕_树叶的mut 2014-10-19 08:19:48

      Python对格式的要求比较高,文章里的代码格式乱了,要注意if、函数定义之后的代码是要有缩进的~文章的格式调好了
      0回复
  • lang 2014-06-30 17:46:38

    +4是因为还有一个消息号的长度,消息号用来区别对应的指令。Int类型的。
    0回复
  • redcrow 2013-10-24 17:03:02

    希望前辈可以写写您学习游戏开发的历程,读过哪些书,学习的方法。。
    0回复
    • 博主 糟糕_树叶的mut 2013-10-27 11:54:05

      这个...可惜我没什么大成就,不好意思写
      0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发