TCP 编程(四)

151 阅读3分钟

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

前文我们设想了一些解决TCP粘包的办法,但是并没有去具体的实现,现在我们先来考虑下实现用最简单的方式来实现,我们使用一种固定长度并填充的方式,采取的编码方式是所谓的「Pascal 字符串 」(具体来说就是第一个字节存放它的长度,后续是字符串的数据部分,虽然我们不一定是字符串,但是由于它面向字节,姑且也可以算字符串)。具体来讲的话,把数据长度固定为256,比这还长的我们先不考虑。比这个短的数据我们则填充一些空字符。 然后我们可以用代码实现两个简单的工具函数,分别用于打包和解包。原来的程序中,我们则把读取缓冲区的长度改为 256 ,以适应这个数据发送方式。

def unpack(data): # 解析数据包
    # 注意此处必须
    return struct.unpack('256p', data)

def pack(data): # 取第 0 个是由于解包函数返回的是一个元组
    # 
    return struct.unpack('256p',data)[0]

这里借用了一下python中自带的 struct 标准库打包,其中的 256p 就是前述的所谓 Pascal 字符串了。值得注意的是,这里实现的两个工具函数的参数和返回值类型都是字节数组。如果需要使用其他的类型也可以使用这个 struct 标准库。它为 Python 中的数据到类 C 风格数据提供了格式字符用于转换。我们现在的网络编程有个值得注意的地方就是字节序,由于网络是跨平台的,因此它规定了使用大端法。struct 也能用于转换网络字节序,而且后续也会使用,顺便介绍一下。

回到程序中,这里姑且算是解决了粘包问题。现在我们考虑些别的问题,当不再粘包的时候,对于某一方来说更期望的是每个请求有对应的回应。而不是单单的传输数据。

先前的处理方式是有限制的。我们期待接受程序和发送程序之间的协作,但这会引入一些新的 IPC 问题。

考虑一个极端的方式,多线程,回到最初的设想请求和回复一起处理,这样它们会有共同的上下文。这样不需要考虑通信问题。那么对于多个请求,则开启多个线程处理,返回数据出于安全考虑则使用阻塞队列加锁统一管理数据的发送。

假设这样处理,会有一个异步的问题,每个处理不一定会按顺序完成,发送的消息不一定能按顺序达到,这也是问题。接收方接受消息将不能正确对应。

为了避免这个情况,一个可行的办法是为在消息的格式中额外添加一个消息编号。接收方可以额外实现一个处理函数,通过消息的编号把回复的数据映射到正确的地方。

不过实际上单个连接的请求有一些更好的处理办法,也就并不一定需要多线程来处理(多线程在 TCP 连接也有一些额外的用处,在这种情况下,一般而言不会推荐使用多线程)。