计网tcp部分面试总结
tcp报文格式:
序列号:通过SYN传给接收端,当SYN为1,表示请求建立连接,且设置序列号初值,后面没法送一次数据,就累加数据大小,保证包有序。
确认应答号:下次希望收到的数据序列号,发送端收到后可认为这个序列号之前的数据都接受了,保证不丢包。
控制位:ACK为1时,确认应答字段生效;RST为1时,表示连接异常必须强制断开;SYN前面说了;FIN为1时,表示请求断开连接。
校验和:检查数据在传输过程中是否有改动。
TCP:面向连接(一对一)、可靠(保证报文有序完整到达接收端)、基于字节流(下面会解释)的协议。
如何唯一确定一个 TCP 连接:源ip,源端口,目标ip,目标端口
UDP:区别:TCP:面向连接,可靠,基于字节流传输,一对一,有拥塞控制,流量控制,首部开销大;UDP:无连接,不可靠,基于包传输(不拆分),支持一对一,一对多,多对多,无拥塞控制,流量控制,首部开销小(只有源端口目标端口);应用:TCP用于文件传输,https;UDP用于直播
三次握手:
开始都是关闭状态。服务器先监听某个端口,处于listen。
1.客户端发送第一个SYN报文请求建立连接,其中SYN=1,随机初始化序列号,处于SYN_SENT,。
2.服务端收到SYN报文,回一个SYN报文,SYN=1(我也请求连接),ACK=1(表示确认收到),确认应答号=客户端的序列号+1(表示我希望下一次收到这个序号),也随机初始化序列号,处于SYN_RCVD。
3.客户端收到SYN报文,回一个应答报文,ACK=1(表示我收到了你的报文),确认应答号=服务端序列号+1,这次报文可以带数据,客户端处于ESTABLISHED,服务端收到应答报文,也进入ESTABLISHED。连接建立完成。
为什么是3次握手,不是2次,4次:
1、3次握手可以确认双方的收发能力,同步双方的初始序列号。2次握手服务端不能确定客户端是否收到自己的报文。
2、避免在旧连接中发送数据,举例客户端先发SYN报文,seq=90,然后断开了,客户端重连,又发送SYN报文,seq=100要建立新连接;现在旧报文(seq=90)先到了,服务端回一个SYN+ack报文,确认号91,客户端收到后,发现确认号不是101(这个他能算出来),就会发送RST报文,服务端收到后释放连接,整个过程是没有发送数据的。那要是服务端在收到RST报文前收到了新SYN,就会回一个challenge ACK报文(收到乱序的SYN时就会回一个),是上一次的确认号91,那客户端收到还是会发RST报文。若是2次握手:当服务端收到syn,就进入ESTABLISHED,第2次握手就可以发数据给客户端,客户端收到后会rst,即这段时间发送的数据全浪费了。
至于4次握手,3次已经能建立可靠连接,也就没必要4次。
第一次握手丢失:客户端一直收不到服务端SYN-ACK报文,会超时重传,且每次超时时间翻倍,达到最大重传次数或者最大超时时间,断开连接。
第二次握手丢失:客户端一直收不到SYN-ACK,会超时重传SYN,服务端收不到第3次握手,也会重传SYN-ACK,服务端收到重传的SYN也没用,当双方达到最大重传次数或者最大超时时间,断开连接。
第三次握手丢失:服务端收不到ACK应答报文,会超时重传第2次握手,直到达到次数。
四次挥手:
- (假设)客户端发送FIN报文,请求关闭连接,处于FIN_WAIT_1
- 服务端收到报文,发送ACK应答报文,进入CLOSE_WAIT状态,客户端收到ACK后进入FIN_WAIT_2状态
- 服务端处理完数据,也向客户端发送FIN报文,进入LAST_ACK
- 客户端收到FIN报文,回一个ACK,进入TIME_WAIT,服务端收到ACK,关闭连接,客户端在2MSL后关闭连接。
为什么要4次挥手:客户端发送FIN,表示我不发了但我还能收,你赶紧把剩下数据发过来,服务端收到先应答确认收到,然后处理数据,处理完毕,发送FIN表示我也不发了,客户端收到回个ACK确认收到,结束。所以是4次。但若被动方没数据发送且开启了tcp延迟确认机制(当发送不带数据的ack时,会等待一段时间,若有数据就一起发送),就会23合并。
第一次挥手丢失:客户端收不到ACK应答,会超时重传,达到次数就断开连接。
第二次挥手丢失:服务端收到FIN回一个ACK进入close_wait,但ACK报文是不会重传的,所以客户端会触发超时重传FIN,达到次数,就断开连接。
第三次挥手丢失:服务端处理完数据,发送FIN丢失,若一直收不到应答,就会重传,达到次数断开,且客户端处于FIN_WAIT_2等待FIN是有时间限制的,达到时间,也会断开连接
第四次挥手丢失:客户端会ACK进入TIME_WAIT,2MSL后关闭,服务端收不到ACK应答,会重传FIN,客户端处于2MSL时,若收到FIN会重发ACK,重置2MSL定时器。
为什么需要TIME_WAIT状态(为什么要等待一段时间):防止第4次握手丢失,等待服务端FIN重传,若直接关闭,重传的FIN可能被新的相同四元组连接接收到,数据错乱,所以要等待一段时间。那为啥等待2MSL: MSL:报文最大生存时间,1MSL第4次挥手ACK不管到没到肯定消失,若到了,不用重传,没到肯定会在1MSL后重传,重传过来最多1MSL。1MSL所以2MS能保证若重传了FIN我一定能收到。至于不继续等3MSL,4MSL:连续丢包的概率很小,忽略它比等待性价比高。
TIME_WAIT过多危害:客户端过多(主动发起关闭连接方)(会占用端口资源,若占满了就不能和当前服务器的当前端口建立连接了,但是端口是可以复用的,所以可以和其他端口或其他服务器建立连接,因为tcp是由四元组确定。服务端过多,不会导致端口资源受限,因为不同的客户端与服务端建立tcp连接使用的四元组肯定不同,端口可以复用,但tcp连接太多会占用系统资源,如文件描述符,内存等。
不要优化TIME_WAIT,服务端若想避免过多的TIME_WAIT,就让客户端去断开,让分布在各处的客户端承受TIME_WAIT。服务器主动请求断开连接进入TIME_WAIT的情况:http长连接超时、长连接请求达到数量也会主动关闭这个长连接。
挥手过程中若服务端的FIN比它发送的数据包先到:FIN先到就是乱序的,会加入到乱序队列,不会进入TIMEWAIT,等待数据到达,检查顺序,再处理FIN,进入TIMEWAIT
服务器出现大量close_wait的连接的原因:服务器内部由大量数据处理,都没处理完,或者出bug了;解决:停止程序,改bug。
tcp连接中一段断电与进程崩溃的区别:客户端断电即主机宕机:服务端无法感知到客户端宕机,所以搞了个保活机制,每隔一段时间发送一个探测报文(你还在吗),若连续几个探测报文都没响应就会认为当前tcp连接死亡;若期间客户端重启了,收到探测报文,内核会发送RST。若是进程崩溃:内核回回收进程资源,并完成挥手操作。
websocket和socket:socket=ip+端口+协议,他是一套标准,实现对下层协议栈的高度封装,屏蔽网络细节,方便网络编程。Websocket是应用层协议,是全双工的,解决http/1.1半双工的问题,服务器可以主动推送消息。他与http/2很像,都用2进制传输,都是全双工,但他只在提供实时双向通信如聊天室,http/2旨在提高性如浏览器。
socket编程建立连接的过程:双方初始化socket得到文件描述符(客户端是传输描述符,服务端是监听描述符),服务端bind,socket绑定ip和端口,调用listen监听,调用accept等待连接成功接收socket,客户端connect,向服务端ip和端口请求连接(这个函数完成3次握手),服务端accept返回通信的socket,自此双方都有传输的socket即可通信。客户端断开调用shutdown/close,则服务端处理完数据后也会调用shutdown/close,连接关闭。
listen的baklog参数意义:就是设置全连接(accept)队列长度。
服务端bind,但没有listen,客户端connect:肯定连不上,回RST。
没有accept还能建立连接吗:可以。调用listen监听会为socket分配2个队列,全连接半连接,收到第一次握手,socket放入半连接队列,收到第3次握手,把socket移到全连接队列,accept()等待全连接队列有连接就取出使用。所以accept不参与建立连接过程。
既然 IP 层会MTU,为什么 TCP 层还需要 MSS 呢:若tcp层不分片,全交给ip层分片,当某一个ip分片丢失,接收方的ip层无法组成一个完整的ip报文,也就无法把数据给tcp层,也就不会发ACK响应回去,发送方的tcp触发超时重传,重发整个tcp报文给ip层因为不分片,则ip层所有的分片都要重发,所以tcp要分片。
SYN攻击:若短时间伪造大量不同ip的SYN发送给服务端,服务端回复SYN+ACK不会收到应答,慢慢的占满SYN队列,后面的SYN就会被丢弃。解决方法:防火墙过滤;增大半连接队列;减小SYN+ACK重传次数或超时时间,加速断开连接;开启syncookie,当收到SYN时,不放入半连接队列,会发送一个带特殊序列号的SYN+ACK,当收到ack后才建立连接。
SYN报文在哪些情况下会被抛弃:1.半连接队列满了2.半连接队列没满,全连接队列满了3.半连接全连接都没满,但当前半连接队列长度>max_syn_backlog的3/4,也会丢弃
如何增大全连接队列:全连接长=min(somaxconn(系统设定的全连接长度上限),backlog(listen函数的参数,我们设定的长度));
如何增大半连接队列:半连接长=max(max_syn_backlog系统设定的半连接队列上限, 全连接长)*2,所以要增大3个参数
客户端断开上线发送SYN:关键就在于源端口号是否与之前的相同:若SYN端口号与历史连接不同,那就建立新的连接,旧连接有保活机制让他死亡。若端口号与历史连接相同:服务端收到SYN后发现序列号不对,会回一个challengACK再次确认(收到了乱序的SYN),客户端收到,发现ack不对,回RST。
如何不杀死进程关闭TCP连接:伪造一个RST报文,先通过工具killcx发送四元组相同的SYN,服务端会回一个challengeACK(收到了乱序的SYN),就拿到它的确认号了,就用这个确认号做RST的序列号发给服务端。
处于TIME_WAIT的客户端收到SYN:这里就不会发challengack了,看SYN的序列号和时间戳是否合法:若序列号比客户端确认号大,且时间戳也比客户端最后收到的报文时间戳大,就合法。直接重用建立连接。不合法回一个第四次挥手的ACK,服务端收到发现不对,回RST。处于time_wait收到rst:有一个参数,若是0就断开连接,若是1就丢掉RST。
TCP如何保证可靠传输:3次握手建立连接:确认通信实体存在;序列号、确认号保证数据有序完整到达;校验和校验数据是否有改动;重传机制(解决丢包问题);流量控制(控制发送方发送速度)、拥塞控制(根据网络拥堵情况控制发送速率)
重传机制:1.超时重传:当某一方收不到响应,会超时重传,超时时间RTO略大于RTT(往返时延),且每重传一次,时间会翻倍,所以他的问题就是超时周期可能较长(可能要一直发到最大重传次数或者达到最大超时时间)。2.快速重传:不以时间驱动,以数据驱动,举例:发送方发送seq1,服务端回ack2,发送端发seq2丢了,然后他发seq3,4,5(没有超时重传),接收端回3个ack2(表示我希望接收2,你给我发3,4,5干嘛),发送方继续发当连续收到3个相同的ack(2次可能是乱序,3次可能是丢包,4次更可能是丢包),触发快速重传,重传seq2,接收方回ack6。但它的问题就是若seq2,seq3都丢了,可收到的都是ack2,那到底重传1个还是全部重传。3.SACK:在tcp头部加SACK,告诉发送方我收到了哪些数据,配合快速重传就可以知道到底要重发哪些包。
流量控制:发送*无脑发占满接收方缓冲区,接收方处理不过来,只能丢包浪费资源,所以要考虑接收方的接收能力来发送数据,这就是流量控制,通过滑动窗口实现。窗口就是一块缓冲区,把数据放在里面等待处理。Tcp报文中有个window字段;发送方告诉发送方我还有多少剩余空间,发送方根据这个大小来发送(窗口大小会变)。
窗口关闭:当收到窗口大小为0,发送方就不发了,当处理完后,发送方发窗口非0的ACK,若丢失就会死锁,所以当一方收到0窗口通知,会开始计时,超时会发送窗口探测报文,若收到ACK说窗口还是0,就重启计时器。若不是0,就可以正常发了。
拥塞控制:根据网络拥堵情况控制发送窗口的大小,防止网络被淹没,导致丢包。所以发送窗口的值=min(拥塞窗口cwnd,流控窗口rwnd)。
如何判断网络是否出现拥堵:发生重传。拥塞控制4个算法:刚开始慢启动:发送方每收到一个ACK,拥塞窗口大小+1,呈指数上升;当cwnd>=ssthresh(慢启动门限),使用拥塞避免算法,每收到一个ACK,cwnd增加1/cwnd,呈线性上升;发生超时重传启动拥塞发生算法,ssthresh变为cwnd/2,cwnd置为初始值,又回到慢启动,但这样像坐过山车,太激进。
当发生快速重传,ssthresh = cwnd/=2,然后使用快速恢复算法,cwnd+3,重传数据包,若再收到重复的ACK,cwnd+1(目的是你赶紧把丢的包发给我,我给你留了空间),收到新的ACK,表示丢的包已经重传,恢复过程就结束了,cwnd的值置为ssthresh,又进入拥塞避免;可以看到只要发生重传即阻塞,cwnd,ssthresh都会减小来应对网络拥堵,只是2种算法减小的程度不同。
如何区分流量控制和拥塞控制:流量控制是根据接收方的窗口大小控制发送方的速度,一方占满接收窗口,丢包浪费资源,拥塞控制是根据网络拥堵情况控制发送方的速度,防止网络被淹没,导致丢包。
TCP抓包:工作再看
close与shutdown:调用close会把文件描述符的减一,当减为0,关闭通信socket,所以他不影响其他进程;shutdown是根据参数选择切断所有读/写/读写,影响所有进程。shuwdown(fd, 0(关闭读),1(关闭写),2(关闭读写));
TCP优化:三个角度优化:三次握手、四次挥手、数据传输
三次握手优化:客户端优化:适当减少SYN报文和SYN+ACK报文的最大重传次数和最大超时时间,增大半连接队列和全连接队列以应对多个连接。四次挥手优化:主动方:减少FIN超时重传次数;当收到ACK进入FIN_WAIT_2状态,若是close函数关闭的连接,关闭读写,收到FIN会回RST,直接关闭,就是3次挥手了(当被动方没有数据要发送,且开启TCP延迟确认机制(默认开启,即若没数据发送,ACK会等一下看有没有数据一起走),2,3次回合并,close/shutown都是3次挥手),是孤儿连接(不能读写,但占资源),可设置孤儿连接的数量参数,超过孤儿连接的数量直接关闭;若是shutdown关闭的连接,可以指定关闭写不关闭读,就是正常的四次挥手,可以设置FIN_WAIT_2的时间。被动方:FIN重传次数,若是close设置孤儿连接的数量;传输数据优化:调整接收方窗口大小,关键就是调整内核缓冲区大小,调整至与网络传输能力匹配,即缓冲区上限设为带宽时延积。
TCP是面向字节流的协议:UDP传输,是不分片的,直接加个头部给网络层,一个消息对应一个报文,所以他是面向报文的协议,而TCP数据可能会被分成一个个报文段发出去,具体是这样的:在TCP里数据没有边界,即发送方把数据视为一连串的字节流,分片时不用管在哪切割,像流一样有序发送,到了再组装,所以他是面向字节流的协议。但是会出现粘包问题:如2个消息:hi,hello,可能是这样hihello,h,ihello…因为没有边界。所以一般设置特殊字符做边界如http设置回车、换行做边界;还可以定义消息结构如包头+包体,包头固定大小,且含有包体大小。
为什么 TCP 每次建立连接时要随机初始化序列号:主要是防止历史报文被新的相同四元组的来连接接收,如一个连接断了,但是还有历史报文,此时又建立了一个相同四元组的连接,他能接收到历史报文,若他的序列号与之前相同,那被完美接收了,数据错乱。所以要随机初始化序列号,保证每次连接序列号不同。使用随机算法,但不能完全避免(有上限,会回绕),所以要配合时间戳,若数据包时间戳不是递增的,说明他是过期的。2者配合就能解决历史报文被接收的问题。序列号怎么变化的:目前是m,发送n的数据(若是SYN/FIN,n取1),则变为m+n,确认号:目前是m,表示我想收到序列号为m的数据,收到后变为m+数据长
http和tcp的保活是一个东西吗:http的keep-alive也叫长连接,该功能是应用程序实现的,使得一个tcp处理多个请求应答,它的超时时间由keepalivetimeout参数设置。tcp的keepalive是保活机制,是内核实现的(tcp都在内核里),内核发送探测报文确认对方是否在线。
TCP 协议有什么缺陷:建立连接的耗时(三次握手,tls四次握手)、接收窗口队头阻塞、网络迁移要重新建立TCP(ip变了),更新很难(tcp在内核实现)。
UCP实现可靠传输:通过QUIC实现,目前的http/3就使用了基于UDP的QUIC协议。QUIC把http/2,TCP,UDP,tls1.3结合了。
建立连接:基于tls1.3+UDP一个rtt就协商出连接ID和通信密钥建立连接,比tcp更快。
拥塞控制:QUIC把TCP的拥塞控制算法在应用层实现,热插拔,方便更新。
解决队头阻塞:首先他支持并发传输(http/2),QUIC为每个流开一个窗口,多个流之间互不依赖,解决多个流的队头阻塞,但是单个流中若丢包还是会阻塞。
可靠传输(完整+有序):也有包号和应答号以及重传机制,但包号是递增的,即使你重传也不是原来的,所以在数据包中添加offset偏移量,接收端根据偏移量排序,以此来保证可可奥传输。这样设计的好处是计算RTT更精确,TCP计算有歧义:对于原始报文和重传报文返回的是相同的ACK,那RTT按谁算呢,QUIC通过包号直接能判断谁是原始的ACK谁是重传的ACK。
但是QUIC比TCP更加占用系统资源,所以2者各有长处。
TCP和UDP可以同时绑定相同的端口吗:可以,TCP和UDP在内核里是2个独立模块,主机收到数据包查看字段判断出是TCP/UDP,然后发给对应的模块处理,TCP/UDP处理好了在发给进程。多个TCP能绑定相同的端口吗:不行,四元组确定唯一的TCP,除非使用SO_REUSEPORT 端口复用。
用了TCP数据一定不会丢吗:数据会丢不可避免,但丢包tcp可以重传,实现传输层到传输层的可靠传输。但不保证数据到应用层是否可靠,所以还需要第三方服务器来对账。
TCP 有哪些机制用于提高传输效率:滑动窗口,实现一次性发送多条数据而不用等待回复,解决了接收方队头阻塞问题;快速重传:之前的超时重传必须达到最大重传次数或最大超时时间才会重传,快充穿实现发送端收到3个相同就会立刻重传;延迟确认:当发送不带数据的ACK,会等待一段时间,若有数据,一起发送过去。