TCP 连接、粘包、及其常见应用
TCP
TCP 粘包是什么?有什么解决方案?尝试基于 TCP 设计一种解决粘包问题的双向块传输协议(Chunk Transport Protocol),并使用 TypeScript + NodeJS 实现一份使用 TCP 长连接的版本。
A:TCP 是一种面向字节流、具有可靠性、有序的、速度慢特性的协议。
TCP 粘包是一种传输过程中的现象,表现为发送方的后一包数据的头部接着前一包数据的尾部,称作TCP粘包;
为什么会出现这种现象?因为 TCP 是一种面向字节流的协议,发送方的数据可能被分割成多个份数据进行传输,IP数据包的负载大概为 1460 字节,当数据远远小于 1460 字节时,为了避免浪费网络 io,TCP
的 Nagle 算法开启时,当前一份数据小于 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 | POST http://localhost/ HTTP/1.1 |
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 CRLFhex-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 调用)
文件传输服务
邮件服务
…