TCP协议

TCP协议处于四层参考模型的第三层,即传输层。同一层的还有UDP协议。TCP和UDP的区别是:TCP是可靠的,面向连接的,基于字节流的服务;UDP是不可靠的,无连接的,面向数据块的服务。

解释如下:

  • 每次使用TCP传输数据时,都要先建立一对一的连接,即面向连接的。而使用UDP时不必先建立连接,而是直接将数据广播出去就可以,即无连接;
  • 因为TCP必须先建立连接,所以TCP传输的速度要比UDP慢;
  • TCP使用一系列机制来保证数据的可靠传输,包括数据确认机制,超时重传机制等。

TCP报头的格式如下(<font color="red">很重要,TCP的一切机制都是以此为基础的</font>):

图中各字段含义解释如下:

  • 源端口和目的端口:指明该TCP报文是由哪一个应用程序发送给哪一个应用程序的。因为端口号标示这应用层的一个服务进程。
  • 序号和确认号:序号表明该报文段在整个数据流中相对于开始位置的偏移量;确认号表明该报文是对对端的哪一个报文的确认,特别声明的是,只有当ACK标志为1时,确认号才有效。TCP的数据确认机制就是通过这两个字段来实现的。
  • 头部长度:表明该TCP报头的长度,包括选项部分。
  • 保留字段:保留不用,以便于将来扩展。
  • 标志字段:共6位,含义分别为S–>SYN,若为1,表明这是一个请求报文。A–>ACK,若为1,表明确认号有效,这是一个确认报文。F–>FIN,若为1,表明这是一个断开连接的请求报文。U–>URG,若为1,表明紧急指针有效。R–>RST,若为1,表明这是一个复位报文段,接收端会清空自己的发送缓冲区。P–>PSH,若为1,提示接收端应用程序应立即从缓冲区中读走数据。
  • 窗口大小:告诉对端自己的缓冲区大小,用于TCP的滑动窗口机制。
  • 校验和:由发送端对TCP头部和数据部分进行CRC循环冗余校验后填充,接收端以此确定该数据报是否损坏。
  • 紧急指针:若URG标志为1,则紧急指针有效,指明TCP带外数据的相对位置。
  • 选项字段:记录一些其他的信息,在此不表。最大长度为40字节,计算方法和IP头部选项的计算方法一致,请参考这里

TCP连接的建立

TCP通过建立一条全双工的通信链路来保证数据的可靠传输。建立连接的过程即为三次握手和四次挥手。

TCP通信的两端都处于某些状态之中,通过在各种不同状态间的切换来完成数据传输。

示意图如下:

图中绿色大写部分标注出来的是TCP的状态转移过程,橙色小写部分标注出来的是客户端和服务器各自所调用的函数。

  • 服务器和客户端首先调用socket函数创建一个socket,此时它们都处于CLOSED状态;

  • 服务器调用bind函数绑定端口号,并调用listen函数监听该socket,进入LISTEN状态;

  • 服务器调用accept函数等待客户端的连接;

  • 客户端调用connect函数,在此函数中发生了三次握手操作,此后客户端处于ESTABLISHED状态;服务器则从accept函数返回,也处于ESTABLISHED状态。三次握手如下:

    • 客户端发送携带SYN标志的请求报文给服务器,随机初始值ISN为x。进入SYN_SENT状态;
    • 服务器收到请求报文后,回复给客户端一个确认报文,表示自己同意建立连接,ack表示对x的确认。同时也会发送自己的SYN请求随机初始值为y。
    • 客户端收到服务器的报文后,知道服务器同意与自己建立连接。同时也回复了服务器,表示“我也同意与你建立连接”,ack表示对y的确认。
  • 接下来服务器与客户端各自传输数据;

  • 当客户端的数据发送完毕后,会调用close函数来断开连接(当然也可以有服务器来断开连接,不过道理是一样的)。在close函数里,发生了两次挥手操作。

    • 客户端发送FIN报文给服务器,表示“我的数据已经发送完了,现在请求断开连接”,此时进入FIN_WAIT_1状态;
    • 服务器收到后,回复了一个确认报文,表示同意断开。ack为堆上次x+1的确认。此时服务器进入CLOSE_WAIT状态;
    • 客户端收到后,进入FIN_WAIT_2状态;
  • 当服务器的数据也发送完毕时,也调用close函数请求断开连接,在close函数发生了另两次挥手操作。

    • 服务器发送FIN报文给客户端,表示“我的数据也发送完了,现在请求断开连接”,并进入LASK_ACK状态;
    • 客户端收到后,发送确认报文,表示“我也同意断开你的连接”。并进入TIME_WAIT状态。TIME_WAIT状态共持续2MSL时间。注:MSL,Max Segment Life,即最大报文生存期。表示一个报文可以存活的最大时间。RFC标准文档建议值为2分钟,Linux下一般为30秒;
    • 服务器收到该确认报文后,连接正式关闭,回到CLOSED状态;
    • 客户端在TIME_WAIT状态持续2MSL时间后,也回到CLOSED状态。一个完整的TCP过程完成。

TIME_WAIT状态

TIME_WAIT状态存在的原因有两个:

  • 保证TCP连接安全可靠地断开;
  • 允许老的重复分节在网络上消逝;

如果没有TIME_WAIT状态,那么当客户端发送的最后一个确认报文在网络上丢失时,对客户端而言连接已经完全断开,没有任何问题。但对服务器来说,并没有收到确认报文,所以在超时之后会再次发送FIN报文给客户端,而此时客户端会认为这是一个非法的报文,会回复一个带RST标志的报文,所以就产生了“僵死连接”。所以主动断开连接的一方需要维护一个TIME_WAIT定时器,在定时器到期之前,如果再次收到了服务器的FIN包,表明自己的最后一个确认报文已经丢失,此时会再次发送确认报文并重置该定时器。

另一种情况,如果客户端的最后一个确认报文并没有丢失,而是阻塞在某个路由器节点上了。此时若没有TIME_WAIT状态,那么服务器和客户端均可以完全关闭连接,然后重新建立一个相似的TCP连接,在此之后,刚才那个确认报文到达了服务器,也会导致服务器认为是非法的,但服务器还维持着原来的TCP连接。

TCP的四个定时器

TCP协议为每个TCP连接规定了四个定时器,分别是重传定时器,持久定时器,保活定时器和时间等待定时器。

重传定时器

用于数据应答机制中。当一端发送出一个数据包时,就启动一个重传定时器。当定时器超时时,就重发该报文并重置该定时器。这是TCP可靠性的基础。

持久定时器

设想这么一种情况,发送端哗哗哗地发送数据,将接收端的接收窗口占满了,此时会收到一个宣布接收窗口大小为0的确认报文,此时发送端就会启动一个持久定时器,若在定时器未到期期间收到了宣布接收窗口有空闲的报文,就撤销该持久定时器,继续发送数据。否则在定时器到期后,就发送一个探测报文,告诉接收端“你的窗口有空闲了没?快点告诉我!”。

若没有持久定时器,可能接收端想通知发送端自己的接收窗口有空闲可以继续发送了,但该报文在半路丢失了,此时会发送端在等待这个已经丢失的报文,接收端在等待发送端的数据,就会造成死锁。

保活定时器

若发送端发送了某些数据后突然就沉默了,也许是出故障了,在这种情况下,这个链接将永远处于打开状态下。

解决方法就是当接收端没接受到一个报文,就重置保活定时器。当保活定时器到期后,也就是超过了规定的阈值,发送端会发送探测报文。若发了若干个探测报文还是没有收到响应的话,就认为对端出现了故障,就关闭连接。

时间等待定时器

刚才说了,时间等待定时器的作用是为TIME_WAIT状态计时。

TCP超时重传机制

TCP的超时重传机制就是发送端发出一个数据包后,立即启动一个重传定时器。若在定时器到期之前收到了确认报文,就撤销该定时器并继续发送数据。否则就认为该数据包在传输途中丢失,重发该数据包并重置定时器。因此这样保证了数据包一定能被对方收到,也就是TCP的可靠性之一。