本文转载自微信公众号「牧小农」,作者牧小农。转载本文请联系牧小农公众号。
一、传输控制协议TCP简介
1.1 简介
TCP(Transmission Control Protocol) 传输控制协议,是一种 面向连接的、可靠的、基于字节流的传输层 通信协议。
TCP是一种面向连接(连接导向)的、可靠的基于字节流的传输层通信协议。TCP将用户数据打包成报文段,它发送后启动一个定时器,另一端收到的数据进行确认、对失序的数据重新排序、丢弃重复数据。
TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种端点我们叫作套接字(socket),将端口号拼接到IP地址即构成了套接字,例如 192.1.1.6:50030
1.2 特点
面向连接的、可靠的、基于字节流的 传输层 通信协议
将应用层的数据流分割成文段并发送给目标节点的TCP层
数据包都有序号,对方收到则发送ACK确认,未收到则重传
使用校验和来检验数据在传输过程中是否有误
二、TCP报文头
1、源端口(Source Port)/ 目的端口(Destination Port):他们各占2个字节,标示该段报文来自哪里(源端口)以及要传给哪个上层协议或应用程序(目的端口)。进行tcp通信时,一般client是通过系统自动选择的临时端口号,而服务器一般是使用知名服务端口号或者自己指定的端口号 (比如DNS协议对应端口53,HTTP协议对应80)
2、序号(Sequence Number):占据四个字节,TCP是面向字节流的,TCP连接中传送的字节流中的每个字节都按顺序编号,例如如一段报文的序号字段值是 107,而携带的数据共有 100个字段,如果有下一个报文过来,那么序号就从 207(100+107)开始,整个要传送的字节流的起始序号必须要在连接建立时设置。首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号
3、确认序号(Acknowledgment Number):4个字节,是期望收到对方下一个报文段的第一个数据字节的序号,若确认号=N,则表明:到序号N-1为止的所有数据都已正确收到,例如:B收到A发送过来的报文,其序列号字段是 301,而数据长度是 200字节,这表明了B正确的收到了A到序号 500(301+200-1)为止的数据,因此B希望收到A的下一个数据序号是 501,于是B在发送给A的确认报文段中,会把ACK确认号设置为 501
4、数据偏移(Offset):4个字节。指出TCP报文段的数据起始处距离报文段的起始处有多远,这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。单位是32位字,也就是4字节,4位二进制最大表示15,所以数据偏移也就是TCP首部最大60字节
5、保留(Reserved):6个字节。保留域
6、TCP Flags:控制位,由八个标志位组成,每个标志位表示控制的功能,我们主要来介绍TCP Flags中常用的六个,
- URG(紧急指针标志):当 URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序在主机上运行。但后来发现了一些问题,需要取消该程序的运行。因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收TCP的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了许多时间
- ACK(确认序号标志):当 ACK=1时确认号字段有效。当 ACK=0时,确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置1
- PSH(push标志):当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后向上交付
- RST(重置连接标志):TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接,可以用来拒绝一个非法的报文段或拒绝打开一个连接
- SYN(同步序号,用于建立连接过程):在连接建立时用来同步序号。当 SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在相应的报文段中使用 SYN=1和ACK=1。因此,SYN置为1就表示这是一个连接请求或连接接受保温。
- FIN(finish标志,用于释放连接):当 FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接
7、窗口(Window):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样就可以控制发送数据的速度
8、检验和(Checksum):检验范围包括首部和数据两部分,由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。这也是TCP可靠传输的一个重要保障
9、紧急指针(Urgent Pointer):紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。
10、TCP可选项(TCP Options):长度可变,最长可达40字节。当没有使用“选项”时,TCP的首部长度是20字节。
三、TCP的三次握手
所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手: 建立连接时,客户端发送SYN包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认,SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手: 服务器收到 SYN 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手: 客户端收到服务器的SYN + ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
3.1 为什么需要三次握手才能建立连接
- 为了初始化Sequence Number 的初始值,实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
- 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
3.2 首次握手的隐患——SYN超时
一、问题起因分析:
- 服务器收到客户端的SYN,回复SYN和ACK的时候未收到ACK确认
- 服务器不断重试直至超时,Linux默认等待63秒才断开连接;(重复5次【不包括第一次】,从1秒开始,每次重试都翻倍:1+2+4+8+16+32=63秒)
二、针对SYN Flood的防护措施:
SYN队列满后,通过tcp_syncookies参数会发SYN cookie【源端口+目标端口+时间戳组成】
若为正常连接则Client会回发SYN Cookie,直接建立连接;
3.3 保活机制:
当我们建立连接后,Client出现故障怎么办?
向对方发送保活探测报文,如果未收到相应则继续发送;
尝试次数达到保活探测数仍未收到相应则中断连接;
四、TCP的四次挥手
所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
- 第一次挥手: Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FINWAIT1状态
- 第二次挥手: Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
- 第三次挥手: Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态
- 第四次挥手: Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
一、为什么会有TIME_WAIT状态
客户端连接在收到服务器的结束报文段之后,不会直接进入CLOSED状态,而是转移到TIME_WAIT状态。在这个状态,客户端连接要等待一段长为2MSL,即两倍的报文段最大生存时间,才能完全关闭,其原因主要有两点:
- 确保有足够的时间放对方收到ACK包
- 避免新旧连接混淆
二、为什么需要四次握手才能断开连接
因为TCP连接是全双工的网络协议,允许同时通信的双方同时进行数据的收发,同样也允许收发两个方向的连接被独立关闭,以避免client数据发送完毕,向server发送FIN关闭连接,而server还有发送到client的数据没有发送完毕的情况。所以关闭TCP连接需要进行四次握手,每次关闭一个方向上的连接需要FIN和ACK两次握手,发送发和接收方都需要FIN报文和ACK报文
三、服务器出现大量CLOSE_WAIT状态的原因
是由于对方关闭socket连接,我方忙于读或写,没有及时关闭连接
当客户端因为某种原因先于服务端发出了FIN信号,就会导致服务端被动关闭,若服务端不主动关闭socket发FIN给Client,此时服务端Socket会处于CLOSEWAIT状态(而不是LASTACK状态)。通常来说,一个CLOSEWAIT会维持至少2个小时的时间(系统默认超时时间的是7200秒,也就是2小时)。如果服务端程序因某个原因导致系统造成一堆CLOSEWAIT消耗资源,那么通常是等不到释放那一刻,系统就已崩溃
解决:1、检查代码,特别是释放资源的代码 2、检查配置,特别是处理请求的线程配置
Linux的检查代码:
- netstat -n|awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'
五、总结
到这里TCP的三次握手四次挥手就讲完了,好久都没有写技术文章了,写了一下,感觉还挺好的,上面是博主的认识,有写的不好的地方,大家可以在评论区讨论或者提问。