TCP和UDP比较

文章目录

一、计算机网络模型

TCP和UDP比较

OSI七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层;

TCP/IP四层模型:数据链路层、网络层、传输层、应用层。

二、TCP/IP四层模型

链路层:负责封装和解封装IP报文,发送和接受ARP/RARP报文等。

网络层:负责路由以及把分组报文发送给目标网络或主机。

传输层:负责对报文进行分组和重组,并以TCP或UDP协议格式封装报文。

应用层:负责向用户提供应用程序,比如HTTP、FTP、Telnet、DNS、SMTP等。

TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议。

  • 在收发数据前,必须和对方建立可靠的连接。

这个协议的重点是面向连接。他其中包含的内容都是和连接相关,首先介绍就是建立连接和断开连接的过程

TCP 报文段结构

TCP和UDP比较

各部分的含义:

源端口号/目的端口号: 表示数据从哪个进程来, 到哪个进程去。

32位序号: 这些字段被 TCP 发送方和接收方用来实现可靠的数据传输。

4位首部长度: 表示该tcp报头有多少个4字节(32个bit)。

6位保留: 顾名思义, 先保留着, 以防万一。

URG: 标识紧急指针是否有效。

ACK: 标识确认序号是否有效。

PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走。

RST: 要求重新建立连接. 我们把含有RST标识的报文称为复位报文段。

SYN: 请求建立连接. 我们把含有SYN标识的报文称为同步报文段。

FIN: 通知对端, 本端即将关闭. 我们把含有FIN标识的报文称为结束报文段。

16位窗口大小: 这个字段用于流量控制。它用于指示接收方能够/愿意接受的字节数量。

16位检验和: 由发送端填充, 检验形式有CRC校验等. 如果接收端校验不通过, 则认为数据有问题. 此处的校验和不光包含TCP首部, 也包含TCP数据部分。

16位紧急指针: 用来标识哪部分数据是紧急数据。

端口号
数据链路和 IP 中的地址,分别指的是 MAC 地址和 IP 地址。前者用来识别同一链路中不同的计算机,后者用来识别 TCP/IP 网络中互连的主机和路由器。在传输层也有这种类似于地址的概念,这就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。

0-1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议。
1024-65535:操作系统动态分配的端口号,客户端程序的端口号,由操作系统从这个范围分配
常见端口号:
SSH服务器:22端口
FTP服务器:21端口
Telnet服务器:23端口
HTTP服务器:80端口
HTTPS服务器:443端口

面向连接

1.建立连接:三次握手

三次握手的作用是:在进行通信前,确定客户端和服务端的收、发能力正常。

TCP和UDP比较
第一次握手:客户端发送包含SYN和seq的网络包,然后服务端收到了。客户端进入SYN_SEND状态;

  • 服务端视角得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发送包含SYN、ACK和seq的网络包,然后客户端收到了。服务器端进入SYN_RECEIVED状态

  • 客户端视角得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。

第三次握手:客户端发送包含ACK和seq的网络包,然后服务端收到了。客户端和服务器端都进入ESTABLISHED状态

  • 服务端视角得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。

面试题:两次握手可以吗?

  • 不可以。
  • 因为可能会出现已失效的连接请求报文段又传到了服务器端。 客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器端。本来这是一个早已失效的报文段。但服务器端收到此失效的连接请求报文段后,就误认为是 客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段,同意建立连接。
  • 假设不采用 “三次握手”,那么只要服务器端发出确认,新的连接就建立了。由于现在客户端并没有发出建立连接的请求,因此不会理睬 服务器端的确认,也不会向服务器端发送数据。但服务器端却以为新的运输连接已经建立,并一直等待客户端发来数据。这样,服务器端的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,客户端不会向服务器端的确认发出确认。服务器端由于收不到确认,就知道客户端并没有要求建立连接。
    而且,两次握手无法保证客户端正确接收第二次握手的报文(服务器端无法确认客户端是否收到),也无法保证客户端服务器端之间成功互换初始序列号。

面试题: 四次握手可以吗?

  • 这个肯定可以。
  • 三次握手都可以保证连接成功了,何况是四次,但是会降低传输的效率。

面试题: 第三次握手中,如果客户端的ACK未送达服务器,会怎样?

  • 服务器端:由于服务器端没有收到ACK确认,因此会每隔 3秒 重发之前的SYN+ACK(默认重发五次,之后自动关闭连接进入CLOSED状态),客户端收到后会重新传ACK给服务器端。

  • 客户端,会出现两种情况:

    1. 在服务器端进行超时重发的过程中,如果客户端向服务器端发送数据,数据头部的ACK是为1的,所以服务器收到数据之后会读取 ACK number,进入 ESTABLISH 状态
    2. 在服务器端进入CLOSED状态之后,如果客户端向服务器发送数据,服务器会以RST包应答。

面试题: 如果已经建立了连接,但客户端出现了故障怎么办?

  • 服务器每收到一次客户端的请求后都会重新复位一个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

面试题: 初始序列号是什么?

  • TCP连接的一方A,随机选择一个32位的序列号(Sequence Number)作为发送数据的初始序列号(Initial Sequence Number,ISN),比如为1000,以该序列号为原点,对要传送的数据进行编号:1001、1002…三次握手时,把这个初始序列号传送给另一方B,以便在传输数据时,B可以确认什么样的数据编号是合法的;同时在进行数据传输时,A还可以确认B收到的每一个字节,如果A收到了B的确认编号(acknowledge number)是2001,就说明编号为1001-2000的数据已经被B成功接受。

2.断开连接:四次挥手

数据传输结束后,通信的双方可以释放连接。数据传输结束后的客户端主机和服务端主机都处于 ESTABLISHED 状态,然后进入释放连接的过程。

TCP和UDP比较
第一次挥手: 客户端将FIN置为1,发送一个序列号seq给服务器端;进入FIN_WAIT_1状态;

第二次挥手: 服务器端收到FIN之后,发送一个ACK=1,acknowledge number=收到的序列号+1;进入CLOSE_WAIT状态。此时客户端已经没有要发送的数据了,但仍可以接受服务器发来的数据。

第三次挥手: 服务器端将FIN置1,发送一个序列号给客户端;进入LAST_ACK状态;

第四次挥手: 客户端收到服务器的FIN后,进入TIME_WAIT状态;接着将ACK置1,发送一个acknowledge number=序列号+1给服务器;服务器收到后,确认acknowledge number后,变为CLOSED状态,不再向客户端发送数据。客户端等待2*MSL(报文段最长寿命)时间后,也进入CLOSED状态。完成四次挥手。

用现实理解四次挥手流程:

  1. 客户与服务器交谈结束之后,客户要结束此次会话,就会对服务器说:我要关闭连接了(第一 次挥手)
  2. 服务器收到客户的消息后说:好的,你要关闭连接了。(第二次挥手)
  3. 然后服务器确定了没有话要和客户说了,服务器就会对客户说,我要关闭连接了。(第三次挥手)
  4. 客户收到服务器要结束连接的消息后说:已收到你要关闭连接的消息,才关闭。(第四次挥手)

面试题: 为什么不能把服务器发送的ACK和FIN合并起来,变成三次挥手(CLOSE_WAIT状态意义是什么)?

  • 因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复ACK,表示接收到了断开连接的请求。等到数据发完之后再发FIN,断开服务器到客户端的数据传送。

面试题: 如果第二次挥手时服务器的ACK没有送达客户端,会怎样?

  • 客户端没有收到ACK确认,会重新发送FIN请求。

面试题 :客户端TIME_WAIT状态的作用是什么?

  • 第四次挥手时,客户端发送给服务器的ACK有可能丢失,TIME_WAIT状态就是用来重发可能丢失的ACK报文。如果服务器端没有收到ACK,就会重发FIN,如果客户端在2*MSL的时间内收到了FIN,就会重新发送ACK并再次等待2MSL,防止服务器端没有收到ACK而不断重发FIN。 MSL(Maximum Segment Lifetime),指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。

确认应答机制(ACK机制)

TCP和UDP比较

TCP将每个字节的数据都进行了编号, 即为序列号:
TCP和UDP比较

每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你要从哪里开始发。

超时重传机制

TCP和UDP比较
主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B,如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发,但是主机A没收到确认应答也可能是ACK丢失了。

TCP和UDP比较

因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉. 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果。

超时时间如何确定?

  • 最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
    但是这个时间的长短, 随着网络环境的不同, 是有差异的.
    如果超时时间设的太长, 会影响整体的重传效率; 如果超时时间设的太短, 有可能会频繁发送重复的包.
    • Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。
    • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传. 如果仍然得不到应答, 等待 4500ms 进行重传。
    • 依次类推, 以指数形式递增. 累计到一定的重传次数, TCP认为网络异常或者对端主机出现异常, 强制关闭连接。

滑动窗口

TCP和UDP比较
这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。

TCP和UDP比较
窗口

  • 窗口大小指的是无需等待确认应答就可以继续发送数据的最大值,上图的窗口大小就是4000个字节 (四个段).

  • 发送前四个段的时候, 不需要等待任何ACK, 直接发送,收到第一个ACK确认应答后, 窗口向后移动, 继续发送第五六七八段的数据…

  • 因为这个窗口不断向后滑动, 所以叫做滑动窗口,操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答,只有ACK确认应答过的数据, 才能从缓冲区删掉。

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被填满, 这个时候如果发送端继续发送, 就会造成丢包, 进而引起丢包重传等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度.这个机制就叫做 流量控制(Flow Control)

接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,通过ACK通知发送端;

窗口大小越大, 说明网络的吞吐量越高;

接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口大小的通知之后, 就会减慢自己的发送速度;如果接收端缓冲区满了, 就会将窗口置为0;这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 让接收端把窗口大小再告诉发送端.

TCP和UDP比较
接收端如何把窗口大小告诉发送端呢? 在我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;

那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?

实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位(左移一位相当于乘以2).

拥塞控制

虽然TCP有了滑动窗口, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.

因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.

TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

TCP和UDP比较
此处引入一个概念程为拥塞窗口

  • 发送开始的时候, 定义拥塞窗口大小为1; 每次收到一个ACK应答, 拥塞窗口加1;
    每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
  • 像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快. 为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.
  • 此处引入一个叫做慢启动的阈值
    当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

TCP和UDP比较
当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;

少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;

当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;

拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.

假设接收端缓冲区为1M. 一次收到了500K的数据;
如果立刻应答, 返回的窗口大小就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会儿再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M

窗口越大, 网络吞吐量就越大, 传输效率就越高.
TCP的目标是在保证网络不拥堵的情况下尽量提高传输效率;

那么所有的数据包都可以延迟应答么?

  • 肯定也不是,有两个限制

数量限制: 每隔N个包就应答一次

时间限制: 超过最大延迟时间就应答一次

具体的数量N和最大延迟时间, 依操作系统不同也有差异,一般 N 取2, 最大延迟时间取200ms.

捎带应答

在延迟应答的基础上, 我们发现, 很多情况下客户端和服务器在应用层也是 “一发一收” 的:

意味着客户端给服务器说了 “How are you”
服务器也会给客户端回一个 “Fine, thank you”

那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起发送给客户端。
TCP和UDP比较

面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;

调用write时, 数据会先写入发送缓冲区中;
如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区; 然后应用程序可以调用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工

由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:

写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;

粘包问题

首先要明确, 粘包问题中的 “包”, 是指应用层的数据包.
在TCP的协议头中, 没有如同UDP一样的 “报文长度” 字段
但是有一个序号字段.
站在传输层的角度, TCP是一个一个报文传过来的. 按照序号排好序放在缓冲区中.
站在应用层的角度, 看到的只是一串连续的字节数据.
那么应用程序看到了这一连串的字节数据, 就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包.
此时数据之间就没有了边界, 就产生了粘包问题

那么如何避免粘包问题呢?
归根结底就是一句话, 明确两个包之间的边界

对于定长的包

  • 保证每次都按固定大小读取即可
    例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可

对于变长的包

  • 可以在数据包的头部, 约定一个数据包总长度的字段, 从而就知道了包的结束位置
    还可以在包和包之间使用明确的分隔符来作为边界(应用层协议, 是程序员自己来定的, 只要保证分隔符不和正文冲突即可)

TCP 异常情况

  • 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别.

  • 机器重启: 和进程终止的情况相同.

  • 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset. 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.

  • 另外, 应用层的某些协议, 也有一些这样的检测机制.
    例如HTTP长连接中, 也会定期检测对方的状态.
    例如QQ, 在QQ断线之后, 也会定期尝试重新连接.

TCP总结

1.保证可靠性的机制

  • 校验和

  • 序列号(按序到达)

  • 确认应答

  • 超时重传

  • 连接管理

  • 流量控制

  • 拥塞控制

2.提高性能的机制

  • 滑动窗口

  • 快速重传

  • 延迟应答

  • 捎带应答

3.基于TCP应用层协议

  • HTTP

  • HTTPS

  • SSH

  • Telnet

  • FTP

  • SMTP

三、UDP

UDP报文段结构

TCP和UDP比较

源端口(可选字段):收端的应用程序利用这个字段的值作为发送响应的目的地址;

目标端口号: 表示接收端端口;

UDP长度: 表示整个数据报(UDP首部+UDP数据)的最大长度;

整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误。如果校验和出错, 就会直接丢弃。

因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的

UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。

面向无连接

知道对应端的IP和端口号就直接进行传输, 不需要建立连接。

具体来说就是:

  • 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是UDP 协议,然后就传递给网络层了
  • 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作

不可靠性

没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;

面向数据报

不能够灵活的控制读写数据的次数和数量

面试题:对于UDP协议来说, 是否也存在 “粘包问题” 呢?

对于UDP, 如果还没有向上层交付数据, UDP的报文长度仍然存在.
同时, UDP是一个一个把数据交付给应用层的, 就有很明确的数据边界.
站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收.
不会出现收到 “半个” 的情况.

UDP能够传输的数据最大长度不能超过64K,由16位UDP长度所限制。

四、IP协议

属于网络层的协议
TCP和UDP比较

基本概念

主机: 配有IP地址, 但是不进行路由控制的设备; 路由器: 即配有IP地址, 又能进行路由控制;
节点: 主机和路由器的统称;

协议头格式

TCP和UDP比较

4位版本号:指定IP协议版本,如IPV4,IPV6

4位首部长度:IP头部长度有多少个32bit,即length4字节,4bit(1111)最大为15,所以IP头部最大长度为60字节

8位服务类型:3位优先权字段(弃用),4位TOS字段和1位保留字(必须置为0),4位TOS表示最小时延,最大吞吐量,最高可靠性,最小成本,四者相互冲突只能选一个。

16位总长度:IP数据报整体占多少字节

8位生存时间(Time To Live):数据报到达目的地最大报文跳数,每经过一个路由,TTL-1,一直减到0还没到就弃用了,主要为了防止路由循环。

8位协议,主要表示上层协议类型

16位头部校验和:使用CRC进行校验,鉴别头部是否损坏

32位源地址和32位目标地址:表示发送端和接收端

网段划分

1.IP地址分为两部分,网络号和主机号,网络号保证互连的两个网段具有不同的标识。主机号:同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号.
2.五类IP地址
TCP和UDP比较

A类0.0.0.0到127.255.255.255

B类128.0.0.0到191.255.255.255

C类192.0.0.0到223.255.255.255

D类224.0.0.0到239.255.255.255

E类240.0.0.0到247.255.255.255

子网掩码

避免大量的IP地址被浪费,引入额外的子网掩码来区分网络号和主机号。

1.子网掩码也是一个32位的正整数. 通常用一串 “0” 来结尾;
2.将IP地址和子网掩码进行 “按位与” 操作, 得到的结果就是网络号;
3.网络号和主机号的划分与这个IP地址是A类、B类还是C类无关;
TCP和UDP比较

特殊的IP地址

将IP地址中的主机地址全部设为0, 就成为了网络号, 代表这个局域网;

将IP地址中的主机地址全部设为1, 就成为了广播地址, 用于给同一个链路中相互连接的所有主机发送数据包;

127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1

五、总结

TCP和UDP比较
完!

上一篇:字节跳动Java内部学习资料泄露


下一篇:C++ Linux 网络编程学习笔记(2)TCP深入