目录
TCP协议全称传输控制协议,其要对数据的传输进行详细的控制。
一、TCP协议格式
- 16位源端口号
- 16位目的端口号
- 32位序号:发送数据包的序号
- 32位确认序号:接收到的数据包序号+1,表示该序号之前的数据都已经接收到
- 4位首部长度:也叫数据偏移,标准的TCP报头长度是20字节,即不带选项的TCP报头。如果TCP报头带上了选项,那么报头长度也会增大,就会使用4位首部长度确认报头具体是多大。单位是4字节,默认值(不带选项)是5;最大是15,即60字节。
- 6个标志位:对应6种TCP报文类型(ACK为1是应答报文;SYN为1是请求连接报文;FIN为1是断开连接报文;RST为1是强制关闭连接报文;PSH为1表示当前数据要立即交给接收方的应用层;URG为1表示16位紧急指针有效)
- 16位窗口大小:接收缓冲区的剩余空间大小
- 16位校验和:确保TCP报文段(头部+数据)在传输过程中未被篡改或损坏
- 16位紧急指针:标识哪部分数据是紧急数据
- 选项:拓展TCP报头的其他属性
- 数据
二、确认应答机制(ACK)
1.确认应答
发送端每发送一个数据给接收端,接收端都要返回一个确认应答(只有TCP报头,没有数据)给发送端,这样发送端才能知道自己的数据已经成功发送给接收端了。
2.数据序号和确认序号
TCP还为发送的数据的每一个字节都编上了序号,即报头中的32位序号。序号即为起始序号+数据的大小。同时应答也有一个确认序号,通常是数据的序号+1,返回给发送端,表明接收端已经成功接收到了确认序号之前的所有数据(因为TCP接收端会按照数据序号对数据进行排序,保证与发送顺序一致),下一次发送端发送数据要从应答中的确认序号处开始发送数据。
其实数据序号的本质是当应用层把数据交给传输层时,数据会被放到发送缓冲区,把发送缓冲区当成一个char类型数组,那么这些数据天然就会根据数组下标形成序号。
3.捎带应答
TCP协议是全双工的,即一个套接字既可以发送给数据也可以接收数据。当主机A发送给主机B数据时,主机B要返回给主机A确认应答,同时主机B也向发送数据给主机A,那么主机B就会将数据和确认应答作为一个整体发送给主机A。这就是捎带应答。
三、超时重传机制
如果主机A给主机B发送数据,但是主机A没有收到主机B的应答报文,那么主机A就会认为主机B没有接收到数据,等待一段时间后仍然没有收到主机B的应答报文,会重新给主机B发送数据。
主机A没有收到主机B的应答报文有两种可能的原因,主机A发送的数据丢失,主机B没有收到;主机B收到了数据,但是主机B发送的应答报文丢失。但是不管哪种情况,在主机A看来都是主机B没有收到数据。
1.超时规定时间
规定一个时间,如果发送方在该时间内没有收到接收方的应答报文,就会重新发送数据。如果该时间规定的太短,就会频繁重发数据,导致效率降低。如果该时间过长,就要花费很长时间等待应答报文,同样效率低下。因此超时的时间是动态变化的。
Linux系统中规定,超时时间以500ms为一个单位进行控制,每次判定超时重传的时间都是500ms的整数倍。
- 超时一次后重发数据,需要等待2*500ms,如果得不到应答再进行重传
- 超时两次后重发数据,需要等待4*500ms,如果得不到应答再进行重传
- 重传达到一定次数后,TCP会认为网络或者对端主机出现异常,将强制关闭连接
2.数据去重
多次重传后,接收端可能会收到多份重复数据。此时接收端可以通过数据序号对数据进行去重。
四、连接管理机制
1.三次握手机制
客户端与服务端建立连接时,要进行三次握手。三次握手结束后,客户端与服务端的连接才全部建立完成。
- 第一次握手:客户端发送SYN标志位为1的报文,表示向服务端发起连接请求
- 第二次握手:服务端对客户端发送的报文作应答,发送ACK标志位为1的报文;同时服务端要响应客户端的连接请求,发送SYN标志位为1的报文。服务端会将这两个报文合二为一,叫做捎带应答。
- 第三次握手:客户端对服务端发送的报文做应答,发送ACK标志位1的报文
补充:客户端与服务端建立连接的时间差
- 客户端在发送ACK报文后就认为连接已经建立,此时客户端可以给服务端发送数据
- 服务端在接收到客户端发送的ACK报文后,才认为连接已经建立
问题:如果客户端认为连接已经建立完成,而服务端连接还没有建立完成。此时客户端给服务端发送数据如何处理?
解决:服务端的连接还没有建立完成就收到了客户端的数据,此时服务端会直接丢弃数据,也不会返回任何应答给客户端
2.三次握手中的最后一次ACK没有应答
由于客户端发送给服务端的ACK没有应答,客户端就无法确认是否服务端收到了自己发送的ACK报文,但是客户端认为服务端一定收到了ACK报文,并且建立好了连接。本质是在赌!
如果客户端发送给服务端的ACK丢失了,导致服务端没有收到客户端的ACK报文,服务端和客户端会分别处理:
- 服务端:服务端在给客户端发送SYN+ACK报文后,收不到客户端发送的ACK报文,服务端会触发超时重传机制,如果多次重传后仍然收不到ACK报文,会释放连接资源。
- 客户端:客户端并不清楚ACK丢失了,客户端认为此时是连接建立成功的。所以客户端会给服务端发送数据,但是实际上连接并没有建立成功,因此服务端收不到数据,也就不会给客户端返回ACK应答报文。客户端在收不到ACK应答的情况下也会出发超时重传,如果此时服务端已经释放连接资源了,客户端重传的任何数据都是非法的,服务端会给客户端返回标志位RST为1的报文,客户端接收到RST报文后,就会强制关闭连接,终止所有重传。如果此时服务端也在进行超时重传,并没有释放连接资源,对于客户端发送的数据会静默丢弃,不会返回应答报文给客户端。
不仅仅是在三次握手机制中,所有的网络通信中会有很多原因导致连接出错,如果主机A已经释放连接资源了,但是主机B不知道对方释放了连接资源,继续给主机A发送数据,那么主机A就会给主机B发送RST报文,主机B接收到RST报文后会强制关闭连接。
3.三次握手机制的作用
- 验证全双工,即验证网络的连通信。客户端和服务端都能以最小次数验证自己既可以发送数据,也可以接受数据
4.四次挥手机制
如果通信双方想要断开连接,就需要进行四次挥手。
客户端或者服务端想要断开连接时,都会调用close关闭套接字,而调用close关闭套接字实际上就是触发两次挥手。两次挥手即是一方给另一方发送标志位FIN为1的报文,另一方接收到FIN报文后就发送ACK报文,收到ACK报文后完成两次挥手。
客户端想要断开与服务器的连接,首先客户端调用close关闭套接字,触发两次挥手,向服务端发送FIN报文,告诉服务端自己要断开连接,服务端接收到FIN报文后给客户端发送ACK应答报文,告诉客户端自己已经收到FIN报文。随后服务端也要调用close关闭自己的套接字,向客户端发送FIN报文,告诉客户端自己要断开连接,客户端收到FIN报文后给服务端发送ACK应答报文,告诉服务端自己已经收到FIN报文。客户端与服务端各自的两次挥手完成后,就是四次挥手完成后,连接才真正断开。
客户端要断开连接,一定是因为自己的应用层数据已经全部发送完成了,会清空自己的发送缓冲区,当客户端调用close触发两次挥手之后,仅仅是客户端断开了与服务端的连接,但是在服务端调用close完成两次挥手之前,服务端并没有断开与客户端的连接。如果服务端还有数据需要发送给客户端,服务端还会继续发送数据,直到服务端的应用层数据也全部发送完成了,才会调用close完成两次挥手。此时客户端与服务端都已经相互断开连接。
补充:客户端关闭套接字后如何读取服务端发送的数据?
如果当客户端关闭套接字后,而服务端仍要对客户端发送数据,此时客户端接收到数据后,套接字已经被关闭了,如何将这些数据读取到应用层呢?实际上关闭套接字不仅仅可以使用close,还可以使用shutdown,shutdown可以自定义关闭读、关闭写或者关闭读写。客户端可以仅仅是关闭写,不需要发送数据了,但是可以继续读取数据到应用层。
补充:四次挥手也可以是三次挥手
如果客户端和服务端双方的应用层数据都发送完了,客户端和服务端同时要断开连接,那么当客户端给服务端发送FIN报文时,服务端会将FIN报文和ACK报文合并发送(捎带应答),然后客户端再给服务端发送ACK报文
5.四次挥手中的状态转换
四次挥手必定是有一方主动断开连接的,以服务端主动断开连接为例:
- 服务端主动断开连接,调用close/shutdown,给客户端发送FIN报文,进入FIN_WAIT1状态
- 客户端收到服务端的FIN报文,回复ACK报文,进入CLOSE_WAIT状态
- 服务端接收到客户端的ACK报文,等待客户端的FIN报文,进入FIN_WAIT2状态
- 客户端应用层的数据处理完成,调用close/shutdown,给服务端发送FIN报文,进入LAST_ACK状态
- 服务端收到客户端的FIN报文,回复ACK报文,进入TIME_WAIT状态,等待2*MSL时间
- 客户端收到服务端的ACK报文,进入CLOSED状态,释放连接资源
- 服务端等待2*MSL时间结束,进入CLOSED状态,释放连接资源
补充:如果是客户端主动断开连接,而服务端存在大量连接一直处于CLOSE_WAIT状态,说明服务端有大量的文件描述符没有关闭,可能存在文件描述符泄露问题
6.为什么要有TIME_WAIT状态
服务端/客户端处于TIME_WAIT状态,要等待2*MSL时间再释放连接资源。MSL时间是TCP报文的最大生存时间。
因为有一些报文可能会由于网络阻塞等问题传输速度特别慢,报文没有丢失,但是却被判定为超时了,多次重传后仍然得不到应答,连接就会被强制关闭,进入TIME_WAIT状态。此时进程退出了,但是连接资源并没有释放,因为要等待2*MSL时间再释放,这样就能防止新的连接立即建立,新的连接接收到阻塞在网络中的历史残留报文,对新连接的数据产生干扰。
等待2*MSL时间是因为假如被动关闭方在1*MSL时间内未收到ACK报文,就会重传FIN报文,重传的FIN报文最长需要1*MSL时间到达主动关闭方,主动关闭方收到FIN报文后发送的ACK报文也需要1*MSL时间到达被动关闭方,总计2*MSL时间。
这也就能解释为什么当客户端与服务端进程结束后,立即重连会导致端口号绑定失败,因为连接资源还没有释放,处于TIME_WAIT状态。
7.SYN洪水攻击问题
TCP中的三次握手机制是存在缺陷的,攻击方会发送大量伪造源IP的SYN报文给服务器,通过耗尽服务端资源使合法用户无法建立连接。
原理:服务端在接收到客户端发送的SYN报文后,会进入SYN-RCVD状态,将连接信息存入半连接队列,而服务端需要接收到客户端发送ACK报文后才能完成三次握手。攻击者可以发送大量伪造源IP的SYN报文给服务器,自己却不响应服务端发送的SYN+ACK报文,服务器会在半连接队列中维护大量的连接信息,当半连接队列被占满以后,就无法再处理合法用户的连接请求。
五、流量控制
TCP通信时,如果接收方的接收缓冲区满了,发送方继续发送数据时接收方会直接丢弃数据。尽管被丢弃的数据可以通过超时重传机制重发,但是这样做效率低下。所以发送方在发送数据时要做流量控制,根据接收方的接受能力来控制发送速度。
接收方的接受能力是根据接收缓冲区的剩余空间确定的,剩余空间越大,接受数据的能力越强,剩余空间越小,接收数据的能力越弱。当接收方接收到数据时,会在返回的ACK报文的16位窗口大小属性中填上接收缓冲区的剩余空间大小,告知发送方自己的数据接受能力。其实通信双方在进行三次握手建立连接时,就会在自己的ACK报文中带上接收缓冲区的剩余空间大小。
如果接收方的发送缓冲区满了,发送方就会停止发送数据进行等待。等待期间发送方会向接收方发送窗口探测报文(本质是携带1字节数据的报文),接收方收到探测报文后会返回窗口更新通知(本质是一个不带数据的TCP报头,16位窗口大小属性中填写了接收缓冲区剩余空间大小)。
六、滑动窗口
发送方可以并行发送多条数据,然后再等待ACK应答。那么发送方是如何确定一次可以发送多少条数据呢?答案是通过滑动窗口。
滑动窗口是发送缓冲区的一个窗口,本质是通过win_start和win_end指针维护的一段区域。滑动窗口之前的是已经发送并且收到应答的数据;滑动窗口中的是可以直接发送,暂时不需要应答的数据(全部发送完成后再等待应答,实际上就是并行发送多条数据);滑动窗口之后的数据是还没有发送的数据。
滑动窗口的大小是根据接收方返回的应答报文中的16位窗口大小确定的,win_start=16位确认序号(确认序号之前的数据都已经成功发送并获得应答了),win_end=win_start+win,win即为16位窗口大小。
滑动窗口解决丢包问题:快重传
- 数据包丢失:每个数据包都有自己的序号,对应的ACK报文中也有确认序号。假设发送1~1000,1001~2000,2001~3000,3001~4000,4001~5000,5001~6000,6001~7000共计7个数据包,如果序号为2000的数据包丢失了(即1001~2000的数据),那么就不会有对应的ACK应答,反而其他所有的ACK应答的确认序号都会是1001,表明1001之前的数据已经接收到了。当发送方接收到3个相同确认序号的ACK报文,就会重发对应的数据包,即重发1001~2000的数据包,这一次接收方收到了数据并做出应答,应答报文中的确认序号会算上之前所有已经接收到的数据,确认序号是7001。这就是快重传操作。
- 应答报文丢失:如果是数据包的应答报文丢失了,没有关系。接收方会根据收到的应答报文取最大的一个确认序号,确定该确认序号之前的数据已经全部接收到了。所以并行发送大量数据包时,丢失几个应答报文是无关紧要的。
七、拥塞控制
TCP可以通过滑动窗口高效地发送大量数据,但是可能当前网络状态比较拥堵,而发送端不知道。如果在不清楚网络状况的情况下,贸然发送大量数据就可能会导致网络状态更加拥堵。
因此TCP引入了拥塞控制,这里要引入几个概念:
- 拥塞窗口:拥塞窗口即当发送的数据超过拥塞窗口值就会导致网络拥堵。实际上的滑动窗口大小=min(拥塞窗口,应答窗口)。由于网络状况是浮动的,所以拥塞窗口值也是浮动的,要经过多次探测才能得知具体值。拥塞窗口初始值是1,要经过慢开始和拥塞避免得出拥塞窗口值。
- 慢开始:拥塞窗口一开始是通过慢开始算法呈指数级增长的,当拥塞窗口值超过阈值时,拥塞窗口通过拥塞避免算法改为线性增长。
- 阈值:拥塞窗口不能一直呈指数级增长,这样会增长特别快,导致网络过载。
- 拥塞避免:当拥塞窗口值超过阈值时,执行拥塞避免算法,线性增长。当拥塞窗口到达某个值时,发送的数据会大量丢包(根据快重传和超时重传机制确定),此时就认定该值是拥塞窗口的最大值。拥塞窗口变为1,阈值变为拥塞窗口最大值的一半,重新执行慢开始算法。
规定了一个拥塞窗口(发送的数据超过拥塞窗口值就会导致网络拥堵)。所以实际上的滑动窗口大小=min(拥塞窗口,应答窗口)。由于网络状况是浮动的,所以拥塞窗口值也是浮动的,要经过多次探测才能得知具体值。
慢启动机制就是
八、延迟应答
当接收方收到数据后,通常不会立即返回给发送方应答,因为立即返回的应答窗口可能很小,即接收缓冲区的剩余空间很小。延迟应答后,接收方的应用层可能会将接收缓冲区的数据读取走,这样接收缓冲区的剩余空间就会增大,返回的应答窗口可能也就会增大,提高发送方的传输效率。
延迟应答基本规则:
- 每隔N个数据包应答一次
- 每隔一次最大延迟时间就应答一次