前言今天是一个特殊的节日,1946年的情人节(原出版日期是2021.2.14),世界上第一台计算机ENIAC在美国宾夕法尼亚大学崭新,标志着一个新时代的到来。电脑已经陪伴人类75年了,所以今天,没有什么特别的,请多多陪伴自己的电脑,手动狗头,手动狗头。网络编程会是一个比较庞大的知识体系,第三章开始讲如何编码和解码。第一篇文章的数据结构提供给第三篇文章,然后通过第二篇文章的通道传输。复习作业第一篇针对第二篇TcppickleTcp网络流,处理过程也可以理解为字节流转二进制流,bytes就是第一篇提到的bytes。Encode压缩就是把一个字节流转成一个二进制流,那么Python是怎么做的呢。主要分为两种,pickle(python特有的序列化字节数组),struct(继承自C的序列化字节数组)库。让我们看看pickle是如何序列化数据的。defencode_pickle(packet:bytes):"""pushintopickle:parampacket::return:"""returnpickle.pack(">i",len(packet))+packet注意这里没有发包,复习一下之前的是的,发送包裹前需要建立socket链接并确认地址。pickle.pack(fmt,*args)的第一个参数fmt是一个很重要的知识点。我们先来了解一个最重要的概念,fmt。众所周知,Python在编程前不需要声明内存宽度。内存宽度是指在内存中占用空间的长度。在声明对象之前会标记数据类型(动态语言没有这个,很多静态语言都有golangvar和:=等自动推断声明)。网络编程需要学习这个是因为Binary的操作,如果详细的话,会标注为signed和unsigned。Signed和unsigned做一些原子计算。在做减法的时候,有些语言需要做一些特殊的处理。幸运的是,Python不需要担心这些。有符号一般是指包含负数。Short是有符号的2字节,unsignedshort是无符号的2字节。上面负数的图片需要背一下。网络自定义字节和字节序上面代码包含int和unsignedint,fmt前面是主机字节序,“>i”代表4个字节带bigendian符号,“<”代表littleendian,“!”代表不匹配。暂时先不考虑字节序转换的具体问题。目前网络编程都是模拟客户端向服务器端发送。字节顺序分别有大端和小端。程序内容以字节为单位,一个字节为8位,每个地址对应一个字节。big-endian模式是将数据的高位放在低位地址,低位地址在左边。转为十六进制只是为了人类阅读,int类型是4个字节,ff6c5a4s。小端倒过来就是大端,也就是4s5a6cff。从这里我们可以发现,大小端顺序只与内存寻址顺序有关,但内存地址中的6c和5a不会成为c6和a5的问题。如果前期不熟悉,可以通过询问需求来确认具体怎么写。数据类型除以8后的数为字节数,数为bit。以JS为例:buffer用于视图处理,比如Uint32Array,U代表unsigned。int32是4个字节,Array可以理解为数组。字节数组是多个字节,Python数据结构是bytes或bytearray(这个和前者的区别是内存不可变)。Uint64Array是8个字节?这是非法的。这里有定长和变长的概念。int属于定长,int宽度不能超过4个字节,所以不会有Uint64Array,但是可以有Uint16Array,16/8=2,占用2个内存字节。固定长度在具体例子中说明。变长是核心部分。我们来看下面两个问题:问题一:引用类型,比如指向对象的指针。如果操作对象,则对象的宽度不会改变。问题2:long类型,网络编程也是模拟客户端向服务器发送数据包,long类型也是变长的,比如不是8的倍数,而是9个字节。看看这9个字节是怎么写的。defencode_pack(packet:strorbytes,size:int,endian:str="big"):"""Packet:parampacket::paramsize:fixedlength:paramendian::return:"""returnint(packet).to_bytes(length=size,byteorder=endian)print(encode_pack("1256478912".encode("utf-8"),9))当然不推荐这种模式,更适合在fmt中拼接,例如Bigendian9bytes--,直接使用>9s即可。struct和pickle的本质是一样的,就像json和bjson和其他json一样,方法也是一样的。现在你可以把前面的串起来了。defencode_9_buffer(packet:bytes):"""将9个字节推入缓冲区:parampacket::return:"""returnstruct.pack(">9s",len(packet).to_bytes(length=9,byteorder="big"))+packetpg="helloworld".encode()print(struct.calcsize(">9s"))#length9print(encode_9_buffer(pg))#b'\x00\x00\x00\x00\x00\x00\x00\x00\x0bhelloworld'pickle和struct是混合的。前9是整个包的示例。这里先预览一下,后者的长度是可变的,也是游戏界最常用的。第四章会提到,本章其实可以满足互联网的一些TCP前端网络编程。使用pickle将4字节的big-endianhelloworld压入缓冲区,然后使用struct解包还原,验证正向和反向序列化是否兼容。所以用python写的后端如果用pickle,用struct也是可以的。defencode_pickle(packet:bytes):"""pushintopickle:parampacket::return:"""returnpickle.pack(">i",len(packet))+packetdefdecode_unpack(packet:bytes):"""这里的解包包不包含struct.unpack:parampacket:::return:"""#因为包头是4个字节,所以合法性验证是先切掉4个字节。如果len(packet)>=4:返回packet[4:].decode()如果__name__=='__main__':pg="helloworld".encode()packet=encode_pickle(pg)print(decode_unpack(packet))复习前面提到的知识点。一个数据包由header和body组成,header是4个字节(body的长度存放在里面)。简单的合法性判断就是检查包裹是否完整。包由2部分组成,第一部分是4字节,然后先判断是否大于等于4字节,然后解包先去掉headerSection中的4个字符,也就是包体的内容.包体内容的还原是decode()。让我们看看如何提取包头中的包体长度。struct.unpack(">i",packet[:4])其他内容可以参考第四篇。本文于2021.02.14首发于TesterHome社区。作者为资深游戏测试开发工程师陈子昂。用Python写的网络编程的文章有四篇,今天给大家分享的是第三篇。
