大家好,我是Leo。
之前聊了 体系结构,消息流程,TCP粘包 。今天接着粘包继续看看TCP与UDP的区别有哪些。
第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务端,客户端进入SYN_SENT状态,等待服务端确认。
第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务端,服务端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务端进入ESTABLISHED状态,完成三次握手,随后客户端与服务端之间可以开始传输数据了。
SYN_SENT:代表TCP连接的发起方第一次发给接受方的时候设置成的状态。
SYN_RECV:服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态。
ESTABLISHED:代表已经建立了连接
在断开连接之前客户端和服务器都处于ESTABLISHED状态,双方都可以主动断开连接,以客户端主动断开连接为优。
第一次挥手:客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。
也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。
第二次挥手:服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。
接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。
第三次握手:服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。
服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。
第四次握手:客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。
客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。
MSL: 报文最大生成时间,它是任何报文在网络上存在的最长时间,超过这个时间的报文将被丢弃。2MSL=60秒
TIME_WAIT:通过这个状态我从网上查了一些,只有主动关闭连接的,才会有TIME_WAIT状态。所以画图的时候画在了左面
这是由于TCP的 半关闭(half-close) 造成的。半关闭是指:TCP提供了连接的一方在结束它的发送后还能接受来自另一端数据的能力。通俗来说,就是不能发送数据,但是还可以接受数据。
TCP不允许连接处于半打开状态时,就单向传输数据,因此完成三次握手后才可以传输数据(第三握手可以携带数据)。
当连接处于半关闭状态时,TCP是允许单向传输数据的,也就是说服务器此时仍然可以向客户端发送数据,等服务器不再发送数据时,才会发送FIN报文段,同意现在关闭连接。
这一特性是由于 TCP双向通道互相独立所导致 的,也使得关闭连接必须经过四次握手。
这一块就是介绍一下客户端为什么要等待2MSL之后才进入CLOSE状态。
2MSL的时间是从 客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没 有传输到服务端,客户端⼜接收到了服务端重发的 FIN 报⽂,那么 2MSL 时间将重新计时。
第三次握手的ACK报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。服务器收到客户端的应答报⽂后,也进⼊ ESTABLISHED 状态。
TCP通过序列号与确认应答机制实现可靠的数据传输。当发送端将数据发出之后会等待接收端的确认应答。如果有确认应答,说明数据已经成功到达。如果没有确认应答,很有可能就丢包了。
在一定时间内,还没有收到确认应答包,就会进行重发。所以即使丢包,仍然能够保证数据可靠传输。
特定时间的间隔就是 主机A向主机B两次数据请求的间隔。
确认应答机制是双向的,所以主机A请求主机B可能出问题,主机B回传主机A也有可能出现丢包的情况,下面我们就用图解释一下。
超时主要由RTO 确认的,RTO代表往返时间,由下图解释。
解决方案:可以通过TCP建立连接时间的延时时间与参考。通过RTT+DelayACK+抖动时间的算法来计算,而不再需要取一个估计的最小值
半连接队列是SYN队列,代表服务端接收到客户端的请求后。
全连接队列是SYN+ACK(accept)队列,代表建立连接后。.
在客户端发起第一次连接时,服务端会将其加入到syn队列中,并且响应客户端syn+ack报文,等到客户端发送ack应答报文时,服务端将该连接从半连接队列中取出,并新建一个新的连接,加入到accept队列当中。等待进程调用accept请求时,将该连接取出来
不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。
这里也就是我们经常听说的SYN attack,如果一直对服务端发送syn包,但是不回ack回应包,这样就会使得服务端有大量请求处于syn_recv状态,这就是所谓的syn洪泛,syn attack,DDos attack
解决方案
2.开启tcp_syncookies:可以在不使用syn半连接队列的情况下建立连接syncookies在接收到客户端的syn报文时,计算出一个值,放到syn+ack报文中发出。当客户端返回ack报文时,取出该值验证,成功则建立连接
3.因为我们在收到syn attack时,服务端会重传syn+ack报文到最大次数,才会断开连接。针对syn attack的场景,我们可以减少ack+syn报文的重传次数,使处于syn_recv状态的它们更快断开连接 修改重传次数:/proc/sys/net/ipv4/tcp_synack_retries
当服务端的全连接队列过小时,容易发生全连接队列溢出。发生全连接队列溢出,后续的请求就会别丢弃。
Linux有个参数可以指定TCP全连接队列满了,会使用什么策略来回应客户端。
丢弃连接只是linux的默认行为,我们还可以向客户端发送RST报文终止连接,告诉客户端连接失败
解决方案
tcp_abort_on_overflow共有两个值分别是0和1
通常情况下设置为0更好,可以提高效率
当全连接队列溢出后,我们需要增大全连接队列的长度,以提高请求容量。
TCP 全连接队列的最大值取决于 somaxconn 和 backlog 之间的最小值,也就是 min(somaxconn, backlog),所以我们需要提高这两个参数的大小才能拿增大全连接队列
URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效。
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
FIN:释放一个连接。
16位窗口大小: 接收缓冲区剩余的空间大小
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP 首部, 也包含TCP数据部分.
16位紧急指针: 标识哪部分数据是紧急数据;
16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度;
如果校验出错,就会直接丢弃。
UDP数据报最大长度64K(包含UDP首部),如果数据长度超过64K就需要在应用层手动分包,UDP无法保证包序,需要在应用层进行编号。
这一篇从TCP与UDP的区别,逐一展开每一块技术。
下一篇将继续TCP与UDP的区别,展开聊一下窗口控制,重发控制,流量控制,拥塞控制。