Tcp 和 Udp的区别是什么?
Tcp是一个面向连接的、可靠的、基于字节流的传输层协议。
Udp是一个面向无连接的传输层协议。
Tcp的3大核心特性:
1、面向连接:Tcp需要三次握手建立连接
2、可靠性:有状态-Tcp会精准的记录哪些数据发送了,哪些数据被接受了,哪些数据没有被收到,而且保证数据包按序到达。可控制-当丢包或者网络环境不佳的时候,Tcp会根据具体的情况调整自己的行为,控制自己的发送速度或者重发。
3、面向字节流:Tcp为了维护状态,将一个个IP包变成了字节流。
Tcp的三次握手:
1、双方都处于closed状态。服务端打开端口监听进入listen状态。
2、客户端发起链接,发送SYN信号,进入SYN-SEND状态。
3、服务端收到SYN信号后,返回SYN和ACK信号,进入SYN-RCVD状态。
4、客户端收到ACK信号后,返回ACK信号,进入ESTABLISHED状态。
5、服务端收到ACK信号,进入ESTABLISHED状态。
从图上可以看到客户端发送SYN seq=x,在ACK ack=x+1并添加SYN seq=y,凡是需要对端确认的,一定消耗TCP报文的序列号。SYN需要对端的确认,而ACK不需要,因此SYN消耗一个序列号,而ACK不消耗。
三次握手的过程中,只有第三次可以携带数据,第三次握手的时候,客户端已经处于ESTABLISHED状态,并且能够确认服务器的状态,相对前两次而言安全,可以携带数据。
Tcp的四次挥手
1、双方处于ESTABLISHED状态,客户端发送FIN信号,进入FIN-WAIT-1状态。
2、服务端接收到FIN信号后,返回ACK信号,进入CLOSE-WAIT状态后发送ACK信号和FIN信号,并转为LAST-ACK信号。
3、客户端接收到ACK信号后进入FIN-WAIT-2状态,再接收到FIN+ACK信号后,进入TIME-WAIT状态,并返回ACK信号,等待2MSL(Maximum Segment Lifetime,报文最大生存时间,在这段时间内如果客户端没有收到服务端的重发请求,那么表示ACK成功,挥手结束,否则客户端重发ACK)后进入CLOSED状态。
4、服务端接收到ACK信号后CLOSED。
为什么要等待2MSL?
- 1个MSL确保四次挥手中主动关闭方最后的ACK报文最终能达到对端
- 1个MSL确保对端没有收到ACK重传的FIN报文可以到达
半连接队列:
当客户端发送SYN信号到服务端,服务端收到以后恢复ACK和SYN,状态有LISTEN变为SYN_RCVD,此时这个连接就被推入了SYN队列,也就是半连接队列。
全连接队列:
当客户端返回ACK后,服务端接收,三次握手完成。这个时候连接等待被具体的应用取走,在被取走之前,它会被退出另一个TCP维护的队列,也就是全连接队列。
SYN Flood攻击原理:
使用客户端在短时间内伪造大量不存在的IP地址,并向服务器端发送SYN。对服务端而言:
1、需要处理大量的SYN包并且返回对应的ACK,会出现大量处于SYN_RCVD状态的连接,从而占满整个半连接队列,导致无法处理正常请求。
2、由于IP是虚假的,服务端长时间收不到客户端的ACK,会一直尝试重新发送数据,直到耗尽资源。
如何应对:
1、增加半连接队列的容量。
2、减少SYN+ACK重试次数,避免大量的超时重发。
3、利用SYN Cookie技术,服务端接收到SYN后不立即分配链接资源,而是根据SYN计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK的时候带上Cookie值,服务端验证Cookie合法之后才分配连接资源。
Tcp报文头部的字段:
如何标识一个连接?
通过Tcp连接的四元组——源IP,源端口,目标IP,目的端口。
Tcp报文为什么没有源IP和目的IP?
在IP层已经对IP进行处理,Tcp只需要记录端口。
数据序号是本报文段第一个字节的序列号,是长为4个字节的无符号整数,表示范围为0~2^32-1,序列号在通信过程中有两个作用:
1、在SYN报文中交换彼此的初始序列号。
2、保证数据包按正确的顺序组装。
ISN即Initial Sequence Number(初始序列号),在三次握手的过程当中,双方会用过SYN报文来交换彼此的 ISN。
ISN 不是一个固定的值,而是每 4 ms 加一,溢出则回到 0,这个算法使得猜测 ISN 变得很困难。
确认序号即ACK(Acknowledgment number)。用来告知对方下一个期望接收的序列号,小于ACK的所有字节已经全部收到。
标记位有SYN,ACK,FIN,RST,PSH。
FIN:即 Finish,表示发送方准备断开连接。
RST:即 Reset,用来强制断开连接。
PSH:即 Push, 告知对方这些数据包收到后应该马上交给上层的应用,不能缓存。
窗口大小占用两个字节,也就是 16 位,但实际上是不够用的。因此 TCP 引入了窗口缩放的选项,作为窗口缩放的比例因子,这个比例因子的范围在 0 ~ 14,比例因子可以将窗口的值扩大为原来的 2 ^ n 次方。
校验和占用两个字节,防止传输过程中数据包有损坏,如果遇到校验和有差错的报文,TCP 直接丢弃之,等待重传。
可选项
常用的可选项有以下几个:
TimeStamp: TCP 时间戳,后面详细介绍。
MSS: 指的是 TCP 允许的从对方接收的最大报文段。
SACK: 选择确认选项。
Window Scale:窗口缩放选项。
Tcp快速打开的原理(TFO)
首轮三次握手(没有Cookie):
1、客户端发送SYN给服务端,服务端接收到。
2、服务端通过计算得到一个SYN Cookie,将这个Cookie放到 TCP 报文的 Fast Open选项中,返回给客户端。
3、客户端拿到这个 Cookie 的值并缓存。
4、正常完成三次握手。
后续三次握手(有Cookie):
1、客户端会将之前缓存的 Cookie、SYN 和HTTP请求发送给服务端。
2、服务端验证了 Cookie 的合法性,如果不合法直接丢弃;如果是合法的,那么就正常返回SYN + ACK并向客户端发 HTTP 响应。
3、正常完成三次握手。
TFO 的优势并不在之后的握手,在拿到客户端的 Cookie 并验证通过以后,可以直接返回 HTTP 响应,充分利用了1 个RTT(Round-Trip Time,往返时延)的时间提前进行数据传输,积累起来也是一个比较大的优势。
Tcp报文的时间戳
TimeStamp是 TCP 报文首部的一个可选项,一共占 10 个字节:
kind(1 字节) + length(1 字节) + info(8 个字节)
其中 kind = 8,length = 10,info 有两部分构成:TimeStamp和TimeStamp Echo,各占 4 个字节。
Tcp的时间戳主要解决两大问题:
- 计算往返时延 RTT(Round-Trip Time)
- 防止序列号的回绕问题
Tcp的超时重传时间
TCP 具有超时重传机制,即间隔一段时间没有等到数据包的回复时,重传这个数据包。这个重传间隔也叫做超时重传时间(Retransmission TimeOut, 简称RTO)。
Tcp的流量控制
对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。
而流量控制索要做的事情,就是在通过接收缓存区的大小,控制发送端的发送。如果对方的接收缓存区满了,就不能再继续发送了。
Tcp的拥塞控制
对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:
1、拥塞窗口(Congestion Window,cwnd)
2、慢启动阈值(Slow Start Threshold,ssthresh)
涉及到的算法有这几个:
1、慢启动
2、拥塞避免
3、快速重传和快速恢复
拥塞窗口(Congestion Window,cwnd)是指目前自己还能传输的数据量大小。
那么之前介绍了接收窗口的概念,两者有什么区别呢?
接收窗口(rwnd)是接收端给的限制(限制发送窗口的大小 = min(rwnd, cwnd))
拥塞窗口(cwnd)是发送端的限制
拥塞控制首先就是要采用一种保守的算法来慢慢地适应整个网路,这种算法叫慢启动。
1、三次握手,双方宣告自己的接收窗口大小。
2、双方初始化自己的拥塞窗口(cwnd)大小。
3、在开始传输的一段时间,发送端每收到一个 ACK,拥塞窗口大小加 1,也就是说,每经过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。它翻倍的阈值叫做慢启动阈值,超过这个值之后就需要拥塞避免了。
拥塞避免:
原来每收到一个 ACK,cwnd 加1,现在到达阈值了,cwnd 只能加这么一点: 1 / cwnd。那你仔细算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最后拥塞窗口的大小 cwnd 总共才增加 1。
也就是说,以前一个 RTT 下来,cwnd翻倍,现在cwnd只是增加 1 而已。
快速重传:
在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。
比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK。当发送端收到 3 个重复的 ACK 时,意识到丢包了,于是马上进行重传,不用等到一个 RTO 的时间到了才重传。
这就是快速重传,它解决的是是否需要重传的问题。
选择性重传
那你可能会问了,既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?
当然第 6、7 个都已经到达了,TCP 的设计者也不傻,已经传过去干嘛还要传?干脆记录一下哪些包到了,哪些没到,针对性地重传。
在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edge和right edge告知发送端已经收到了哪些区间的数据报。因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment),它解决的是如何重传的问题。
快速恢复
当然,发送端收到三次重复 ACK 之后,发现丢包,觉得现在的网络已经有些拥塞了,自己会进入快速恢复阶段。
在这个阶段,发送端如下改变:
1、拥塞阈值降低为 cwnd 的一半
2、cwnd 的大小变为拥塞阈值
3、cwnd 线性增加
Nagle 算法的规则如下:
1、当第一次发送数据时不用等待,就算是 1byte 的小包也立即发送
2、后面发送满足下面条件之一就可以发了:
3、数据包大小达到最大段大小(Max Segment Size, 即 MSS)
4、之前所有包的 ACK 都已接收到
延迟确认
试想这样一个场景,当我收到了发送端的一个包,然后在极短的时间内又接收到了第二个包,那我是一个个地回复,还是稍微等一下,把两个包的 ACK 合并后一起回复呢?
延迟确认(delayed ack)所做的事情,就是后者,稍稍延迟,然后合并 ACK,最后才回复给发送端。TCP 要求这个延迟的时延必须小于500ms,一般操作系统实现都不会超过200ms。
不过需要主要的是,有一些场景是不能延迟确认的,收到了就要马上回复:
1、接收到了大于一个 frame 的报文,且需要调整窗口大小
2、TCP 处于 quickack 模式(通过tcp_in_quickack_mode设置)
3、发现了乱序包
Nagle算法和延迟确认,前者意味着延迟发,后者意味着延迟接收,同时使用会造成更大的延迟,产生性能问题。
Tcp 的 keep-alive
Tcp的keep-alive的作用就是探测对端的连接有没有失效,通常是7200s检测一次。
站在应用的角度:
1、7200s 也就是两个小时检测一次,时间太长
2、时间再短一些,也难以体现其设计的初衷, 即检测长时间的死连接
所以大部分的应用并没有默认开启 TCP 的keep-alive选项。