TCP
TCP报头/首部(header)
首部是四位长度,单位不是字节,而是4个字节。
选项之前的20个字节是一定有的,首部最长是(1111 -> 15)也就是15 * 4 = 60字节。
选项部分范围就是0 ~ 40字节。
首部长度,其实就是划分了header和payload之前的分界线。
保留位:先占个位置,虽然暂时不用,保不齐以后可能会用。
关于TCP的特性
直观上看待TCP的特点
1.有连接
2.可靠传输
3.面向字节流
4.全双工
可靠性(TCP的核心特性)!!
尤其是要区分可靠性和安全性,TCP是可靠的,但是安不安全另当别论。
保证可靠性的核心:确认应答
1.确认应答(ACK)
发送方放松数据给接收方了,接收方就回应一个应答报文,如果发送方收到了这个应答报文,那么认为就是对方已经收到了。
由于网络上的传输,顺序是不确定的,因此就不能单纯的通过收到数据的顺序来确定逻辑,就需要对应大进行编号。
实际上,TCP传输数据不论条,而是论字节(面向字节流)
TCP的序号和确认序号,是以字节为单位进行编号的。
针对每个字节,分别编号,以此进行累加(实习上TCP的序列号的其实不一定是从1开始的)
确认序号和序列号之间的关系
第一个请求,A给B发送了1000字节的数据,徐浩就是1-1000(假设从1就开始编号了),这个操作就相当于发了一个TCP数据报,这个数据报的序号是1,长度是1000.
确认应答数据报,里面的确认序号是1001,意思就是1001之前的数据,B已经收到了。
发送方就可以根据确认单应答报文来确定接收方是否是收到了,只要发送方搜狐当了应答,就认为解说房已经收到,可靠性传输就完成了。
TCP的核心是可靠性,可靠性的核心是确认应答。
2.超时重传
确认应答机制中,这个是比较顺利的情况,但是传输过程中还是可能出现丢包的。
一旦发生数据丢包,就要进入超时重传的机制中了。
实际场景中,比如发消息没收到回复。
此时存在两种可能:
1.发的消息过去了,但是丢了,对方没看见
2.我发的消息过去了,对方看见了,也恢复了,但是回复消息的时候丢了
发送方无法区分,当前是发的数据丢了,还是应答数据丢了。
发送方能做的事情,只是在一段时间之后,重新发送一条数据。
解决方案
发送方把数据发出去之后,等待500ms,若是没有收到应答,就认为是丢包了。
超时时间会动态变化,不是一成不变的。
不同场景等待时间不同,等待时间会逐渐延,延长时间也是意味着让数据重传的频率降低。重传还是失败,大概率传输是不通了。
3.连接管理(重点)
TCP是有连接的,连接管理说的就是,如何建立连接(三四握手),如何断开连接(四次挥手)。、
三次握手(建立连接)
举个生动形象的例子,比如我想向女神表白,为了表达我的爱慕之意,给女神写了一封情书,女神同意了。
此时这个②过程表示两层含义,一层是接收到了我的爱慕表达,一层是也像我表达了爱慕之意
这样一来,男女朋友关系就建立了~
但是在TCP协议中实际的含义是如下这样:
谈谈三次握手的过程(画图)
本质上,就是,A向B请求连接,B给与回应.B也向A请求连接,A也给与回应
本来应该是"四次握手"
但是中间两次操作,是可以合在一起的,这两个操作在时间上是同时发生的
当A的syn 到达B的时候,B的内核就会第一时间进行应答ACK,同时也会第一时间发起SYN.这两件事同时触发,于是就没必要分成两次传输,直接一步到位
为啥要三次握手,为啥要建立连接?
主要目的是有两个:
1.投石问路:通过三次握手的过程,来确认A和B之间的传输是通畅的。尤其是要确认,A和B各自的发送能力和接受能力是否正常。
2.协商参数,通过三次握手。让A和B之间通通气,选择一些传输中合适的参数,例如。TCP的序列号从几开始。
举个例子:想象打电话的过程
延伸的问题:
为什么是三次握手不是四次握手。两次行不行?
可以四次握手,但是完全没必要,中间的ACK和SYn是可以合并在一起的,如果分成两个,传输的开销要比第一个大,涉及到封装和分用,效率上略低一点。
但是不能两次握手,这样不能知道A的接受能力和发送ACK是否正常,就相当于打电话A没有回应B,不知道是A的听筒坏了还是麦克风坏了。
TCP是有状态的
重点掌握的状态:
1.LISTEN:类似手机开机,信号满格,可以随时打入电话,服务器的状态。当我们创 建好ServerSocket实例的时候就进入了LISTEN状态。
2.ESTABLISHED:类似接通了电话,双方开始进行通话了,代码中accept返回了,得 到了一个clientSocket。
四次挥手(断开连接)
我们之前说三次握手,很明显我们知道A是客户端,B是服务器,客户端是主动的,服务器是被动的。
但是在四次挥手中,客户端和服务器都可以主动断开连接。
FIN是结束报文段
针对上图中的ACK和FIN为啥不能合并?
对于 B 来说,ACK 和 FIN 的出发时机是不一样的。
1.B 只要搜狐到 FIN 就会立即触发 ACK ,这个是内核态完成的。
2.B 发送的FIN 实际是用户代码控制的,代码中出现了
socket.close()
这样的操作的时候,才会触发 FIN。甚至说,如果B的代码写的出问题了,有可能一直不调用close。
重要掌握的状态:
CLOSE_WAIT:服务器搜狐到 FIN 之后,进入的状态,等待用户代码调用close,来发送 FIN。
TIME_WAIT:表示是客户端收到了FIN 之后进入了 TIME_WAIT,这个状态存在的一对主要就是为了处理最后一个ACK丢包问题。
在三次握手和四次挥手的过程中,同样可能会丢包,一旦丢包就会触发超时重传
1)第一个FIN丢了,A 迟迟收不到ACK,就会重传FIN
2)第一个ACK丢了,A迟迟收不到ACK,还会重传FIN
3)第二个FIN丢了,B 迟迟收不到ACK,还会重传FIN
4)第二个ACK丢了,B 迟迟收不到ACK,还会重传FIN
假设,如果A收到FIN之后,并返回ACK之后,连接就销毁,而不是进入TIME_WAIT状态,会发生什么?
此时一旦最后一个ACK丢了,就无法重传ACK了,因为连接已经销毁了。
TIME_WAIT即使进程已经退出了,TIME_WAIT状态仍然会存在(TCP 连接不会立即销毁)
TIME_WAIT会等待一定的时间,如果一定时间之内也没有重传的FIN过来,才真正销毁
如果服务器上出现大量的CLOSE_WAIT,这是啥情况?
这是代码出现的bug,close没有及时被调用到。
如果服务器上出现大量的TIME_WAIT,是啥情况?
这个也可能是代码bug,但是不能石锤,主动发起FIN的一方会进入TIME_WAIT,就需要排查服务器是否应该主动断开连接。
哪方先断开连接,哪方就会进入TIME_WAIT,进程退出之后,TIME_WAIT状态仍然存在,TCP连接仍然存在。
如果让服务器先退出,服务器这边就会进入到TIME_WAIT状态(原来的连接占据着端口),接下来如果服务器立即启动,新的进程又会尝试重新绑定这个端口。可能会存在端口绑定失败的情况。
4.滑动窗口
TCP不仅仅是为了保证可靠性,还要尽可能的提高传输效率。
其实可靠性和效率是矛盾的!TCP努力的在可靠性的前提之下,又做出了很多性能优化的手段。
在这个过程中,发送方要花很多时间来等,这个等待就浪费了大量的时间。
现在咱们是批量发送,一次发一波,一次等一波的ACK,把多组数据的ACK的等待时间给重叠起来了。
一次批量发的数据的长度,就成为“窗口大小”。
如果没有批量发送数据的长度限制(窗口无限大,完全不等ACK,就一直发),其实就没有可靠性可言。
如果窗口越大,整体的效率就越高,窗口越小,整体的效率就越低。
“滑动”的含义
当前窗口范围是1001 - 5000 ,也就意味着,发送方现在同时发送了 1001 - 2000,2001 - 3000,3001 - 4000,4001 - 5000,同时等待着四组数据的ACK。
假设,2001这个ACK先到发送方就会知道了,1001 - 2000这个数据已经被对方收到了,发送方也就不用继续等待这个数据了,接下来就立即再发一个5001 - 6000,仍然保证窗口大小是4份数据,保证当前同时等待4份数据的ACK
并不是把4份ACK都等到才发新的数据,而是随着收到ACK就随着往后发送。
如果在滑动窗口的场景中出现了丢包,怎么办?
这种情况下,没有关系,只要不是全部都丢了就好。
关键在于确认序列号的设定,后一个能包含前一个。
这种情况下,仍然需要进行重传,但是如何让发送方知道是数据报丢了呢?
因为1001丢包了,主机B会一直索要1001,确认序列号仍然是1001。
发送方这边,如果连续几次看到1001这个ACK,就知道了1001这个数据丢失了,接下来就会重传1001。
前面的2001 - 7000,这些数据已经到达了接收端了,只不过是在接收缓冲区里等待,当1001 - 2000这个数据到打的时候,B就知道了,7001之前的数据都到齐了,此时继续索要7001这个数据即可。