通用技术 用 Python 写网络编程(一)

陈子昂 · 2021年01月31日 · 最后由 FelixKang 回复于 2021年03月01日 · 2294 次阅读
本帖已被设为精华帖!

前言

游戏产业和互联网都是互通的,随着 Http2.0-未来版本,以及更多行业也有更多非 Http 请求的工作任务。

协议已经开始需要掌握:
http 镞,websocket ,基础 Tcp,基础 Udp
Rpc,webRtc,Tars,Kcp,ENet
序列化数据结构
Protobuf,Thrift,Json,xml

网络编程能力已经慢慢成为测试人员的一项必备能力了。
拿游戏产业来说可以有以下作用:
1.接口测试
2.压力测试
3.mock-造环境数据(需要附加 ssh 服务器,导入所有配置表,修改数据库)
4.mock-GM 工具(需要附加 Mock 和做一些类似数据库存储过程的功能)
5.平衡性测试(新手引导职业升级速度,抽卡,Pve 副本,Pvp 胜率,活动完成等)
6.战斗验证(帧同步,状态同步)前后端在开发语言有不少小细节。
7.日志文件同步
然而这块学习是有一定难度的,一部分原因是因为不能和自动化(随时找个网页和 app)和 http 接口(有教学接口),有练手场景。后面练习,需要自己写服务端才能练习。
还有一部分是需要网络知识,希望通过这个补全和掌握这块的知识空缺。

网络编程

Client to Server 客户端担任 send 发送 消息--->服务器
Server to Client 客户端担任 recv 接收 <----消息<----服务器
Server push Client 服务器主动推送给客户端的消息,比如小红点,比如一些夹带触发的消息,比如打怪升级了,服务器会 push 升级消息给 Client.

这个里面细节是很多的。比如 C 和 S 两端链接是走了 message queue,服务器 mq 按帧去保存,比如最多保存 500 帧,每 500 帧清除一次。比如中间有压缩 ,签名或者加密,顺序不能乱,比如内存对齐和补位。

这个里面发送的消息,消息是一个对象,对象有数据结构,这块是网络编程时最先需要学习。
在建立 socket 之后,socket 设置数据类型 ,必须设置基础的,Ipv4 还是 ipv6,数据流 (tcp) 还是数据包 (udp)
在建立链接也就是管道后,返回数据也有一些细节。
建立管道成功后,也就是开始发包,这个后面详细讲。

发送流程

Client-->(msg 原始数据容器 ) 缓存区-->(二进制) 网卡-->(二进制) 网关或者其他-->(二进制) 服务器
这里注意,一定要区分内存里数据长得样子(给机器使用得)和自己看得(排错用得,基本都是用 16 进制)
自己看得就不能当成 msg 原始形态,比如 C# toString("x2") 就是转 16 进制,但是发得时候你转 16 进制发就不正确了。
为什么不能那么转,需要了解下不同字符的长度,有些字符是会在二进制转换环节被自动翻译去除字符的 比如 "\x"。

msg 原始数据容器 分为以下几种类型:
容器在这里就是内存里面装 msg 的,容器不是基础数据类型
1.字符串 字符串没啥好说的,Python 不区分 char 和 string,在使用时非二进制的,这个在转到网卡那边时还是会翻译成二进制的。
2.字节 输出是 b 开头的,说明是 bytes,多个 byte 组成的,也是字节数组,字节也可以拼接成长的字节。字符串转字节是通过 encode() 转换。
比如 "hello world".encode() -->b'hello world'。这里做得就是通过编译器对字符串进行编码,进行编码时默认为 utf-8。utf-8 向下兼容 ascii 码。
3.bytearray bytes 都是一样,但是 bytearray 内存可变的。
内存可变和不可变怎么来区分呢,因为上面三种数据类型一定是迭代器,迭代器就是可以被遍历的。

t1 = "hello world"
t1[0] = "a"
t2 = "hello world".encode()
t2[0] = "a"
#上面这么写都是会抛错的,因为内存不可变

bytearray 可以使用 对象 [0] 去赋值。

t2 = bytearray([1,2,3])
t2[0] =4  
print(t2)  # \x04 就是因为t2[0]修改了,bytearray(b'\x04\x02\x03')

可变和不可变在网络编程里面也是有关系的,不可变的可以用于形参,可变的不推荐用形参会,调用函数会多拷贝一个副本。

网络处理

上面三种基础的数据类型,bytes 和 bytearray 在原始形态下是二进制,大部分情况下,只需要使用 bytes 就能当网络编程的容器了。
这里要做到 客户端把原始数据进行打包投递到网关。
这里有个概念是 服务器并不是客户端发什么都接收的,所有客户端需要知道服务器能接收什么数据类型(就是二进制对服务器来说会转换成什么)。
这里先不定义一个 socket 来回发,通过都是先要知道自己发过去的是对的。

下面选择用最常见的 bytes,题目为客户端需要发送 bytes 数据,每次不能超过 1450 个字节(最大传输单元),发送结果打印到控制台。
这里有个重要知识点,bytes 不是字节,是字节数组,一个 byte 占一个字节,基础数据类型 (部分引用数据类型) 在缓存区里面占不同的位数,所以可以理解为 bytes 的长度也就是这个字节数组的长度。

def session_bytes(datas: bytes,mtu:int=1450):
    """
    客户端需要发送bytes数据,网络字节序是大端
    每次最大传输单元1450个字节,超过就分成2段传
    byteorder 代表主机字节序,big是大端。
    .to_bytes(2,byteorder="big")的出现是因为字节数组是二进制的,所以在遍历时在内存通过ord()转换成了int。
    :return:
    """
    msg =[b"",b""]
    if len(datas) >= mtu:
        for i in datas[:mtu]:
            msg[0] += i.to_bytes(2,byteorder="big")
        for i in datas[mtu:]:
            msg[1] += i.to_bytes(2,byteorder="big")
    else:
        for i in datas:
            msg[0] += i.to_bytes(2,byteorder="big")
    return msg

Python 语法不解释,注意看注释。msg 是有序,网络传输在应用层都是有序的,在网络层自己重传和组合是网络层操作的,应用层 Python 是用不到的。
字节序决定了最高位字节在字节数组的开头还是结束,是指具体内容先传入的在左边还是在右边,左边就是开头,这种是 big 大端。
客户端本地显示是不重要的,默认本机的可以使用 print(sys.byteorder) 打印,上面例子设置字节序 big 为大端,是标记网络缓存区 - 网卡那层传输用的。字节序只要和目标端服务端一致就行。
Tips:就是把 mtu 传入时 bytes 数据类型数据给改短用于测试。

非基础数据类型

Json 这里就不用多讲了,Json 和内存打交道和文件打交道是分开的。内存打交道 json.loads() 和 json.dumps()

def json_to_bytes(json_data: dict) -> bytes:
    """
    Json转换字节数组
    :param json_data:  {"cat": "maomao"}
    :return: bytes
    """
    if isinstance(json_data, (str,dict)):  
        return json.dumps(json_data).encode("utf-8")

上面例子 Json 转成 bytes。

import xmltodict   #pip install xmltodict

def xml_to_bytes(xml_out_put: str)-> bytes:
    """
    xml转换字节数组
    :param xml_out_put:
    :return:
    """
    xml_data = xmltodict.parse(xml_out_put)
    data_json = json.dumps(xml_data,ensure_ascii=False)
    data_dict = json.loads(data_json)
    if isinstance(data_dict, (str, dict)):
        return json.dumps(data_dict).encode("utf-8")

xml 这种比较远古了,但是优点是带做个平级区域和坐标的概念。

网络编程温习

以上内容基本上面都有,练习到完全不用查网上就能写。
1.字符串和 bytes 和 bytearray 的练习,操作切片,同类型拼接,不满足类型转换,长度。
2.学习大端和遍历 bytes 时的转换。
3.Json 和内存打交道二个方法,xml 部分可以顺带理解下。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 10 条回复 时间 点赞

用心之作

恒温 将本帖设为了精华贴 02月01日 02:08
恒温 回复

感谢会持续创作的,后续帖子里面还会发布一些关于业余时间用 Python 游戏服务端的一些组件设计,配合 client 端调式。

陈子昂 回复

python 专家水平~

期待下一篇😀

请楼主多聊聊网络编程里面的
粘包的常见解决方案,
生产者消费者模型,
多路复用机制等
的话题。期待后续~~~

少年已老 回复

好的没问题,这些一点点从客户端往服务器发去展开。不过多路复用是在服务器端的,也会讲的。

苏立轩 回复

今天就会有。

陈子昂 用 Python 写网络编程(三) 中提及了此贴 02月14日 13:28
陈子昂 用 Python 写网络编程(三) 中提及了此贴 02月14日 13:28

顶 大猫师兄加油

顶 大猫师兄加油

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册