跟UDP完全相反,TCP提供的是面向连接、可靠的字节流服务。面向连接意味着两个应用程序在交换数据之前都得先建立连接。TCP通过检验和,确认和重传的方式来提供可靠性。两个应用程序通过TCP连接交换8bit构成的字节流,TCP不在字节流里面插入任何的记录标识符。如果一方三次分别发送10字节、20字节、50字节,接收方可以分四次接收这80字节,每次20字节。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端。另外,TCP不对字节流做任何解释。
1.TCP头部
TCP头部格式如下图所示( 图片来源)![]()
每个TCP段都包括源端口号和目的端口号,用来寻找发送端的收端应用进程。 一个TCP连接有源IP地址、目的IP地址、源端口号、目的端口号共同决定。序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。确认序号指的是接收端希望下一次收到字节的序号,所以应该是已收到字节序号加1.首部长度4字节,之所以存在是因为可选字段的需要,最多60字节,固定20字节。检验和覆盖了整个首部跟数据,并且是强制的。而UDP是可选的,IP只覆盖首部。只有URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。其他,后面碰到再讲。2.TCP的连接与断开
TCP的连接与断开也就是著名的三次握手和四次挥手,其示意图如下( 图片来源)。![]()
三次握手:请求端发送一个SYN报文段指明想要连接的端口号以及初始序列号,置SYN=1.接收端收到这个片段后,把ACK确认序号设为发送端序号加1和指明服务器端初始序列号的SYN报文段一起发送,置SYN=1.接收端收到后,发送确认序号为服务器端SIN加1的ACK。到此三次握手完成。四次挥手:终止一个连接需要4次挥手最主要是因为半关闭的缘故,由于TCP连接是全双工的,每个方向必须单独的关闭。客服端发送一个FIN报文段,服务器收到后返回一个序列号加1的ACK确认。至此一端关闭就完成了,它将不能再发送数据但是可以接收数据。另一半关闭也是一样的。最大报文长度(MSS)表示TCP传往另一段的最大的块数据长度。当一个连接建立时,连接的双方都要通报自己的MSS。这是可协商的,如果有一方不同意时,将使用默认值536字节。TCP经常用于若干并发服务器,那么:当一个服务器进程接收来自一来自客户进程的服务请求时是怎么处理端口的?如果多个连接请求几乎同时到达会发生什么情况?对于第一个问题:还记得前面说一个TCP连接是由源IP地址、目的IP地址、源端口号跟目的端口号四个因素共同决定的,当一个服务器进程接收来自一来自客户进程的多个服务请求时,本地端口号还是一样,由于客户端端口号会随机产生,所以并不会冲突。对于第二个问题,一般是使用以下规则:TCP首部中的RST是用于“复位”的,一般来说,无论何时一个报文端发往基准的连接出现错误,TCP都会发出一个复位报文段。
- 服务器端有一个连接队列,该队列里面的值表示已经建立TCP连接,但是没有被应用层接受。
- 应用层将指明该队列的最大长度,称为积压值。一般是取0到5之间的一个整数。
- 对于新的连接请求,如果连接队列里面还有空间,那么将建立连接否则就抛弃。
2.TCP数据传输
2.1经受时延的确认
通常TCP在收到数据时并不立即发送ACK,相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为 数据捎带ACK)。绝大多数实现采用的时延是200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。2.2Nagle算法
该算法要求一个TCP连接上最多只能有一个未被确认的分组发送,也就是说下一个确认未到达之前不能发送分组。它收集这些小的分组,等下一个确认到达时一起发送。这个算法的好处是它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。当交互系统为用户提供实时的反馈时必须无时延的发送,所以需要关闭该算法。2.3滑动窗口协议
滑动窗口协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。发送方、接收方滑动窗口如下所示( 图片来源):![]()
![]()
offered window称为提供的窗口,由接收方提供advertised Window来指导发送方修正offered window的大小,这也称为流量控制。可用窗口表示发送方可以立即发送多少数据。接收方窗口多有的一部分表示已经发送确认但是还没有交给应用程序。3.TCP重传
3.1超时重传
当发送方发出一个TCP片断时便开始计时并等待接收方的ACK回复,如果一段时间后还没有收到,就认为它超时了再发送一个TCP片段,这就称为超时重传。在超时重传中最主要的问题是等待的时间怎么确定,太长了,丢失的片段没有及时重传,引起网络资源的浪费,太短了,误判丢失可能引起不必要的重传。这个问题可称为 重传超时时间(RTO)的确定。发送一个TCP片段,数据需要从发送方到接收方,ACK需要从接收方到发送方,一来一去正好一个RTT时间。有一种算法是,对RTT采样,得到SRTT,计算它的平均值(mean)和方差(std)。RTO=mean+4std。网络越不稳定,方差就越大,所以这是合理的。3.2快速重传
当一个失序的TCP片段到达时,接收方都要发送一个重复的ACK。该ACK并不会被延迟,目的在于让对方知道自己接收到了一个失序的TCP片段并告知对方自己希望收到的TCP片段。如果一连串收到3个或3个以上的重复ACK,那很可能就是中间的一个TCP报文段丢失了,这时并不需要等待定时器的溢出,直接重传该报文段,这称为TCP报文段的快速重传。4.TCP拥塞控制
拥塞避免算法假定由于分组收到损坏引起的丢失是非常少的,因此分组丢失就意味着在源主机和目的主机之间某处发生了拥塞,所以要进行拥塞控制。先介绍一种算法:4.1慢启动算法
慢启动算法为发送方增加了一个新的窗口,称为拥塞控制窗口,cwnd。cwnd刚开始设置为一个报文段,每收到一个ACK就窗口加1,所以这是一种类似于指数的增长。发送方一次能发送的数据由发送窗口跟cwnd的最小值决定。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。4.2拥塞避免算法
拥塞避免算法维持一个慢启动门限ssthresh。当一个连接建立时,cwnd为1,ssthresh设为65535字节。当cwnd小于等于ssthresh时,则进行慢启动,否则进行拥塞避免。拥塞避免的算法是没收到一个ACK,cwnd加1/cwnd,这样就可以确保最多为cwnd增加一个报文段,其实基本不可能增加一个。当发生超时或收到重复ACK时,ssthresh设为当前窗口大小的一半(当前窗口大小为cwnd跟发送窗口的更小值,但最少2mss),发生超时,将cwnd设为一个报文段。4.3快速重传算法
当收到第3个重复的ACK时,把ssthresh设为cwnd的一半,cwnd设为ssthresh加上3个mss,并重传丢失的报文段。继续收到重复的ACK时,cwnd加一个报文段并发送一个分组(如果新的cwnd允许的话)。收到一个新的报文段的ack时,把cwnd'设为ssthresh(为第一步设的值),进入拥塞避免算法。5.TCP的坚持定时器
TCP通过让接收方指明希望从发送方接收的数据字节数来达到流量控制的目的。当接收方接收的数据来不及处理,没有可用的接收窗口时,将发送一个窗口大小为0的ACK确认,发送窗口被关闭。当接收的数据交给应用程序处理,接收方将发送一个ACK确认来通知发送方窗口变大。但是因为ACK不带数据,TCP不保证一定可达,要是这个ACK丢失了,将会使连接处于死锁状态。这就需要一个TCP的坚持定时器来周期性地向接收方查询以便发现窗口是否已增大。窗口探查将包含一个字节的数据,TCP总是允许在窗口关闭以后发送一个字节的数据。收到窗口为0的ACK并不确认该字节,于是该字节将持续重传。5.1糊涂窗口综合症
接收方通知一个小的窗口,然后发送方发送一段小的数据,接收窗口处理一小段数据后又通知一个小的窗口,一直往复把大部分的资源都浪费在首部的传送上,这称为糊涂窗口综合症。可以通过以下两个思路来解决,一是发送方一定长度的报文段才发送,二是接收端不通告小窗口。6.TCP的保活定时器
许多时候一个服务器希望知道客户主机是否崩溃并关机或者崩溃又重新启动。许多实现提供的保活定时器提供这种能力。