UDP和TCP

面试题

TCP和UDP有哪些区别?

UDP协议是面向无连接的,TCP是面向连接的

所谓建立连接,是为了客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,用这样的数据结构来保证所谓的面向连接的特性。

面向无连接也就是说不需要在正式传递数据之前先连接双方。
UDP具体的说就是:

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

面向连接的TCP,在正式传递数据之前需要连接双方,这里使用的是三次握手

TCP提供可靠交付,UDP不可靠

通过TCP连接运输,无差错,不丢失,不重复,并且按序到达。

我们都知道IP包是没有任何可靠性保证的,一旦发出去,走丢了,缺失了,都不会处理,UDP继承了IP包的特性,不保证不丢失,不保证按顺序到达。

TCP是面向字节流的,UDP基于数据报的(TCP是一种流模式的协议,UDP是一种数据报模式的协议)

TCP发送的时候是一个流,没头没尾,IP包可不是一个流,而是一个个的IP包,之所以变成了流,这也是TCP自己的状态维护做的事情,而UDP继承了IP的特性,基于数据包的,一个一个的发送

“TCP是一种流模式的协议,UDP是一种数据报模式的协议”,这句话相信大家对这句话已经耳熟能详~但是,“流模式”与“数据包模式”在编程的时候有什么区别呢?以下是我的理解,仅供参考!

1、TCP
打个比方比喻TCP,你家里有个蓄水池,你可以里面倒水,蓄水池上有个龙头,你可以通过龙头将水池里的水放出来,然后用各种各样的容器装(杯子、矿泉水瓶、锅碗瓢盆)接水。
上面的例子中,往水池里倒几次水和接几次水是没有必然联系的,也就是说你可以只倒一次水,然后分10次接完。另外,水池里的水接多少就会少多少;往里面倒多少水,就会增加多少水,但是不能超过水池的容量,多出的水会溢出。
结 合TCP的概念,水池就好比接收缓存,倒水就相当于发送数据,接水就相当于读取数据。好比你通过TCP连接给另一端发送数据,你只调用了一次write, 发送了100个字节,但是对方可以分10次收完,每次10个字节;你也可以调用10次write,每次10个字节,但是对方可以一次就收完。(假设数据都 能到达)但是,你发送的数据量不能大于对方的接收缓存(流量控制),如果你硬是要发送过量数据,则对方的缓存满了就会把多出的数据丢弃。
2、UDP
UDP 和TCP不同,发送端调用了几次write,接收端必须用相同次数的read读完。UPD是基于报文的,在接收的时候,每次最多只能读取一个报文,报文和 报文是不会合并的,如果缓冲区小于报文长度,则多出的部分会被丢弃。也就说,如果不指定MSG_PEEK标志,每次读取操作将消耗一个报文。
3、为什么
其实,这种不同是由TCP和UDP的特性决定的。TCP是面向连接的,也就是说,在连接持续的过程中,socket中收到的数据都是由同一台主机发出的(劫持什么的不考虑),因此,知道保证数据是有序的到达就行了,至于每次读取多少数据自己看着办。
而 UDP是无连接的协议,也就是说,只要知道接收端的IP和端口,且网络是可达的,任何主机都可以向接收端发送数据。这时候,如果一次能读取超过一个报文的 数据,则会乱套。比如,主机A向发送了报文P1,主机B发送了报文P2,如果能够读取超过一个报文的数据,那么就会将P1和P2的数据合并在了一起,这样 的数据是没有意义的。
TCP是可以有拥塞控制的,UDP则没有

TCP意识到包丢失或者是网络环境不好了,就会根据情况调整自己的行为,看看是不是发快了,要不要发慢点,UDP就不会,应用让我发我就发,不管网络情况,那么堵的很,我还要发,有点像愣头青的样子

UDP 高效

UDP没有像TCP那样复杂,头部开销很小只有八个字节,相比TCP的至少二十个字节要少很多,在传输数据报文时,是很高效的。

TCP是有状态的服务,UDP则是无状态服务

TCP能精确地记着发送了没有,接受了没有,发送到了哪?应该接受哪个?错一点都不行。
UDP 通俗地讲就是没脑子,啥也不管,不记录。

传输方式 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信.

UDP

UDP的三大特点
沟通简单

不需要大量的数据结构,处理逻辑,包头字段。前提是它相信网络是美好的,相信网络通路默认就是很容易送达的,不容易被丢弃的

轻信他人

他不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,他也可以传给任何人数据,甚至可以同时传给多个人数据

做事不懂变通

不知道什么时候坚持,什么时候退让,不会根据网络的情况进行发包的拥塞控制

UDP的三大使用场景
第一 需要资源少,网络情况比较好的内网,或者对于丢包不敏感的应用

其中DHCP协议(动态主机配置协议)就是基于UDP,操作系统镜像的下载使用的TFTP(简单文件传输协议,端口是69),也是基于UDP协议的

不需要一对一沟通,建立连接,而是可以广播的应用。

DHCP就是一种广播的形式

需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩,一往无前。

很多应用都是要求低时延的,它们一般是不想用TCP如此复杂的机制,而是根据自己的场景实现自己的可靠和连接保证。例如,有的应用觉得,有的包丢了就丢了,没必要重传,后面的包到了,就先给客户端展示,不一定非要等到齐,如果网络不好,那也要尽快传,速度不能降下来,抢在客户失去耐心之前到达。

基于UDP的五个实例
网页或者APP的访问

原来访问网页和手机APP都是基于HTTP协议的,HTTP协议是基于TCP的,建立连接都需要多次交互,对于时延比较大的主流移动互联网来说,建立一次链接需要的时间会比较长,然后既然是移动中,TCP可能还会断了重连,很耗时的。而且目前的HTTP协议,往往采取多个数据通道共享一个连接,这样本来为了加快速度,但是TCP的严格顺序策略使得哪怕共享通道,前一个不来,后一个即使和前一个没有关系,它也要等着,时延会加大

而QUIC(Quick UDP Internet Connections 快速UDP互联网连接),是Google提出的一种基于UDP改进的通信协议,其主要目的就是降低网络延时,提供更好的用户互动体验

QUIC在应用层上,会自己实现快速建立连接,减少重传时延,自适应拥塞控制。

流媒体协议

以前直播中协议 使用的是RTMP(实时消息传输协议),这个协议是基于TCP,TCP都知道,慢的一批,现在直播就是基于UDP,实现自己的视频协议

实时游戏
物联网

物联网领域终端资源少,很可能只是内存非常小的嵌入式系统,而维护TCP协议代价很大,
物联网对实时性要求也很高,Google的一家公司Nest建立的Thread Group 推出物联网通信协议Thread,就是基于UDP的

移动通信领域

在4G网络里,移动流量上网的数据面对的协议GTP-U就是基于UDP的,因为这个协议本身就比较复杂。

TCP

三次握手

A : SYN

客户端进入SYN-SENT状态

B : SYN ACK

服务端进入SYN-RCVD状态

A : ACK

双方进入ESTABLISHED(已建立)

三次握手除了双方建立连接外,主要还是为了沟通一件事请,就是TCP包的序号问题

A要告诉B,我这面发起的包的序号起始是从哪个号开始的,B同样告诉A,B发起的包的序号是从哪个号开始的,为什么不能都从1开始,因为这样往往会产生冲突

比如:A连接上B之后,发送了1,2,3三个包,但是发送3的时候,中间丢了,或者是绕路了,于是重新发送,后来A掉线了,重新连接上B后,序号又从1开始发送,然后发送2,但是压根没有向发送序号为3的包,但是上次绕路的那个3有回来了,发给了B,B自然认为就是下一个包,这时候就发生了错误。

因而,每个连接都要有不同的序号,这个序号的起始序号是随时间变化的,可以看成是一个32位的计数器,没4ms加一,如果计算一下,绕路的 包就会很早就死了。因为IP包头里面有个TTL,也即生存时间

四次挥手

A : FIN

客户端A进入FIN-WAIT-1状态

B : ACK

服务端B进入CLOSED-WAIT状态

B : FIN,ACK

服务端B进入LAST-ACK状态

A : ACK

客户端A进入TIME-WAIT(2MSL)

最后2MSL过后,双方都进入了CLOSED状态

如何实现一个靠谱的协议?

为了保证顺序性,每一个包都应该有个ID,在建立连接的时候,会商定起始的ID是什么,然后按照ID一个个发送,为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个之前的ID,表示都收到了,这种模式成为累计确认或者累计应答

详细内容看专栏

顺序与丢包问题

TCP具体是通过怎样的方式来保证数据的顺序化传输呢?

主机每次发送数据时,TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收主机对分配的这个序列号进行确认,如果发送主机在一个特定时间内没有收到接收主机的确认,则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收主机一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。

具体步骤如下:
(1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;

(2)并为每个已发送的数据包启动一个超时定时器;

(3)如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;

(4)否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。

(5)接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去

流量控制问题

所谓的流量控制就是让发送方的发送速率不要太快,让接收方来得及接受。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。

TCP头里有一个字段叫Window(或Advertised Window),用于接收方通知发送方自己还有多少缓冲区可以接收数据。发送方根据接收方的处理能力来发送数据,不会导致接收方处理不过来,是谓流量控制。暂且把Advertised Window当做滑动窗口,更容易理解滑动窗口如何完成流量控制

由滑动窗口协议(连续ARQ协议)实现。滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。

详情请看掘金专栏 TCP
滑动窗口
发送端窗口包含已经发送但未收到应答的数据和可以发送但是未发送的数据

发送端的窗口是由接受端窗口剩余大小决定的,接收方会把当前接收窗口的剩余大小写入应答报文,发送端收到应答报文后,会随之窗口进行滑动

Zerp窗口

在发送报文的过程,可能会出现对端出现零窗口的情况,在该情况下,发送端会停止发送数据,并启动persistent timer,该定时器会定时发送请求给对端,让对端告知窗口大小,在重试次数超过一定次数后,可能会中断TCP连接

拥塞控制

拥塞处理和流量控制不同,后者是作用于接收方,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。

拥塞处理作用于网络,防止过多的数据拥塞网络,避免出现网络负载过大的情况

拥塞处理包括了四个算法:慢开始,拥塞避免,快速重传,快速恢复

慢开始算法

就是在传输开始时将发送窗口慢慢指数级扩大,从而避免一开始就传输大数量导致网络阻塞。相比大家都下载过资源吧,每当我们开始下载的时候都会发现下载的速度是慢慢提升的,而不是一蹴而就直接拉满宽带。

慢开始算法步骤具体如下:
  • 连接初始设置拥塞窗口为1MSS(一个分段的最大数据量)
  • 每过一个RTT就将窗口大小乘二
  • 指数级增长肯定不能没有限制的,所以有一个阈值限制,当窗口大小大于阈值时就会启动拥塞避免算法
拥塞避免算法

拥塞避免算法相比简单点,每过一个RTT窗口大小只加一,这样能避免指数级增长导致网络阻塞,慢慢将大小调整到最佳值

在传输过程中可能定时器超时的情况,这时候TCP就会认为网络拥塞了,会马上进行以下步骤:

  • 将阈值设为当前拥塞窗口的一半
  • 将拥塞窗口设为1MSS
  • 启动拥塞避免算法
快速重传

快速重传一般和快恢复一起出现,一旦接收端收到的报文出现失序的情况,接收端只会回复最后一个顺序正常的报文序号,如果发送端接收到三个重复的ACK,无需等待定时器超时而是直接启动快速重传算法,具体的算法分为两种:

TCP Taho 实现如下:
  • 将阈值设置为当前拥塞窗口的一半
  • 将拥塞窗口设为1MSS
  • 重新开始满开始算法
TCP Reno实现如下
  • 拥塞窗口减半
  • 将阈值设为当前拥塞窗口
  • 进入快恢复阶段(重发对端需要的包,一旦接收一个新的ACK答复就退出该阶段),这种方式在丢失多个包的情况下就不怎么好
  • 使用拥塞避免算法
TCP的拥塞控制主要是来避免两种现象,包丢失和超时重传

一旦出现了这些现象就说明,发送的速度太快了,要慢一点。

常见面试题

为什么客户端在TIME_WAIT状态必须当代2MSL的时间

1 为了保证客户端发送的最后一个ACK报文段能够到达服务器,这个ACK报文段有可能丢失,因为使处于LAST-ACK状态的服务器收不到对方发送的FIN+ACK报文段,服务器会超时重传FIN+ACK这个报文段,而客户端就能在2MSL时间内收到这个报文,然后重传,重新启动2MSL计时器,最后,客户端和服务器都正常进入到CLOSED状态。
如果客户端没有等待一段时间,就无法收到服务器发送的重传报文,因而不会再次发送去确认报文,那么服务器就无法按照正常步骤进入CLOSED状

2 防止已失效连接请求报文段,出现在本连接中,客户端发送完最后一个ACK报文后,再经时间2MSL,就可以是本地连接持续的时间内所产生的所有报文都从网络中消失,这样就不会在下一个新连接中出现失效的连接请求报文

为什么连接的时候是三次握手,关闭的时候却是四次握手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

为什么不能用两次握手进行连接?

3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

超时重传和快速重传

超时重传:当超时时间到达时,发送方还未收到对端的ACK确认,就重传该数据报

快速重传:当后面的序号先到达,如接收方接收到了1,3,4而2没有收到,就会立即向发送方重复发送三次ACK=2的确认请求重传,如果发送方连续收到了3个相同序号的ACK,就重传该数据包,而不用等到超时。

TCP首部字段,有哪些字段
  • 源端口号 16位
  • 目的端口号 16位
  • 序号 32位
  • 确认序号 32位
  • 首部长度 4位
  • 保留 6位
  • 窗口大小16位
  • 校验位 16位
  • 紧急指针 16位
  • 选项(长度可变)
  • 标志位一共6位
  • FIN结束
  • SYN请求链接
  • RET重置
  • ACK确认
  • PSH (推送)
  • URG(紧急 和紧急指针一起)
SYN洪泛攻击

我们在TCP三次握手的讨论中已经看到,服务器为了响应一个收到的SYN,分配并初始化连接变量和缓存。然后服务器发送一个SYNACK进行响应,并等待来自客户端的ACK报文段,如果某客户不发送ACK来完成该三次握手的第三部,最终(通常在一分钟之后)服务器终止该半开连接并回收资源

这种TCP连接管理协议为经典的Dos攻击中的SYN洪泛攻击提供了环境。在这种攻击中,攻击者发送大量的TCP SYN报文段,而不完成第三次握手的步骤,随着这种SYN报文纷至沓来,服务器不断为这些半开连接分配资源(但从未用),导致服务器连接资源被消耗殆尽

防御措施SYNCookie 被部署在主流的操作系统中 工作方式如下:

当服务器接收到一个SYN报文时,它并不知道该报文段是来自一个合法的用户,还是一个SYN洪泛攻击的一部分。因此服务器不会为该报文生成一个半开连接。相反服务器生成一个初始TCP序号,该序列号是SYN报文段的源和目的IP地址与端口号以及仅有该服务器知道的秘密数的一个复杂函数(散列函数)。这种精心制作的初始序列号被称为"“Cookie”,服务器则发送具有这种特殊初始序列号的SYNACK分组。重要的是,服务器并不记忆该Cookie或任何对应于SYN的其他状态

如果客户端是合法的,则它返回一个ACK报文段。当服务器收到该ACK,需要验证该ACK是与前面发送的某些SYN相对应的,如果服务器没有维护有关SYN报文段的记忆,这是怎么完成的呢?正如你猜测的那样,它是借助于cookie来做到的。前面讲过对于一个合法的ACK,在确认字段中的值等于在SYNACK字段(此时为cookie值)中的值加1,服务器则将使用在SYNACK报文段中的源和目的地IP地址与端口号以及秘密数运行相同的散列函数。如果该函数的结果加1与在客户的SYNACK中的确认(cookie)值相同的话,服务器认为该ACK对应于较早的SYN报文段,因此它是合法的服务器则生成一个具有套接字的全开的连接

在另一方面,如果客户没有返回一个ACK报文段,则初始的SYN并没有对服务器产生危害,因为服务器没有为它分配任何资源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章