TCP和UDP
先上两张图。


TCP
TCP 与 UDP 的区别相当大。它充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP
中都没有。
此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信(
主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)。
通过序列号与确认应答提高可靠性
- 在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知。这个消息叫做确认应答(ACK)。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。 反之,则数据丢失的可能性很大 。
- 在一定时间内没有等待到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。
- 未收到确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到,只是返回的确认应答在途中丢失。这种情况也会导致发送端误以为数据没有到达目的地而重发数据。
- 此外,也有可能因为一些其他原因导致确认应答延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
- 对于目标主机来说,反复收到相同的数据是不可取的。为了对上层应用提供可靠的传输,目标主机必须放弃重复的数据包。为此我们引入了序列号。
- 序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。

重发超时的确定
- 重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔。 如果超过这个时间仍未收到确认应答,发送端将进行数据重发。最理想的是,找到一个最小时间,它能保证“确认应答一定能在这个时间内返回”。
- TCP 要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间及其偏差。将这个往返时间和偏差时间相加,重发超时的时间就是比这个总和要稍大一点的值。
- 在 BSD 的 Unix 以及 Windows 系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,最初其重发超时的默认值一般设置为6秒左右。
- 数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
- 此外, 数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
以段为单位发送数据
- 在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS)。最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。
- TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送。进行重发时也是以 MSS 为单位。
- MSS 在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小。然后会在两者之间选择一个较小的值投入使用。
利用窗口控制提高速度
TCP 以1个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。
为解决这个问题,TCP
引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。如下图所示:

窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。
滑动窗口控制

上图中的窗口内的数据即便没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此,发送端主机需要设置缓存保留这些待被重传的数据,直到收到他们的确认应答。
在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
收到确认应答的情况下,将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也别称为滑动窗口控制。
窗口控制中的重发控制
在使用窗口控制中, 出现丢包一般分为两种情况:
- ① 确认应答未能返回的情况。在这种情况下,数据已经到达对端,是不需要再进行重发的,如下图:

- ② 某个报文段丢失的情况。接收主机如果收到一个自己应该接收的序列号以外的数据时,会针对当前为止收到数据返回确认应答。如下图所示,当某一报文段丢失后,发送端会一直收到序号为1001的确认应答,因此,在窗口比较大,又出现报文段丢失的情况下,同一个序列号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答,就会将其对应的数据进行重发。这种机制比之前提到的超时管理更加高效,因此也被称为高速重发控制。

拥塞控制
在介绍具体的概念之前,首先我要提到的是TCP中对于拥塞控制的最高思想,主要有三条,那就是既要尽量快的利用信道的最大能力,不要浪费;又要谨慎的控制信道的负载,不要拥堵;同时如果发生了拥堵,必须能够自我调节,不要崩溃。根据这个最高思想,TCP设计了一系列拥塞避免的算法。
慢启动
在一个网络环境下,信道是共享的,也就是说所有发送的数据都会在这个信道上穿梭。这个道理和高速公路是一样的,你只是高速公路上车流中的一辆车。所以说,当你准备开始发送数据的时候,你不能仅仅做到不管三七二十一,就先按自己的节奏发出自己的数据。如果这个时候信道已经很拥挤,你发出的数据一定也会堵在路上,从而造成进一步的重传,这些重传更加加重了网络的负担,本来拥堵不堪的道路只会越来越差。
所以TCP的设计者们设计了一个保守的策略,这种策略带有浓浓的工科生特点,就是先谨慎的试试,再大胆的前进,实践检验效果。具体的说就是TCP的设计者们采用了一个新的窗口,称之为拥塞窗口,记为cwnd,这个cwnd初始设置为一个one
segment的大小,在这里我们简单的理解为TCP报文一次允许发送的最大的大小以方便后面叙述。当发出这个一个报文之后,如果发送端能够收到对应的ACK。那么下一次就会发送2个segment的报文,接着如果还是收到这2个segment的ACK,那么说明这个信道还是可以承受当前大小的包的,那么接着进行试探,发送4个segment的包,收到4ACK,就发送8个segment,以此指数级增长的类推。
简单的说,慢启动就是一句话,大胆尝试,小心求证。而且在慢启动算法中,报文的增长速度是很快的,所以说这个算法的过程并不符合这个算法的名称,之所以称之为慢启动,是他小心的去试探网络情况的哲学上的”慢慢来”。
拥塞避免
上面的慢启动很明显可以在很短的时间内让信道的利用率达到一个比较理想的值,由于前面一直强调过,信道的资源总是有限的,所以到某一个阶段,这些快速增长的报文必定会充满信道使得信道变得开始拥挤。TCP的设计者们为了不要使得慢启动算法变成一个只有理论意义,而在很短的时间内又会造成网络崩溃从而导致本末倒置。所以说在慢启动的基础上,设计者们做了一些现实的改进与妥协。除了cwnd这个概念,设计者们还定义了一个叫做ssthresh的概念,slow
start thresh,
中文一般翻译为慢启动阈值,比如65535个字节。如果cwnd的大小达到了这个阈值,那么就不采用指数增长这样的比较暴力的方法,而是采用每次收到一个ACK,cwnd就扩大1个单位的方法,这样比较保守,目的之一就是慢慢而可靠的试出来信道所能承受的最大的负载,符合前面所说的哲学的第一条。
但是信道资源总是有限的,而cwnd在前面描述的过程中无论是怎样的方式,他一直递增的,所以触碰到天花板是必然,按照前面的“三条不要”的最高思想,在遇到信道已经塞满的情况下,需要做的就是快速的减少对信道的负担并且能够快速的恢复。
TCP怎么知道信道拥堵了呢?反过来问,信道拥堵会给发送端带来什么呢?第一个明显的效应就是发送出去的消息收不到ACK,也就是前面说的超时了,如果连续几个消息都没有收到ACK而不停的发生重传,那么就可以判定为信道已经拥堵了。这个时候,拥塞避免算法会进入恢复阶段,其方法很简单:
- ssthresh设置为当前发生拥塞cwnd的一般,如果cwnd在30个字节的时候(当然不可能只有这么小)发生了拥堵,那么新的ssthresh就设置为15。
- TCP重新进入慢启动阶段,也就是将cwnd设置为1,指数增长知道达到新的ssthresh的15,然后再重新按照拥塞避免第一个阶段,线性增长。
这个过程可以用图表示:

快速重传
网络是一个很复杂的环境,如果有这么一种情况,网络发生了拥堵但是又那么的拥堵,这种情况的表现是什么呢?按照前面介绍过的滑动窗口,TCP不是one by
one的发送数据包的,如果发送的数据包是1,2,3,1和3已经到,但是2没有到,由于拥堵在网络中丢失了,那么接收端会不断告诉发送端下一个需要的报文是2号报文,即使你后面的报文都到了,在2号报文没有收到的情况下,会一直发送对1号报文的ACK,表示需要的是2号报文。如果连续收到三个连续的ACK,就认为网络发生了拥堵。用语言描述有点绕,用图来表示就比较清晰。

这种情况说明了两种情况:网络确实发生了拥堵,但是又没有完全拥堵。因为如果完全拥堵了,那么发送端也不会受到三个ACK数据报文,所以这种情况没有必要从头再来,因为最高思想的第二点让我们最大的利用信道的能力。按照这个,设计者们又改进了上面的算法,提出了一个快速重传的方案,其思想如下:
- ssthresh设置为cwnd的一半
- cwnd设置为ssthresh的值
- 不需要重新进入慢启动阶段而是进入拥塞避免阶段
用图来表示这个过程如下:

快速恢复
快速重传算法已经尽力快的恢复对于网络的传送,但是设计们本着”面包里面抠面粉”的原则,在上面的快速重传算法中尝试想想有没有进步空间,在全面分析之后,提出了快速恢复的算法,其具体做法如下:
- 在收到3个重复的ACK之后,ssthresh设置为cwnd的一半,然后把cwnd设置为ssthresh加3个单位的大小,接着重传丢失的报文段,如果用前面的例子来举例就是重传2号报文。
- 如果这个时候再次收到重传的ACK,那么拥塞窗口增加1。
- 如果收到的是新的数据包的ACK,把cwnd设置为第一步的ssthresh的值。为什么这么做,因为如果收到的新的ACK,说明网络已经恢复了,可以进入拥塞避免的线性增长阶段了。
第一个例子里为什么加3呢,因为这个时候连续的收到3个ACK包,那么可以认为网络还有3个单位大小的余额,同时也可以这么想,说明有3个“老”的数据包已经从网络上离开了。
粘包和拆包
首先因为tcp是面向字节流的协议,所以他不保证包的独立性,只能在上层应用层进行拆包处理。
发送粘包的原因:
- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去;
- 接收数据端的应用层没有及时读取接收缓冲区中的数据;
- 数据发送过快,数据包堆积导致缓冲区积压多个数据后才一次性发送出去(如果客户端每发送一条数据就睡眠一段时间就不会发生粘包);
拆包:
- 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;
- 在包尾增加回车换行符进行分割,例如FTP协议;
- 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;
- 更复杂的应用层协议。
UDP
UDP协议,即用户数据报协议(User Datagram
Protocol),是一个简单的面向数据报的传输层协议。UDP协议只在IP数据报服务商增加了很少一点的功能,就是复用和分用,以及差错检测的功能。
UDP 常用于一下几个方面:1.包总量较少的通信(DNS、SNMP等);2.视频、音频等多媒体通信(即时通信);3.限定于 LAN
等特定网络中的应用通信;4.广播通信(广播、多播)。
UDP特点
- 无连接的
发送数据之前不需要建立连接,减少了开销和发送数据之前的时延。
- 尽最大努力交付
不保证可靠的交付,主机不需要维持复杂的链接状态表。
- 面向报文的
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
没有拥塞控制。
支持一对一、多对一和多对多的交互通信。
首部开销小,只有8个字节。
UDP首部结构

UDP首部由4各字段组成,各占两个字节:
- (1)源端口
在需要对方回信时使用,不需要时全为0。
- 目的端口
发送UDP数据报的目的地。
- 长度
UDP数据报的长度,最短为8个字节,只包含首部。
- 检验和
用于检验UDP数据报在传输过程中有没有出差错,有则丢弃。
两者的差别与联系
- TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。
- UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。
- TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。