TCP 连接中,如何判断一段数据的起始和结束?

2016-01-08 22:42:37 +08:00
 amaranthf
最近用 nodejs 做一个代理,这个代理比较特殊的一点是,服务器和客户端之间只能建立一个 tcp 连接(因为建立连接的成本很高),而客户端上使用代理的程序(如浏览器)可能会尝试向本地代理 127.0.0.1:XXX 建立多个连接。

我考虑的一个解决方案是,客户端收到数据之后,在数据前面加上一个字段表示端口,发给服务器,服务器据此处理、收到数据后,也在数据前面加上端口并返回给客户端。

那么问题来了:
client.on('data',data=>{...});
我需要知道这段 data 是完整的,否则没办法提取发送时附带在“头部”的内容。
那么是不是说,我远端 write 一个 buffer 进来,这里的 data 就一定是收到完整的那一段 Buffer 呢?如果不是的话,一般是怎么判断我收到的数据已经完整了呢?
8055 次点击
所在节点    程序员
19 条回复
Strikeactor
2016-01-08 22:45:53 +08:00
用 HTTP 协议吧
jasontse
2016-01-08 23:01:44 +08:00
wikipedia 写的很明白了,包结构部分
https://en.wikipedia.org/wiki/Transmission_Control_Protocol
cmingxu
2016-01-08 23:12:52 +08:00
tcp 连接是 stream , 所以没有头和尾的概念, 一种做法是传输的包前加上此包的长度信息。另外就是特殊的结束符(得注意传输内容和结束符一致的情况)。
ryd994
2016-01-08 23:16:17 +08:00
带长度,每个数据段的开头先声明长度
你会有这个问题说明你对 TCP 的理解不对。对应用程序, TCP 连接就是无限长的数据流,直到关闭,网络上的数据包程序是看不见的。
而且你确定建立连接的成本有那么高?比 mutiplex 这些数据所需的各种头信息还要高?有没有考虑 fastopen ?有没有考虑 udp ?
easing
2016-01-08 23:37:14 +08:00
这是上层协议的事情,传输层不负责内容数据逻辑上的起始。你可以模拟 http 协议,要么携带长度信息,要么规定特殊字符作为起始标志。
amaranthf
2016-01-08 23:57:13 +08:00
@ryd994 因为我的代理服务器是在内网,而且不能开端口映射,所以必须由服务器主动连接到客户端,为此需要使用另一方的服务器进行中转才能建立连接。
znoodl
2016-01-09 00:07:20 +08:00
定义协议,比如最简单的前 4 个字节做长度,后面的长度是数据
mengskysama
2016-01-09 00:12:27 +08:00
自己实现好累的,你看这样行不行,在内网代理服务器搭一个代理,然后内网代理服务器通过 ssh 连接到公网服务器,并把自己的 S5 代理端口 ssh 映射到公网服务器上,公网服务器用 iptables 做个转发,这样一行代码都不用写。
amaranthf
2016-01-09 00:19:17 +08:00
@mengskysama 就是利用公网服务器做一个隧道呗,理论上是可以啦,不过我搞这个东西本来就是为了解决延迟问题,那台内网机器所在的网络非常快,我这儿 ping 过去只要 10ms ,再到外面的出口也是 10ms ,而通过公网服务器这么来回一中转……至少就 100 了……
mengskysama
2016-01-09 00:26:18 +08:00
@amaranthf 可能没明白你的要求,你意思是你(公网),那台服务器(内网),然后通过自己和那台服务器建立一个连接来访问公网资源?
amaranthf
2016-01-09 00:33:17 +08:00
@mengskysama 是这样……
我的机器 A ,内网机器 B
A 处于公网(实际上也是内网,不过路由这边可以做端口映射);
B 处于内网,且不能与公网直接通讯。
B 可以通过代理 P1 连接“外面的网络”,另外也可以通过代理 P2 连接正常的公网。 P1 和 P2 都属于 B 所在的内网,所以延迟可以忽略。另外最最重要的一点就是, P1 是开白名单的……而且速度稳稳碾压各种国外 vps 的直连……
就是这么个复杂的情况……
amaranthf
2016-01-09 00:40:23 +08:00
@mengskysama 我的设计是这样的,需要利用公网服务器 C 。
1. B 通过 P2 与 C 建立长连接
2. A 需要连接的时候,通知 C 自己的 ip 和端口
3. C 通知 B
4. B 通过 P2 连接 A
5. A-P2-B-P1-外面 建立这样的链路
mengskysama
2016-01-09 00:43:46 +08:00
@amaranthf
A 映射一个端口 2222
在 B 上开一个代理服务器听一个端口 0.0.0.0:3333
B 通过 P1 建立 ssh 连接到 A 的 2222 ,并且映射端口 3333 到 A 的 3334
这样。
mengskysama
2016-01-09 00:45:56 +08:00
@amaranthf 不需要 C 了,你直接 A 和 B 长连不就行了有个东西叫 Autossh 。。 IP 用 DDNS ,端口固定。
amaranthf
2016-01-09 00:48:20 +08:00
@mengskysama 呃,我是考虑做一个应用范围广一些的,比如我写个手机 app 就也能利用这套代理了……不过 ssh 隧道方面的东西确实没有仔细了解过,我查查相关资料,看怎么用那个简化一下吧,多谢啦
r00tt
2016-01-09 11:21:29 +08:00
tcp 流式的,没有包的概念,这种情况一般都是自定义数据包,可以设计定长,也可以设计非定长( Tag + Length + Body + CheckSum 类似这种拉~),可以用 flatbuffers ,protobuf 来实现比较好
hao123yinlong
2016-01-09 15:44:01 +08:00
/**
* 半包处理解码器
*
* 两端通信单向一个完整的数据包由 Length , Type , Message 三部分组成,固定占用 4 个字节的 Length 声明了 Message
* 的字节长度, Type 占用一个字节, Length 不包含 Type 所占一个字节 Message 是一个接口调用所需参数 protobuf 对象编码后的字节数组
*
* 当 Lenght int 值为 0 时,表示是心跳数据包,向客户端回复心跳数据包 ,心跳数据包 5 个字节
*
* | Length | Type | Message | | (4 bytes) | (1 bytes) | (n bytes) |
*
* @author yintengfei
*
*/
shyling
2016-01-09 20:56:46 +08:00
固定的结束符就可以了吧。。
每次 on('data')把 chunk 插到 Buffer[]后面,然后从前面开始读到结束符,读不到就是没结束。。
MiskoLee
2016-01-12 23:04:48 +08:00
这个问题,我们可以提炼一下。

1. 什么叫结束?

我们举个例子,对于手机来说,并不存在结束对话这个状态。只有接通,未接通,通话中,挂断等状态。因此,此时,手机网络无法获知与处理对话结束这个状态。那么,现实中,它是怎么工作的?

A : 你好!
B :你好!
A :....
B :.....
A :先这样吧,下次再聊!( A 尝试发起结束对话)
B : Bye !( B 确认结束通话)

然后 A , B 互相挂断电话。


同理, TCP 中并不存在所谓的数据接收完毕这种状态。因此,这种状态是我们人为附加上的,所以,需要我们人为的来处理这件事。

首先,我们来看看 HTTP 是怎么工作的。

在 Http 标准分为固定格式的 Header 与任意格式的 Body 构成。
在 header 中有定义 Content-Length 字段。

Http 的定义中,其实就包含了上述各位描述的:特定 chunk 符, DataLength 等技术手段。

首先是 Http Header

Http Header 就是一组固定格式的文本,内部通过特定符号完成断句。

从 Header 中读取到 Content-Length 字段,读取等长的 Body ,然后关闭 TCP 连接。

当 Content-Length 未读取成功的时候,则等待服务端断开连接。

我们在使用浏览器下载稍微大型的文件的时候,通常会遇到两种情况

1. 100KB/12.3M 预计 2 分钟后下载完毕
2. 已接收 100KB

原因,我们可以思考。

现在,我们再来看一个经典的 TCP 协议。 MYSQL 协议。

MYSQL 在 TCP 层,把数据封装成一个一个的 packet 。每个 packet 的信息非常少,我们可以粗略的理解为

|DataLegnth|DataBody|

读取每个 packet 的时候,首先读取 DataLength,接着读取 DataLength 长度的 DataBody.


综上所述。

在 TCP 中,数据传输必须要有一个协议。这个协议至少要有一个信息,就是定义一个完整数据的长度。

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

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

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

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

© 2021 V2EX