TCP 连接中,如何判断一段数据的起始和结束? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
amaranthf
V2EX    程序员

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

  •  
  •   amaranthf
    regomne 2016-01-08 22:42:37 +08:00 8623 次点击
    这是一个创建于 3564 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近用 nodejs 做一个代理,这个代理比较特殊的一点是,服务器和客户端之间只能建立一个 tcp 连接(因为建立连接的成本很高),而客户端上使用代理的程序(如浏览器)可能会尝试向本地代理 127.0.0.1:XXX 建立多个连接。

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

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

    1. 什么叫结束?

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

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

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


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

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

    在 Http 标准分为固定格式的 Header 与任意格式的 Body 构成。
    在 headr 中有定义 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 中,数据传输必须要有一个协议。这个协议至少要有一个信息,就是定义一个完整数据的长度。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2478 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 01:44 PVG 09:44 LAX 18:44 JFK 21:44
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86