TCP协议具有 有连接,可靠传输,面向字节流,全双工的特点
注意:4位首部长度的单位不是字节,而是4字节,所以TCP报头最大长度是15 * 4 = 60字节
对于网络协议来说,扩展升级,是一件成本极高的事情,后续TCP引入一些新功能的时候,就可以使用这些保留位字段
如果首部长度值是5,表示整个TCP报头长度是20字节(5x4字节)(相当于没有选项)
如果首部长度值是15,表示整个TCP报头长度是60字节(15x4字节)(选项相当于是40字节)
示例图:
A给B发了个消息,B收到后就会返回一个应答报文(ACK),此时A收到应答之后,就知道刚才发的数据已经顺利被B接收。
考虑更复杂的情况
示例图:
由于网络上可能存在“后发先至”,收到消息的顺序是可能存在变数的,变成了“去吃饭不?:不好”,“去学习不?:好”,这就跟原义不符。
为了解决上述后发先至问题,给传输的数据,和应答报文,都进行编号。
示例图:
这样就可以通过序号来区分当前应答报文是针对那个数据进行的
判断一条报文是否是应答报文,取决于其报头中ACK标志位,如果ACK这个标志位为1,则是应答报文,为0,则不是。
实际上,由于TCP是面向字节流的,TCP的序号也是按照字节来编号的
TCP的字节的序号是依次累加的,每个TCP数据报报头填写的序号只需要写TCP数据的头一个字节的序号即可。
TCP知道了头一个字节的序号,再根据TCP报文长度,就很容易知道每个字节的序号。
确认序号的取值,是收到的数据的最后一个字节的序号+1
例如:确认序号为1001
表示的含义:
1.<1001的数据都已经确认收到了。
2.A接下来应该从1001这个序号开始继续发送(B向A索要1001的数据)
小结:TCP可靠传输能力,最主要就是通过确认应答机制来保证,通过应答报文(ACK),就可以让发送方清楚的知道传输是否成功,进一步的引入了序号和确认序号,针对多组数据进行详细的区分
上述讨论确认应答的时候,只是讨论了顺利传输的情况,并未讨论传输出现问题的情况(例如丢包)
以下是丢包的两种情况
- 主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B;
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发
第二种情况:ACK丢失
第二种情况重传数据,可能会导致主机B收到很多重复数据。TCP就要对其进行去重以及重新排列。
TCP存在一个“接收缓冲区”这样的存储空间(接收方操作系统内核里的一段内存),每个TCP的socket对象,都有一个接收缓冲区。
主机B收到主机A的数据,其实是B的网卡读到数据,并将这个数据放到B的对应socket的接收缓冲区中,后续应用程序使用getInputStream,进一步使用read从接收缓冲区中读取数据。
TCP使用这个接收缓冲区,根据数据的序列号识别是否有数据重复,如果重复,将后来的数据舍去,并对收到的数据进行重新排序。
小结:由于去重和重新排序机制(都依赖于TCP报头的序号)的存在,发送方只要发现ACK没有按时到达,就会重传数据,即使重复了,顺序乱了,接收方都能很好地处理。
TCP的可靠传输就是通过确认应答+超时重传来进行体现的。
其中确认应答描述的是传输顺利的情况,
超时重传描述的是传输出现问题的情况。
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
建立连接阶段的几种状态:
1.LISTEN :
监听对方建立连接请求,表示服务器已经准备就绪,随时可以有客户端来建立连接,相当于手机开机,信号良好,随时可以接听他人的电话。
2.SYN_SEND:属于请求连接,此时发送的报文段,不能携带数据。
3.SYN_RECEIVE:接收到对方的连接建立请求。
4.ESTABLISHED :
连接建立完成,接下来可以进行正常通信,相当于拨打电话,对方接通。当客户端在此状态下,发送的ACK报文段可以携带数据,
所谓的三次握手,本质上是“四次”交互。
通信双方,各自要向对方发起一个“建立连接”的请求,同时,再各自向对方回应一个ack。中间两次交互是可以合并成一次交互的,因此就构成了“三次握手”。
问题一:为什么要将中间两次交互合并?
答案:和封装分用相关,封装分用两次比封装分用一次成本更高。
问题二:如果是两次握手能否建立连接的过程?
答案:不可以。
第一次握手:客户端发送网络包,服务端收到。
服务端可知:客户端的发送能力,服务端的接收能力正常。
第二次握手:服务端发送网络包,客户端收到。
客户端可知:客户端的发送能力,接收能力正常。服务端的发送能力,接受能力正常。但是此时服务端并不知道自己的发送能力是否正常。
第三次握手:客户端再次发送网络包,服务端收到。
服务端可知:客户端发送能力,接受能力正常。服务端的发送能力,接受能力正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常
三次握手的意义:
1.让通信双方各自建立对对方的“认同”
2.验证通信双方各自的发送能力和接收能力是否正常
3.在握手的过程中,双方需要协商一些重要的参数,来完成数据的同步。
建立连接阶段
四次挥手和三次握手非常类似,都是通信双方各自向对方发起一个断开连接的请求,在各自给对方一个回应。
建立连接阶段的几种状态:
1.FIN_WAIT_1:客户端主动调用close时,向服务器发送结束报文段(FIN),同时进入FIN_WAIT_1;
2.CLOSE_WAIT:(出现在被动发起断开连接的一方)
当客户端主动关闭连接(调用close),服务器会收到结束报文段(FIN),服务器返回确认报文段(ACK)并进入CLOSE_WAIT;
3. FIN_WAIT_2:客户端收到服务器对结束报文段的确认(ACK),则进入FIN_WAIT_2,
开始等待服务器的结束报文段(FIN);
4. LAST_ACK:进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
5.TIME_WAIT:(出现在主动发起断开连接的一方)
假设是客户端主动断开连接,当客户端进入TIME_WAIT状态的时候,相当于四次挥手已经挥完了,客户端收到服务器发来的结束报文段(FIN),进入TIME_WAIT,并发出LAST_ACK;
【TIME_WAIT -> CLOSED】客户端要等待一个2MSL(报文最大生存时间)的时间,才会进入CLOSED状态。
6.服务器收到了对FIN的ACK,彻底关闭连接,进入CLOSED状态
TIME_WAIT的意义:四次挥手与三次握手一样,也会出现丢包的情况,当服务器未收到最后一个ACK,就会进行重传操作,因此使用TIME_WAIT状态保留一定时间,是为了处理最后一个ACK丢包的情况,能够在收到服务器重传的FIN之后,客户端能够针对这个重传的FIN进行ACK响应。
问题一:为什么三次握手的中间两次j交互能够合并,而四次挥手不能合并?
答案:
- 三次握手这三次交互过程,是纯内核中完成的,服务器的系统内核收到SYN之后,就会立即发送ACK,也会立即发送SYN,同一时机,所以能够合并。
- 四次挥手中FIN的发起,不是由内核控制的,而是由应用程序调用socket的close方法(或者进程退出)才会触发FIN,ACK则是由内核控制,当接收到发送方发来的FIN之后,就会立即返回ACK,这两者之间通常情况下会有个时间差,因此无法合并。
问题二:想一想,为什么是TIME_WAIT的时间是2MSL?
MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话
就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能错误的);
同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN。这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);
问题三:服务器上出现大量的 CLOSE_WAIT 状态,是什么原因?
答案:服务器没有正确的关闭 socket,导致四次挥手没有正确完成。这是一个 BUG。只需要加上对应的 close 即可解决问题
上述讨论的确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做使得性能较差。
此时就要引用滑动窗口机制来提高性能。
具体操作:批量发送,批量等待,使用一份等待时间来等待一组数据的多个ACK,本质上就是降低确认应答等待ack消耗的时间。
窗口大小:无需等待确认应答而可以继续发送数据的最大值,上图窗口大小就是4000;
下面是针对丢包的两种情况进行的讨论
情况一:数据包已经抵达,ACK丢了。
这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;
- 当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,是在提示发送端1001-2000的数据并没有接收到,应将对应的数据1001-2000重新发送。
- 这个时候接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了,被放到接收缓冲区中。
上述机制也被称为“快速重传机制”。
流量控制是一种干预发送窗口大小的机制
问题一:为什么要控制窗口大小?
- 窗口太大,会消耗大量的系统资源
- .窗口太大,完全不等待ack,就无法保证可靠性
- .要考虑接收方的处理能力
问题二:TCP协议段格式中,16位窗口大小是否意味着窗口大小最大是64kb呢?
答案:不是,TCP通过在选项部分引入窗口扩展因子M,使得实际窗口大小是 窗口字段的值左移 M 。
流量控制的工作就是根据接收方的处理能力(通过查看接收方接受缓冲区的剩余大小),来协调发送方的发送速率
发送端给接收端发送数据,接收端查看自身接收缓冲区剩余大小,并将这个值通过ACK报文返回给发送端,发送端会根据这个数字来确认下一轮发送的窗口大小。
当窗口大小为0时,发送方就要暂停发送,暂停发送的等待过程中,会给接收端定期发送窗口探测报文,这个报文不携带具体的业务数据,只是为了触发ack,查询窗口大小。注意:窗口大小是“发送方”的概念,是通过接收方的ack报头里的窗口大小字段来告诉给发送方的。
流量控制考虑的是接收方的处理能力,接下来讲述的拥塞控制则是考虑传输过程中中间节点的处理能力,二者共同决定发送方的窗口大小(二者较小值)
拥塞控制,本质上就是通过实验的方式,来逐渐找到一个合适的窗口大小。
TCP是个非常复杂的协议,不仅仅只有上述提到的特性!!!如果想了解更多TCP特性,可以去翻阅RFC标准文档。