TCP/UDP相关面试常问

TCP/UDP相关面试常问_第1张图片

哪个函数控制了三次握手的连接

没有哪个具体的函数控制了连接这种说法,客户端使用connect函数(udp中的connect不进行握手过程)将ip和port拷贝到tcp协议栈中,具体的三次握手连接是在协议栈中进行的,当第一次握手成功时,服务端会生成一个相应结点,这个结点保存在半连接队列中,它能够表示一个tcp连接的11个状态,sendbuf,recvbuf也是这个结点的一部分,每个数据包都包含了五元组的信息,故第三次握手时可以通过五元组信息在半连接队列中找到对应结点放入全连接队列中(注意这里的转移过程不是拷贝)调用accept函数会为之分配一个fd,可以用这个fd来表示它。 这个结点的生命周期一直到通信结束释放。
其实tcp通信的实体就是这个结点:它还有其他的称呼,tcp控制块或者tcb。

这里可能大家就有疑问了,那listen()函数用来干什么,listen是用来监听服务端套接字的 (只有监听着这个套接字才能收到客户端发送的连接请求) 保证客户端可以正常与服务端建立三次握手,同时使用了listen函数后也能够在协议栈中判断连接数量来决定是否接受新的连接请求。

半连接队列中结点的生命周期:当第二次握手得不到响应,一直重传第二次握手,到达一定次数后就会释放这些结点资源

为什么要三次握手:

第一次握手:Client什么都不能确认,Server确认了对方发送正常,自己接收正常

第二次握手:Client确认了:自己发送、接收正常,对方发送正常、接收正常;Server确认了:对方发送正常,自己接收正常

第三次握手:Client确认了:自己发送、接收正常,对方发送正常接收正常;Server 确认了:对方发送正常,接收正常,自己发送正常,接收正常。

为什么两次握手不行,假设Client给Server发送一个连接请求过去(第一次握手),但由于网络原因,迟迟没有到达Server端,所以Client端迟迟收不到对方的ACK回应(第二次握手),这时假设Client不想建立本次连接了,但这个由于网络延迟等原因发送的请求建立包(第一次握手)又到达了Server端,对于Client端来说这是一个没用了的请求连接,但Server端会单方面建立这次连接。

TCP/UDP相关面试常问_第2张图片

有发送消息的步骤的函数:connect , send, close, shutdown
recv看似收到消息后会回复ack确认,但实际这种现象是协议中对收到数据的自我回应,不算在上边。

1:大量time_wait 原因:由图可看出,只有主动发起close的一端才会有time_wait阶段,服务器如果出现大量time_wait,说明是程序中的哪个位置设置不当,服务器主动close了,这时候应该检查服务器程序逻辑代码

2:大量close_wait原因:当recv() == 0,说明对方发起了close断开连接,按照正常情况我们应该直接close来关闭连接,但如果当recv() == 0 出现时,我们在这种情况下还有很多业务代码要处理,这些业务代码就会耽搁close的时间,导致迟迟不能close。

if(recv == 0) {
    //假设很多业务逻辑代码
    close(fd);
}

正确做法,要不直接close(fd),要么另外开一个线程,去执行这些业务逻辑代码,异步实现。

3:大量fin_wait2

当遇到这种情况,与迟迟收不到对方的close有关,fin_wait1和fin_wait2一样,没有其他方式彻底关闭本次连接(因为在它们主动调用了close之后就一直在等待对方的回应),这个时候只有唯一的办法就是使用kill 来关闭连接。

4:epollrdhup和epollhup

当对方关闭写端时,我方触发EPOLLRDHUP事件。

当对方读写端都关闭,我方触发EPOLLHUP事件,但实际上我们只需要知道对方的读端关闭,就会触发EPOLLHUP,(因为一个连接的正常关闭都是先对方关闭写端,我方收到消息后关闭读端,想一想FIN断开连接的过程。)这也是为什么有EPOLLHUP,EPOLLRDHUP,但没有EPOLLWRHUP的原因,因为没必要

延申:SIGPIPE当往一个读端关闭的管道或socket写入数据,触发SIGPIPE信号,errno被设置为EPIPE。跟EPOLLHUP类似的效果。

5:列举read(recv) 和 write(send) 不同的情况
TCP/UDP相关面试常问_第3张图片

6:四次挥手后等待2MSL作用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBMVkFLm-1659455527995)(C:\Users\ASUS\Pictures\博客图片\20180208112533496.jpg)]

MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃

第一点:确保四次挥手的完成,我们要知道TCP有超时重传机制,在发送一个数据包后(这里假设是第三次挥手的数据包),若在规定的时间内未收到确认包,则会认为数据包丢失,重新发送一次数据包;
在此情况下(客服端向服务端主动发起关闭),假设服务端设置了RTO(Retransmission Time Out,超时重传时间)是2MSL(这个2MSL和四次挥手后的2MSL不是同一个,只是刚好时间长度一样)。服务端发送的FIN+ACK包(第三次挥手)在MSL的最后一个时刻传输到了客户端,而客户端回发了ACK包,此时客户端开始了TIME-WAIT(这里说的就是四次挥手后的2MSL等待);如果这个ACK包经过MSL还未发送到,此时服务端已经超过2MSL为收到ACK包的回应了,服务端就超时重传FIN+ACK包(第三次挥手),**注意注意:这种极端情况下,当服务端超时重传(第二次发送第三次挥手),对于客户端四次挥手后等待的2MSL来说,它从开始等待到现在这个时刻,已经过去了1MSL,**第二次发送的第三次挥手的生存时间最大是1MSL,所以如果不出意外,客户端四次挥手后等待的2MSL(目前还需等待1MSL)内一定可以收到对方的重传(第二次发送第三次挥手)。

如果第二次发送第三次挥手由于网络等原因还是失败了,对于客户端来讲,四次挥手后等待的2MSL并没有收到任何重传,所以客户端进入close状态,但是服务端也不会傻傻的一直等着,有一个保护机制, FIN 报文的重传次数是受限的,具体由 tcp_orphan_retrie这个参数决定。服务端会重试一段时间,重试次数达到限制,服务端就主动断开了。【达到重试次数时,相当于服务端那边认为已经是出了什么问题,不可能收到这个ACK包,主动断开了】

第二点:防止新的tcp连接收到上一次由于网络等原因迟到的数据包,如果没有这个2msl等待时间(在2msl等待期间,这个端口在默认情况下不能被再使用,不过可以在setsockopt函数中设置SO_REUSEADDR选项,从而不必等待2MSL时间结束再使用此端口。),假如下一次建立的新连接刚好使用了与上一次一样的端口号,若上一次连接中,对方有由于网络等原因迟到的数据包,有可能将会被这次新的连接接收到而引起错误,每个数据包都是有最大生存时间的,等待2msl确保新的连接不会收到上一次的由于网络等原因迟到的数据包。

了解UDP可靠传输吗?

先了解两个概念:

  • 滑动窗口是接受数据端使用的窗口大小,用来告知发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。
  • 拥塞窗口是数据的发送端,拥塞窗口不代表缓存,拥塞窗口指某一源端数据流在一个RTT内可以最多发送数据包

当问到UDP可靠传输时, 无非就是将TCP的特性在应用层实现一遍,所以需要先分析TCP的问题。

udp的应用场景举例,以及优势:

对于dns,没必要用tcp那么麻烦的三次握手四次挥手,就算失败,设置一个定时器,重传请求就是了

对比tcp优点:重传策略灵活,对于tcp,,由内核协议栈实现,而udp重传策略比较灵活,可以在应用层把tcp的这些特性都实现,tcp升级难度大,也可以理解为,如果修改了tcp的策略,它是对所有应用都会生效,而udp的应用层封装可以根据应用的不同进行更新。

适用于实时性要求比较高的情况。

QUIC方法

分析对于TCP存在的问题:

  1. 升级难,完全由内核实现,比如对于重传策略,udp就能在应用层更灵活的设置,但tcp不行,完全使用了内核实现的策略。
  2. tcp建立/断开连接的消耗延迟
  3. tcp存在队头阻塞的问题(包括发送端队头阻塞, 接收端队头阻塞)
  4. 网络迁移需要重新建立tcp连接

解决方案:

2:对于QUIC而言,因为是基于UDP的,所以无需建立/断开连接的消耗;

3:对于队头阻塞问题,这里就要涉及到http/2的使用,抽象了stream的概念,实现了http并发传输(并发发送多个http请求),一个stream就代表http/1.1里的请求和响应,每个帧的头部会携带stream ID以及offset偏移量信息,所以接收端可以根据这些信息有序组装,但是由于http/2多个stream请求都是在同一条TCP连接上传输,这就意味着多个stream共用一个滑动窗口,因为只有一个滑动窗口,所以会出现TCP层队头阻塞问题,而对于QUIC协议,它借鉴了http/2中stream的概念,但是QUIC给每一个stream都分配了一个独立的滑动窗口,这样使得一个连接上的多个stream没有依赖关系,各自控制各自的滑动窗口,StreamA被阻塞后,不影响streamB,streamC的读取,而对于http/2,所有stream都在一条TCP连接上,streamA阻塞后,streamB,streamC必须阻塞

4:比如从5g变到了wifi,由于QUIC具有连接ID,可以通过这个连接ID来标记通信的两个端点,即使ip地址变化,只要仍保存有上下文信息(比如连接ID,TLS密钥),就可以无缝复用原连接,没有丝毫卡断感,而对于TCP,需要重新建立建议,包括TCP三次握手,TLS四次握手的时延,以及tcp是慢启动的一种方式,给用户的感觉就是网络突然卡顿了

quic补充

RTT计算的歧义:
quic报文中的packet number是严格单调递增的,即使是重传报文,也同样递增,这样就能避免tcp中出现的RTT计算的歧义(TCP中的RTO是基于RTT计算的),因为tcp中的重传报文序号不会递增,还是原来那个序号。

QUIC支持乱序确认:
上边这样做还有一个好处,不再像TCP那样必须有序确认(即3,4,5到了,到2没到,回复给发送端的ack就会一直是2,当2到了,直接回复5,也就是就这回复值ack表明前边的数据一定是到达了的,并且2对应2,3对应3,序号与ack号是对应的),QUIC支持乱序确认(比如发送了3,4,5 ,过程中4,5顺利到达接收端,3丢失了,超时重传,但此时会把3编号为6,因为packet number严格递增,此时接收端接收6,但接收端知道这是3的数据,那么这是怎么做到的呢?因为每个包都包含了它对应的stream字段以及偏移量offset字段,通过它们就能确定这是哪一个包了)。

quic的流量控制:
通过window_update帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据。
通过BlockFrame告诉对端由于流量控制被阻塞了,无法继续发送数据。
quic实现了两种级别的流量控制,stream级别和connect级别(所有stream和起来的概念),一般讨论stream级别即可。
因为udp没有实现流量控制,所有quic自己实现。当接收窗口成功接收到连续的一半接收窗口的大小的数据时,最大接收窗口向右移动,同时向对端发送窗口更新帧,当发送方收到接收方的窗口更新帧后,发送窗口的有边界也会往右扩展,以此达到窗口滑动的效果。
但如果没有达到连续的一半接收窗口的大小时,那么接收窗口还是无法滑动,但这只影响一个stream。

quic的拥塞控制
跟tcp差不多,可以说照搬,但是由于quic的拥塞控制的实现是在应用层,对于不同的应用可以灵活的调整拥塞控制策略

quic可以更快的建立连接
quic首次连接需要1RTT,服务器通过CA证书私钥加密后,返回给客服端保存起来。下一次再连接时只需要0RTT,
简单来说,通过diffie-hellman算法来进行密钥的交换,可以通过该算法在双方互不知情的情况下建立加密通信,而对于普通的http/2协议,需要进行tcp握手以及tls握手,至少需要2.5RTT,虽然http/2是多路复用技术,但如果出现队头阻塞,效率可能还没http1高

KCP方法

比如kcp,它是一个纯算法实现,相对于TCP,KCP以浪费10%-20%的带宽,换取比TCP快30%-40%的传输速度,并且最大延迟降低3倍的传输效果。
特性:

  • RTO翻倍与不翻倍
    对于TCP超时重传时间是RTO2,如果连续丢3次就变成RTO * 8了,而KCP是1.5(实践证明这个更好),提高了传输速度
  • 拥塞控制方面
    KCP的优势在于可以完全关闭拥塞控制,非常自私的进行发送,但可能造成更为拥堵的情况。TCP大公无私,经常牺牲自己来减少网络拥塞,考虑大局。
  • 快在哪里
    没有使用任何系统调用接口,完全是用户层实现,无需建立/关闭连接。很多影响速度的参数都可配(RTO等)。

你可能感兴趣的:(tcp/ip,面试,udp)