面试官偶尔会问到TCP相关的知识点,在最早之前我是一脸懵逼答非所问的,故整理了一下关于TCP的相关知识点,希望对大家有所收获!
为了更进一步了解网络层面的知识,先晒出一张网络体系结构图,加深理解
计算机网络体系结构图
all.pngTCP UDP的区别
TCP | UDP |
---|---|
面向连接的协议。基于这种连接方式, 通信设备应在传输数据前建立连接,并应在传输数据后关闭连接 | 面向数据报的协议。意味着打开、维护、终止连接不会有开销。UDP对于广播和多播类型的网络传输是有效的 |
点对点通信,连接两端的socket | |
面向字节流。TCP把传输的各种数据当做无结构的字节流来用 | |
可靠性。它能够保证向目标路由器的数据传输 | 不可靠性。不能保证向目的地传送数据 |
错误检测机制。TCP提供了广泛的错误检查机制,这是因为它提供流量控制和数据确认。 | UDP只有使用校验和的基本错误检查机制 |
数据排序。数据包能够按照顺序到达接收器 | 没有数据排序。若有需求,则需要再应用程序层进行管理 |
速度较慢。相对UDP而言速度较慢。 | 快、简单、高效 |
重传机制。支持重传丢失的数据包 | 无重传机制 |
标头大小为20个字节 | 标头大小为8个字节 |
重量级 | 轻量级 |
用于HTTP,HTTP,FTP,SMTP和Telnet | 用于DNS,DHCP,TFTP,SNMP,RIP和VoIP |
注:本文所指的Client 均为发送方,Server为接收方
TCP 三次握手、四次挥手
三次握手
3-way-handshake_1.jpg建立一个TCP连接时,需要Client和Server总共发送3个包。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行
connect()
时。将触发三次握手。
-
Step 1(SYN).
Client 端要和Server端 建立连接,所以要发一个SYN(即同步序列号)的包,初始序号
x
,保存在包头的序列号(Sequence Number)字段里,指明打算连接的Service port,。用于告知Server:我(Client)可能要与你开始通讯了,现在发给你一个我(Client)启动段的序列号。
此时Client进入
SYN_SEND
状态 -
Step 2(SYN+ACK).
Server 端 接收到数据包(通知)后使用一个SYN-ACK信号位设置,来响应Client 端的请求。
即发送了自己的序列号(SVN),初始序号为
y
,和确认号(ACK,即Client发来的序列号递增1, 即x + 1
)。此时Server进入
SYN_RCVD
状态 -
Step 3(ACK).
Client接收到Server端的响应后
发送确认包(ACK,即Server 发来的序列号递增1, 即
y + 1
)来确认收到响应,此时Client 进入ESTABLISHED
状态,当Server 接收到该ACK包后,也进入ESTABLISHED
状态。
四次挥手
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake)。
需要四个包的原因是是因为TCP的半关闭引起的
客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行
close()
操作即可产生挥手操作。
下面假设Client主动发起挥手动作
4-way-connect-termination.jpg-
Step 1(FIN).
Client 端(发起方)要关闭TCP连接,所以要发一个FIN包,序号为
x
。发送完毕后,此时Client进入
FIN_WAIT_1
状态(此时表明无数据可发送,但仍可接受数据) -
Step 2(ACK).
当Server 端 接收到FIN包后,立即向Client发送确认包(即Client发来的FIN包的序号递增1,
x + 1
)。发送完毕后,此时Server 进入
CLOSE_WAIT
状态(此时表明接收到了Client的关闭,但还没做好“思想准备“关闭连接)当Client 端 接收到ACK包后,进入
FIN_WAIT_2
状态 -
Step 3(FIN).
Server 端 发送ACK包一段时间(这段时间它有一些关闭过程)后,开始发送FIN包,序号为
y
发送完毕后,此时Server 进入
LAST_ACK
状态 -
Step 4(ACK).
当Client 端 接收到FIN包,即关闭请求后,发送一个确认包(即Server发来的FIN包的序号递增1,
y + 1
)发送完毕后,此时Client 进入
TIME_WAIT
状态,目的在于在时间周期n内,允许Client 在发送的ACK包丢失的情况下重新发当Server 端接收到ACK包后,连接正式关闭,此时Server进入
CLOSED
状态。Client资源(包括端口号、缓冲区数据)都被释放当Client在时间周期n结束后,仍没收到Server 发的ACK包,则认为已正常关闭连接,此时Client 也进入
CLOSE
状态
TCP协议如何保证可靠传输
从上面的体系图可以看到,TCP(即运输层)的报文信息最终会交付到网际层。而网际层不会提供可靠的服务。所以还是要TCP来保证可靠的传输,才能最终保证数据服务的可靠
原理
宏观上看,从TCP的特性可以得知,它有自己的错误检测机制、数据按序传输、确认应答+序列号、支持重传的功能。然后具体的内部处理是怎样的呢?主要有以下两点
-
停止等待协议
这是最简单的保证可靠传输的协议
以下会发生两种情况
-
无差错
停止等待协助-无差错.jpg
可以看到Client 在发送分组M1(即数据单元)后,暂停,等到Server发回确认后,继续发送下一个分组...
这是理想条件下的无差错情况
-
有差错
停止等待协助-超时重传.jpg
-
当Client 发送 分组M1(会先设置一个计时器,在此计时器内M1仍存在,以便重传)时,可能会遇到数据无法到达Server,或者Server 检测出问题并丢弃了它,在指定时间内Client 如果未收到来自Server 的确认,则会重传M1,即人们常说的超时重传
超时重传会有以下情况
-
确认丢失(发回延迟)
Client发送分组M1,Server收到M1并发送确认分组,而在指定的时间内Client没有收到确认,后会重传M1.
而由于Server 已经收到过M1了, 所以此时它需要
丢弃M1分组, 发送确认分组
-
确认迟到(发送延迟)
由于网络延迟等原因,Client发送的分组M1,在指定时间后才到Server, 此时Client 还没来得及收到确认,再次发送分组M1
而由于Server 刚好收到了M1,所以此时它需要
丢弃M1分组, 发送确认分组
,Client
收到>=2个以上的确认,会执行丢弃操作,并且停止发送
-
连续ARQ协议
由于停止等待协议对信道的利用率太低,故可以采用流水线的方式来传输,即连续ARQ协议。
这里需要提到一个
发送窗口
的概念。发送窗口支持滑动,所以也有滑动窗口
这么一个概念 Client 会维护一个发送窗口,一个窗口内可以有多个连续分组进行发送,而不必等待对方的确认一条条分组发。
Server 亦不会对每个分组进行回传确认,而是在按需发送到达的最后一个分组到达之后,发送确认,代表这个窗口的分组已经发送成功
具体实现
1. 使用滑动窗口
窗口主要分为接收窗口和发送窗口
-
接收窗口
接收窗口.png
“接收窗口”大小取决于应用(比如说tomcat:8080端口的监听进程)、系统、硬件的限制。图中,接收窗口是31~50,大小为20。
在接收窗口中,黑色的表示已收到的数据,白色的表示未收到的数据。
当收到窗口左边的数据,如27,则丢弃,因为这部分已经交付给主机;
当收到窗口右边的数据,如52,则丢弃,因为还没轮到它;
当收到已收到的窗口中的数据,如32,丢弃;
当收到未收到的窗口中的数据,如35,缓存在窗口中。
- 发送窗口
发送窗口的大小swnd=min(rwnd,cwnd)。rwnd是接收窗口,cwnd用于拥塞控制,暂时可以理解swnd= rwnd =20。
图中分为四个区段,其中P1到P3是发送窗口。
tips:发送窗口以字节为单位。为了方便画图,图中展示得像以报文为单位一样。但这不影响理解。
2. 重传与确认
-
确认
这里主要是通过累计确认的方式
-
重传
这里主要是上面说的
超时重传
,每一个报文都会有超时计数器,当超过指定时间后,Client(发送方)会触发重传报文
3. 流量控制(基于滑动窗口)
流量即发送方发送的报文流量。当接收方来不及处理数据时,通过滑动窗口,告诉发送方能够接受的单位字节是多少,以降低发送的频率,防止包丢失
流量控制.png-
在建立连接时,接收方(B),告诉了发送方(A):
我的接收窗口是400(单位字节)
. -
图中的
ACK
为TCP首部的ACK字段,ack
为首部的确认号字段. -
流量控制体现在:
rwnd=300, rwnd=100, rwnd=0
.在确认报文的窗口字段设定了发送方能够发出的数据多少,从而控制流量.注意只有到首部的ACK
字段值为1,窗口字段的值才有效. -
假设在B发送了
rwnd=0
之后,过段时间由于自己又希望接收到数据,于是发出rwnd=400
的报文,但是该报文丢失了,这样A依然无法发送数据,B希望接收但接收不到数据.
为解决该问题,TCP为每个链接都设有一个持续计时器
.只要接收到对方窗口为0的通知,就启动持续计时器.在计时器到期后,就发送探测报文
,对方可以在该报文的确认中告知当前的窗口值.若窗口任然为0,那么就重新设定计时器,若不为0,那么上述的问题就解决了.
4. 拥塞控制
拥塞是指对网络某一资源(带宽,缓存等)的需求超过了可提供的部分,从而使网络中传送的数据不能按时到达,网络性能变差的情况.
拥塞控制就是防止过多的数据注入到网络中,这样网络中的资源压力就小了.
流量控制和拥塞控制似乎很相似,但是他们不同.前者立足于接收和发送者双方的情况;而后者注重的是数据量对网络环境的影响
TCP 粘包、拆包
由于TCP 是一个面向字节流的协议,这也决定了它的数据是无结构的。所以TCP无法得知应用层对于这快数据的定义,而是基于自身缓冲区的实际情况进行数据包的拆分,或者将多个数据包进行合并来发送。
参考下图,在不同的条件下,会发生多种现象
- Server 分别接收P1,P2,没有发生粘包、拆包
- Server 一次接收P1+P2两个报文,发生了粘包
- Server 先接收P2, 再分别接收了P1_1, P1_2,发生了拆包
- Server 先接收了P2+P1_2,再接收了P1_1,发生了粘包、拆包
- 另一种极端情况,当窗口非常小,恰逢P1又很大时,可能会发生多次对P1进行拆包
首先我们要知道,发送的数据会先传入发送缓冲区,再通过网络传输发送到接收端的缓冲区
以上现象发生的原因主要是
- 发送的字节 大于 TCP发送缓冲区的大小,会发生拆包
- 发送的报文 大于 MSS(最大报文长度),会发生拆包
- 发送的字节 小于 TCP发送缓冲区的大小,会将多次写入缓冲区的报文一并发送,即发生粘包
解决方案,需要上层应用程序做对应的处理
-
规定报文长度
。例如设定每条报文固定长度为200字节,当不够时,用空格填充 -
报文末尾添加回车换行符
。例如FTP协议 -
将报文分为header and body
,在头部中声明报文长度,然后根据这个长度来获取报文
我们常用的Netty 已经帮我们处理好这些问题,我们仅需调用特定的方法即可。这个在后续的Netty挖掘机系列文章会提到栗子。
比如有:
-
LineBasedFrameDecoder
基于换行符解决 -
DelimiterBasedFrameDecoder
基于分隔符解决 -
FixedLengthFrameDecoder
指定长度解决
参考链接:什么是 TCP 拆、粘包?如何解决?
网友评论