TCP 连接、粘包、及其常见应用

TCP

TCP 粘包是什么?有什么解决方案?尝试基于 TCP 设计一种解决粘包问题的双向块传输协议(Chunk Transport Protocol),并使用 TypeScript + NodeJS 实现一份使用 TCP 长连接的版本。

A:TCP 是一种面向字节流、具有可靠性、有序的、速度慢特性的协议。

TCP 粘包是一种传输过程中的现象,表现为发送方的后一包数据的头部接着前一包数据的尾部,称作TCP粘包;

为什么会出现这种现象?因为 TCP 是一种面向字节流的协议,发送方的数据可能被分割成多个份数据进行传输,IP数据包的负载大概为 1460 字节,当数据远远小于 1460 字节时,为了避免浪费网络 io,TCPNagle 算法开启时,当前一份数据小于 1460 字节时,此时会等待第二份数据包再进行发送,这就是TCP粘包。

当然,开启 Nagle 算法时,如果迟迟等不到第二份数据包,等待超时,一般为 200 ms,此时也会立刻发送这一份数据。

解决方案:

  • 固定包长的数据包:在发送方分包分片,不足的包长用特殊字符代替,在接收方重新组装包片;
  • 以指定字符(串)为包的结束标志,如换行符、空格符等;
  • 用包头和包体的格式,在包头说明包体有多大;

问题:

1.http 请求表单提交的时候如何区分每一段内容

通过 Content-type 来确定不同的数据传输方式;

Content-Type值 描述
application/x-www-form-urlencoded 在发送前编码所有字符(默认)
multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
text/plain 空格转换为 “+” 加号,但不对特殊字符编码。
 1. application/x-www-form-urlencoded 是原生表单的默认编码,提交表单进行抓包,内容为:

Content-Type: application/x-www-form-urlencoded;charset=utf-8 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

​ 提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST http://localhost/ HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 12
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: http://localhost
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7

user=1&pwd=2

2. multipart/form-data ,提示表单类型为 enctype=’multipart/form-data’时,进行抓包,内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
POST http://localhost/ HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 242
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: http://localhost
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryy3qoZZUB0X2oOsTg
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7

------WebKitFormBoundaryy3qoZZUB0X2oOsTg
Content-Disposition: form-data; name="user"

asdajlsd
------WebKitFormBoundaryy3qoZZUB0X2oOsTg
Content-Disposition: form-data; name="pwd"

asdasdkad
------WebKitFormBoundaryy3qoZZUB0X2oOsTg--

3.客户端如何判断请求所得到的响应数据已经接收完成 ?

  • Content-length: 大多是静态资源时,服务端知道资源的长度

  • Transfer-Encoding:请求动态资源时,服务端无法预先知道Content-length,就需要用 chunk 模式来传输数据。

    即如果要一边产生数据,一边发给客户端,服务器就需要使用”Transfer-Encoding: chunked”这样的方式来代替Content-Length。

    chunk编码将数据分成一块一块的发生。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用**回车换行(CRLF)**隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

    Chunk编码的格式如下:

    Chunked-Body = *chunk
    “0” CRLF
    footer
    CRLF
    chunk = chunk-size [ chunk-ext ] CRLF
    chunk-data CRLF

    hex-no-zero = <HEX excluding “0”>

    chunk-size = hex-no-zero *HEX
    chunk-ext = *( “;” chunk-ext-name [ “=” chunk-ext-value ] )
    chunk-ext-name = token
    chunk-ext-val = token | quoted-string
    chunk-data = chunk-size(OCTET)

    footer = *entity-header

    即Chunk编码由四部分组成:1、0至多个chunk块,2、**”0” CRLF,3、footer,4、CRLF****.**而每个chunk块由:chunk-size、chunk-ext(可选)、CRLF、chunk-data、CRLF组成。

常见 TCP 应用场景

  • 浏览器访问网页(HTTP/HTTPS)

  • 后端服务通信(API 调用)

  • 文件传输服务

  • 邮件服务