TCP协议
一. TCP的设计目标
1. TCP与UDP的重要区别在于:
- TCP充分实现了数据传输时的各种控制功能: 可以在丢包时控制重发, 可以对顺序乱掉的分包进行顺序控制
- TCP是面向连接的协议: 只有在确认对方存在的情况下才会发送数据, 从而控制流量的浪费
2. TCP的设计目标
TCP设计的目的是, 保证数据报的可靠性传输. 因此需要考虑很多事情: 比如数据报的破坏, 丢包, 重复, 乱序到达等问题. 针对这些问题, TCP使用检验和(数据破坏), 序列号(乱序), 确认应答(可靠性), 重发控制, 连接管理和窗口移动等机制实现可靠性传输
二. TCP的一系列控制
1. 通过序列号和确认应答确保可靠性
-
什么叫确认应答
当发送端的数据到达接收端后, 接收端返回一个"已收到"的通知, 这个通知称为ACK.
如果一段时间内, 发送端仍没有接收到应答, 则认为数据报丢失并进行重发. 因此, 即使网络中发生丢包, TCP仍能保证数据最终能到达对端, 实现可靠传输 -
什么是序号
- 序号是按顺序给发送数据的每一个字节打上的编号, 序号会写在TCP首部中.
- 接收端查询收到的数据报中TCP首部的序列号和数据长度, 计算出自己下一步需要接受的序号, 并将这个序号作为ACK的一部分发送出去
- TCP的数据长度并未写入TCP首部, 而是通过计算得来的:
2. 重发的超时时间如何确定
重发超时时间之所以不能写死成一个固定值, 是因为不同网络环境中, 数据报在发送端和接收端之间往返的时间可能相差很大, 即使是相同的链路, 不同时段下数据报的往返时间也可能相差较大. 此时:
- 如果超时时间设置的太小, 会导致反复重发相同的数据报, 而这些数据报在一定时间后是可以被接收端正确接收的, 造成网络流量的浪费
- 若果设置的很大, 会导致网络通信能力下降
TCP要求无论在何种网络环境下都要提供高性能的通信.因此, 发送方在每次发包时都会计算数据报的往返时间(RTT:Rounf Trip Time)及其偏差. 将RTT和偏差相加, 重发时间就是比这个和大一点的值.
Unix和Windows中, 超时时间都是0.5秒的倍数. 不过由于最初不知道往返时间, 所以重发时间一般设置为6秒. 数据重发后如果在超时时间内接收不到应答, 则会再次重发该数据报, 且超时时间会以2倍, 4倍的指数函数延长; 此外, 如果重发次数到达一个阈值后还没有收到任何应答, 就会判断为网络或对端主机发生异常, 强制关闭连接并通知通信行为异常.
3. TCP连接管理
TCP是面向连接的, 即只有在确认对方存在的情况下才会发送数据. 因此, TCP通常使用3次握手和4次挥手来client端和server端建立连接和关闭连接.
- 建立连接 :
- 客户端发送SYN包, 请求连接
- 服务端发送ACK(对客户端SYN包的应答)和SYN包.
- 客户端返回ACK (对服务端的SYN包进行应答)
TCP中第一个发送SYN包的一方成为客户端, 接收的一方称为服务端
- 关闭连接:
- 客户端发送FIN包, 请求断开连接
- 服务端返回ACK, 对客户端发送的FIN做应答
- 服务端发送FIN包请求断开连接
- 客户端针对服务端的FIN做ACK应答
(1) 为什么关闭连接时, 服务端需要返回ACK后再发送FIN请求? 而不是像建立连接时一样, 把ACK和SYN一同发送.
因为关闭连接时, 客户端发出FIN时, 只说明客户端没有数据要传输给服务端了, 而服务端可能还有数据发送给客户端. 因此服务端先发送一个ACK应答表示自己受到了客户端的断开连接请求, 在玩送完剩余数据后, 服务端也发送FIN请求给客户端请求关闭
(2) 建立连接时, SYN包的作用? (数据报大小确认)
建立TCP连接时, 要确定以后数据报发送的大小, 这个大小要是在IP中不会再分片的最大长度:
这主要是考虑到避免数据报到网络层L3时, 由于过大被L3层的设备切片后传输. 由于L3是不可靠传输, 一旦发生切片, 如果某个切片发生丢失, L3层设备会重发所有分片的数据, 造成网络流量浪费. 而L3层分片的大小, 主要是由L2层设备决定的. 比如生活中使用最广泛的以太网(Ethernet, IEEE 802.3)的帧大小是1518字节, 根据Ethernet Frame的定义, L2 Frame由14字节Header和4字节Trailer组成, 所以L3层(也就是 IP 层)最多只能填充1500字节大小,这就是 MTU 的由来.当在MTU=1500的L3网络上传输时, MSS为1460(即1500-20字节IP头-20字节TCP头).
因此, 客户端在发送SYN时, 会在TCP首部中写入自己的MSS(maximum segment size), 告知对方自己的接口能够适应的MSS大小, 服务端返回的SYN中也会包含服务端的MSS大小. 然后再二者中选择一个较小的值投入使用. 因此TCP数据报的大小是以段(segment)为单位的
4. 利用滑动窗口发送segment-提高传输效率
(1) 发送一个应答一个的方式:
如果发送端1次发送一个段, 等待这个段被接收端应答后再发送第二个段, 这种传输方式的缺点在于, 数据报往返时间越长, 通信性能越低
(2) 滑动窗口的方式:
发送方主机发送了一个段后, 无需阻塞的等待这个段的应答, 而是继续发送段. 窗口大小: 指无需等待确认应答就可以继续发送段的个数. 等整个窗口的数据发送完毕后, 就要进入等待接收端ACK, 如果其中有部分段出现丢包, 那么发送端仍然要负责重传, 因此, 发送端要设置缓存保留这些待重传的段, 直到收到他们的确认应答
在收到ACK后, 窗口将滑动到ACK中的序号位置, 这样可以顺序的把多个段同时发送提高通信性能.
(3) 窗口中的数据段出现丢失怎么办?
- 首先, 考虑ACK应答丢失的情况
在没有使用滑动窗口时, 未收到ACK应答的数据需要被重传;
当时用滑动窗口后, 因为接收端只有在正确接收某个段后才会对该段返回ACK, 所以ACK确认的段之前的所有段都是以正确顺序被接收端接收的. 所以, 如果对第n段的ACK丢失, 而第n+1段的ACK正确抵达发送方, 则说明前面n段也已经正确到达接收方而不用重传
- 然后, 考虑窗口中某个数据段丢失的问题
由于接收方需要按顺序接收数据段, 一但中间某个序号的数据段丢失, 接收端在接收到后面序号的数据段后, 先保存这个数据段, 然后对该段返回ACK, 但ACK中的"下一个期望序号"写的是丢失的那个数据段的序号. 当发送端收到3个相同的ACK, 则证明"下一个期望序号"的数据段丢失, 会立刻重传. 这种重传机制往往比超时重传更加高效
fastretransmission.png
5. 流量控制
如果发送端发送数据报的速率, 超过了接收端缓冲区的大小, 则接收端只能抛弃这些发送来的数据, 而这又会出发重传机制, 造成网络流量的大量浪费. 因此, TCP提供了一种由接收方控制发送方发送速率的办法 - 通过接收方控制发送方发送窗口的大小
-
发送方会每个一段时间发送一个窗口探测包给接收方, 接收方将自己当前缓冲区的大小放在TCP首部中, 然后组成"窗口更新通知"返回给发送方, 发送方根据"窗口更新通知"调整自己的发送窗口大小
liuliajng.png
6. 拥塞控制
计算机网络处于一个共享环境下, 网络出现拥堵时, 入股突然发送个较大的数据, 极有可能导致整个网络瘫痪. 因此, TCP通过一个慢启动的算法对发送数据量进行控制:
- 首先定义一个拥塞窗口, 初始值为1. 每收到一个ACK, 拥塞窗口大小+1. 发送方发送数据时, 取拥塞窗口和接收端主机通知的窗口大小二者中较小的值作为滑动窗口大小.
- 不过, 随着数据报的往返, 为了提高传输效率, 拥塞窗口也会以1,2,4..的指数函数增长, 这会导致拥塞状况激增. 为了防止这些, TCP定义了拥塞窗口阈值的概念. 因此:
- 在未发生超时重传时, 拥塞窗口大小每次以2倍的速度递增
- 当发生超时重传后, 慢启动阈值定义为当前拥塞窗口大小的一半, 且新的拥塞窗口大小改为1. 此后, 若未发生超时重传, 拥塞窗口仍以2倍的速度递增, 递增到慢启动阈值后, 拥塞窗口大小每次+1;
-
当发生重复应答(3个相同的ACK确认)触发高速重发时, TCP认为这种情况下的拥塞情况比超时重传下的拥塞情况轻许多, 因此, 慢启动阈值改为当前拥塞窗口的一半, 并且将拥塞窗口的大小改为慢启动阈值的大小.
yongse.png
7. 几个提高网络利用率的规范
- 延迟ACK应答
因为TCP采用滑动窗口机制, 因此某几个ACK确认的丢失并不会造成这几个段的重传, 因此可以从接收方哪里减少ACK的发送, 只对当前正确收到的最大数据报序号做应答. 这样做的另一个好处是: 如果收到1个数据报就立刻应答, 由于接收方还没来的急处理数据, 返回的ACK中控制流量的窗口就会很小, 从而降低通信效率. 因此, 接收方往往采用延迟应答的机制 :- 收到2个数据报后再返回ACK应答
- 其他情况下, 延迟0.2秒发送ACK应答
-
Nagle算法
Nagle算法是一种发送方延迟发送数据报的策略. 它仅当下列2个条件至少有一个满足的情况下才能发送数据报:- 已发送的数据报已经全部收到ACK
- 数据报达到最大可发送长度MSS
Nagle的本意是提高网络利用率, 因为当数据很小时就被组成数据报发送的话, tcp头部所占的字节比例太高, 降低通信效率; 但如果接收方开启了延迟ACK应答的话, 由于ACK的延迟, 使得数据报小于MSS时, Nagle要等待全部ACK到达后再发送数据报, 使得发送延迟进一步增大. 因此, 对于低延迟的要求, 往往要关闭Nagle(例如:Xwindow).
java.net.Socket.setTcpNoDelay(True) // java中使用这个API关闭Nagle算法