TCP概述
1. TCP提供的服务
我们知道TCP是一个面向连接、提供可靠数据数据传输服务的传输层协议。面向连接意味着发送端和接收端在交换数据前需要建立一个连接,和我们平常打电话一样,在通话前,需要拨号建立连接。为了提高TCP连接的可靠性,TCP提供了超时重发、流量控制、拥塞控制、报文校验、重排序、去除重复。
- 超时重发: 发送端发送一个TCP报文段的同时会启动一个定时器,随后等待接收端接受数据向发送端发送确认报文,如果不能再超时时间前收到确认报文,发送端则会重发该TCP报文段。
- 流量控制:发送端和接受端都会维护一个缓存队列,表示能够接受的数据长度。接收端只能允许发送端发送接收端能接收的数据大小,这样可以判断收发两侧的网络拥塞程度,从而动态调整收发两侧的数据传输速率。
- 拥塞控制:相比流量控制,拥塞控制主要是根据网路收发方之间的设备、链路的拥塞情况,动态调整收发方发送数据的速率。一般是采用慢启动的方法,初始时发送1个报文,收到该报文的ACK确认后,允许发送2个报文段,收到这两个报文段的确认报文后,允许发送4个报文段…,当发送报文丢失或超时等,将允许发送的报文长度减半。
- 报文校验:在TCP报文段的首部中有16位的校验字段,用来在接受方校验发送方发送的报文的准确性。如果报文出错,则会发送重发确认报文。
- 重排序:由于TCP采用的网络层协议是IP协议,最终的TCP报文段都会分割成IP数据报,并且IP不是按照顺序发送的,所以接受方接受的TCP报文也是无序的,需要按照报文序号重排序,生成完整的TCP报文。
2. TCP报文段格式
TCP采用的网络层协议是IP协议,所以TCP报文段会封装在IP数据报中。
从上图可知,TCP首部长度默认为20字节,具体内容如下。
- 端口号:由于TCP实际上是在接收方主机的两个进程上建立连接,而主机上每个端口号对应一个进程,所以TCP首部需要记录源端口号和目的端口号。(源IP、源端口号、目的IP、目的端口号)可以唯一确定网路中的每一个TCP连接。
- 序号:TCP给每个要发送的报文都标上了序号,一是告诉接受端我发送的报文序号,从而接收端能根据该序号确定下一次数据交换需要接受的数据序号;二是数据的重排序就是根据这个序号来进行的。一般刚建立连接时,会选择一个初始序号ISN(Initial Serial Number),其发送的第一个数据序号为ISN + 1。
- 确认序号:当接收方收到发送方的数据后,会发送一个确认报文给发送方,确认报文中包含接受方所期望的下一个数据序号,该序号就是确认序号。所以确认序号一般是上一条接受到的数据的序号+1。该字段只有当 ACK=1 时才有效。
- 首部长度:由于首部中包含选项字段,所以首部不一定是20个字节。首部长度记录的就是首部中32bit的个数。默认是5。
- 6个标志字段:在TCP首部中包含6个标志字段,分别是URG、ACK、PSH、RST、SYN和FIN,取值为0/1,标识为1时表示对应的功能启用。
URG: 表示紧急指针有效
ACK:表示确认序号有效
PSH:标识TCP中包含数据
RST:表示连接重新建立
SYN:表示连接建立
FIN:表示连接终止
- 窗口大小:前面提到了TCP提供的流量控制功能,就是在接收两端维持的可以接收的数据大小,大小为字节数,最大为65535字节。
- 校验和:提供TCP首部和TCP数据的校验功能。一般由发送方提供校验值,接收方进行校验。
- 紧急指针:当URG=1时,该字段有效。
- 选项:TCP有一些可选字段,即在TCP报文段中可有可无的数据。一般常见的字段是最大报文长度(MSS,Maximum Segment Size),一般连接的两端会在连接的第一个报文段中指明这个字段。这个字段表明收发方能接受的最大报文长度。
- 数据:TCP报文段发送真正有效的数据。这个字段是可选的,可以在连接的双方发送没有数据的报文段。一般建立连接、断开连接、确认报文等都是发送的没有数据字段的报文段。
3. TCP连接的建立(三次握手)
- 发送端准备与远程主机的建立连接,首先开始准备一个SYN=1的报文段,表明其想建立连接,同时初始化Seq为ISN(Initial Serial Number),然后通过网络将其发送;
- 接收端收到该报文后,解析报文发现SYN=1,说明报文发送者想和它建立连接。随后,其准备一个报文,SYN=1,ACK=收到的报文段Seq+1,向发送方表明我已收到你发送的想建立连接的报文段,同意与你建立连接。同时设置Seq=接收端的Seq,由于之前未和发送端建立连接,所以Seq=接收端的ISN,然后通过网络将其发送。
- 当发送端收到该报文后,解析发现SYN=1,ACK=1,说明接收段已同意与其建立连接。随后,准备一个报文,ACK=收到的报文段Seq+1的报文,表示已经接受到接收端与其建立连接的报文段。
为什么要三次握手后才能建立连接?
1. 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。如果不设置三次握手,就可能会导致客户端发送一个连接请求到服务器,服务器就建立连接,然后给客户端发送反馈ACK,发送端不处理该ACK,也不给服务器发送ACK或数据,就会导致服务器一直以为建立了连接,一直处于等待客户端数据的状态,造成服务器的浪费。
2. 由于TCP连接是全双工的,即两侧都能互相发送消息。为了保证连接的可靠性,提出了三次握手的最优方案。如果要在全双工的线路建立连接,首先需要发送方通知接收方我要与你建立连接,接收方要给发送方一个确认收到连接反馈;同时接收方也需要通知发送方,我要和你建立连接,发送方也需要对该连接请求发送确认消息。其实接受方收到的发送方要连接请求,给予确认的报文可以和接收方发送给发送方建立连接的报文组成一个数据包一起发送。所以最终形成了只需要三次握手就能建立连接。
4. TCP连接的结束(四次挥手)
1. 发送完数据,想断开连接的一方,在最后发送的报文中设置Fin=1,告知另一方,断开连接;
2. 接收方收到后,根据收到的Fin报文发送对应的ACK,表示我已收到断开连接请求;
3. 接收方过一段时间后,发送Fin=1的报文,表示接收方应用程序执行完毕,想要和发送方断开连接;
4. 发送方收到该Fin=1的报文,发送一个ACK报文,表示接收到了接收方的断开连接请求
为什么要四次挥手后断开连接?为什么不是三次挥手?
前面提到了,TCP是全双工的,即两个方向都能发送数据。客户端发送给服务端一个FIN=1的报文,表示从客户端到服务端的数据连接断开,但不代表服务端不能发送数据了。收到客户端的FIN后,服务端返回对应的ACK,但没有将服务端的FIN=1包含进去,是因为服务端可能还有一些数据需要发送,服务端需要等待一段时间,等所有的报文发送完毕后,再通知客户端,断开客户端到服务端的数据连接。