Go 怎么避免连续读取过多字节(TCP)

2019-12-18 14:19:51 +08:00
 dying4death

简单地说,服务端将一段数据 AES 加密,客户端接收到数据后解密。

伪代码展示:

// 从输入流里读取加密过的数据,解密后把原数据放到 bs 里
func (secureSocket *conn) DecodeRead(bs []byte) (data []byte, err error) {

	n, err := secureSocket.Read(bs)
	if err != nil {
		fmt.Println("decode read: ", err)
		return
	}
	fmt.Println("decode read: ", bs)
	fmt.Println("decode read count: ", n)
	data, err = secureSocket.Cipher.decode(bs[:n])

	return
}

// 把放在 bs 里的数据加密后立即全部写入输出流
func (secureSocket *SecureTCPConn) EncodeWrite(bs []byte) (int, int, error) {
	
	data, err := secureSocket.Cipher.encode(bs)
	if err != nil {
		return
	}
	writedCount, err := secureSocket.Write(data)
	if err != nil {
		return 0, 0, err
	}
	return writedCount, len(data), nil
}

环境使用 Mac,本地测试一切正常。加解密以及数据转发等都能正常进行。然后将服务端代码打包成 linux 执行文件丢到 VPS 中运行,出现了客户端解密失败,发现是因为客户端读取数据时,读取了多段字节,导致无法解密。

比如说就是,服务端依次发送,[1 2 3],[4 5 6]两端加密后的字段,客户端读取了[1 2 3 4 5 6]或者其他可能,并不是读取[1 2 3],然后[4 5 6]这样。所以无法解密。

因为服务端也是转发的数据,客户端无法确知加密后完整的一段数据长度时多少?

8309 次点击
所在节点    Go 编程语言
54 条回复
dying4death
2019-12-18 14:22:59 +08:00
奇怪的地方在于,mac os 没有问题。把服务端代码打包成 darwin 后,放在同事的 macbook 上跑,也是正常的
yannxia
2019-12-18 14:32:46 +08:00
TCP 传输数据的时候本来就是这样的( TCP 没保证数据按照你输入的间隔发送,只是保证了顺序和输入的顺序一样),TCP 本身只是保证了数据传输的事情,你这个算协议的部分,一般解决之道都是定长,或者特定结束符之类的解决办法。
cheneydog
2019-12-18 14:33:25 +08:00
分包问题
zhs227
2019-12-18 14:34:51 +08:00
TCP 根本就不分段的,你需要自己定义一个边界,然后在边界上把包分开再解密
salamanderMH
2019-12-18 14:35:34 +08:00
TCP 是流式协议
zappos
2019-12-18 14:35:49 +08:00
请搜索 TCP 粘包问题。。
index90
2019-12-18 14:35:56 +08:00
TLV 了解下
dndx
2019-12-18 14:36:31 +08:00
TCP 本来就是流式的协议,没有包的概念。
ZRS
2019-12-18 14:36:47 +08:00
TCP 是一个数据流,你要自己定义包的概念再去切分
zappos
2019-12-18 14:39:46 +08:00
算了直接说吧,有两种方式,第一种就是像链路层那样添加分割符,比如 FFFF 当分隔符,那么数据里的 FF 就变成 FFFE 之类的。

第二种就是像 HTTP 那样,如果一个字段是不定长的,那么把长度写在前面。
monsterxx03
2019-12-18 14:41:51 +08:00
你需要自己定制协议,比如用 int16 来标记数据长度, 发送端: len(2 bytes int16) + data

读取端链接建立后, 先读 2 bytes, 转换成 int16, 你就之后 data 数据多长了, 继续读 data. 后续又是 len + data ...
bwangel
2019-12-18 14:42:54 +08:00
AzadCypress
2019-12-18 14:43:05 +08:00
tcp 是面向数据流的连接
你要从流中读取特定分段,可以:
使用转义字符分割,例如用"/0"作为数据结尾,并在发送的时候将原始数据中的 /替换为 //。这样每次读取到 /0 就可以将已经读取到的数据分离出来,然后将 //还原为 /就是原始数据
或者在开头的地方添加一个 2 字节的数据长度字段,每次先读取 2 字节,然后继续从流中读取一个确定的长度
shintendo
2019-12-18 14:43:52 +08:00
粘包警察密切关注此贴(狗头)
gamexg
2019-12-18 14:48:22 +08:00
使用 cipher.NewOFB + cipher.StreamWriter 等方式做加密,
加密时 [1 2 3],[4 5 6] 分开写加密,读取时 [1,2,3,4,5,6] 一次读取解密也能获得正确的明文。
dosmlp
2019-12-18 14:48:28 +08:00
TCP 是流式协议,并不保证你每次发多少字节就会每次收多少字节,这个要通过自己的分包协议实现
maichael
2019-12-18 14:50:11 +08:00
看到粘包两个字了,过一会怕是又干起来了。
fgodt
2019-12-18 14:50:32 +08:00
你需要自己定个简单的协议比如[前四字节 data 长度][data]
[0x00,0x00,0x00,0xff][0x00,0x01...]
接收端先读 4 字节长度,算出长度后再接收 0xff 个字节 这就是你需要解密的数据
xkeyideal
2019-12-18 15:05:53 +08:00
看一下 nsq 的源码: https://github.com/nsqio/nsq/blob/master/nsqd/protocol_v2.go#L54
楼上很多人都说了,TCP 是流式协议,需要自己定义协议格式,然后按照协议去读取并解析。
robot1
2019-12-18 15:19:35 +08:00
TCP 是流式协议

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/630159

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX