C++ Linux Web Server 面试基础篇-计网(一)

⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生‍。
如果觉得本文能帮到您,麻烦点个赞呗!

近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支持一下呗。⭐️❤️
Qt5.9专栏定期更新Qt的一些项目Demo
项目与比赛专栏定期更新比赛的一些心得面试项目常被问到的知识点。

Linux Web Server项目虽然是现在C++求职者的人手一个的项目,但是想要吃透这个项目,还是需要一定的基础的,以项目为导向,进行基础的学习。

涵盖了计算机网络(网络编程)常见的知识点和常见的操作系统知识

博主参加过大大小小的互联网厂和银行的秋招和春招的笔试与面试,整理了下面的2万7千字的长文(都是干货,写作不易啊),喜欢,觉得有帮助的,欢迎订阅专栏,后续有很多优质的文章进行更新,有任何疑问,欢迎留言!
在这里插入图片描述

面试基础篇-计网 一

    • 1、说一下TCP三次握手的过程
    • 2、三次握手的必要性
    • 3、四次挥手的过程
    • 4、四次挥手的必要性
    • 5、第四次挥手为什么要等待2MSL?
    • 6、UDP通信的时候丢包情况如何避免?
    • 什么是TCP粘包/拆包?发生的原因?
      • 原因
      • 解决方案
    • 参考资料:

1、说一下TCP三次握手的过程

C++ Linux Web Server 面试基础篇-计网(一)_第1张图片

C++ Linux Web Server 面试基础篇-计网(一)_第2张图片

  • 初始状态:客户端处于 closed(关闭)状态,服务器处于 listen(监听) 状态。
  • 第一次握手:客户端发送请求报文将 SYN = 1同步序列号和初始化序列号seq = x发送给服务端,发送完之后客户端处于SYN_Send状态。(验证了客户端的发送能力和服务端的接收能力)
  • 第二次握手:服务端收到 SYN 请求报文之后,如果同意连接,会以自己的同步序列号SYN(服务端) = 1、初始化序列号 seq = y和确认序列号(期望下次收到的数据包)ack = x+ 1 以及确认号ACK = 1报文作为应答,服务器为SYN_Receive状态。
  • 第三次握手: 客户端接收到服务端的 SYN + ACK之后,知道可以下次可以发送了下一序列的数据包了,然后发送确认序列号 ack = y + 1和数据包的序列号 seq = x + 1以及确认号ACK = 1确认包作为应答,客户端转为established状态。(分别站在双方的角度上思考,各自ok)

2、三次握手的必要性

第三次握手主要为了防止已失效的连接请求报文段突然又传输到了服务端,导致产生问题。

  • 比如客户端A发出连接请求,可能因为网络阻塞原因,A没有收到确认报文,于是A再重传一次连接请求。
  • 连接成功,等待数据传输完毕后,就释放了连接。
  • 然后A发出的第一个连接请求等到连接释放以后的某个时间才到达服务端B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段。
  • 如果不采用三次握手,只要B发出确认,就建立新的连接了,此时A不会响应B的确认且不发送数据,则B一直等待A发送数据,浪费资源。

3、四次挥手的过程

C++ Linux Web Server 面试基础篇-计网(一)_第3张图片

  1. A的应用进程先向其TCP发出连接释放报文段(FIN=1,seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
  2. B收到连接释放报文段后即发出确认报文段(ACK=1,ack=u+1,seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。
  3. A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
  4. B发送完数据,就会发出连接释放报文段(FIN=1,ACK=1,seq=w,ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。
  5. A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最大报文段生存时间)后,A才进入CLOSED状态。B收到A发出的确认报文段后关闭连接,若没收到A发出的确认报文段,B就会重传连接释放报文段。

4、四次挥手的必要性

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。但是在关闭连接时,当Server端收到Client端发出的连接释放报文时,很可能并不会立即关闭SOCKET,所以Server端先回复一个ACK报文,告诉Client端我收到你的连接释放报文了。只有等到Server端所有的报文都发送完了,这时Server端才能发送连接释放报文,之后两边才会真正的断开连接。故需要四次挥手。

5、第四次挥手为什么要等待2MSL?

  • 保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。
  • 防止已失效的连接请求报文段出现在本连接中。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。

6、UDP通信的时候丢包情况如何避免?

比如向我们现在这样通话,假设出现丢包的情况,应该如何处理

没有考虑过

参考答案:

UDP通信—收包率低/丢包率高的

原因:

(1)缓存太小,不能及时接收数据

连续多个UDP包超过了UDP缓冲区大小,比如

1、UDP包过大

2、UDP发包速率过快,突发大数据流量超过了缓冲区上限

(2)recvfrom()接收到数据之后处理速度太慢

如果数据接收和处理是连续进行的,那么可能由于数据处理过慢,两次recvfrom() 调用的时间间隔发过来的包丢失

解决办法

  • UDP包过大

解决办法:增加系统发送或接收缓冲区大小

int nBuf = 32 * 1024 

setsockopt(s,SOL_SOCKET, SO_RCVBUF, (const char*)&nBuf, sizeof(int));
setsockopt(s,SOL_SOCKET, SO_SNDBUF, (const char*)&nBuf ,sizeof(int))
  • 发包速率太快

解决办法:增加一个应答机制,处理完一个包后,在继续发包

recvfrom() 接收到数据之后处理速度太慢

服务器程序启动之后,开辟两个线程,一个线程专门用于接受数据包,并存放在应用层的缓冲区;另外一个线程用于专门处理和相应数据包请求,避免因为处理数据造成数据丢失。本质上还是增大了缓冲区大小,只是将系统的缓冲区移动到了自己的缓冲区。

  • 最复杂的方式

在应用层实现丢包重发机制和超时机制,确保数据包不丢失。

什么是TCP粘包/拆包?发生的原因?

一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和粘包问题。

原因

1、应用程序写入数据的字节大小大于套接字发送缓冲区的大小.

2、进行MSS大小的TCP分段。( MSS(网络传输最大值)=TCP报文段长度-TCP首部长度)

3、以太网的payload大于MTU进行IP分片。( MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。)

解决方案

1、消息定长

2、在包尾部增加回车或者空格符等特殊字符进行分割

3、将消息分为消息头和消息尾

4、使用其它复杂的协议,如RTMP(实时信息传输协议)协议等。

TCP粘包、拆包及解决办法

为什么常说 TCP 有粘包和拆包的问题而不说 UDP ?

由前两节可知,UDP 是基于报文发送的,UDP首部采用了 16bit 来指示 UDP 数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。

而 TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 并没有把这些数据块区分边界,仅仅是一连串没有结构的字节流;另外从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段,基于上面两点,在使用 TCP 传输数据时,才有粘包或者拆包现象发生的可能。

为什么会发生 TCP 粘包、拆包?

  • 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
  • 待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
  • 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
  • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

粘包、拆包解决办法

由于 TCP 本身是面向字节流的,无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,归纳如下:

  • **消息定长:**发送端将每个数据包封装为固定长度(不够的可以通过补 0 填充),这样接收端每次接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
  • **设置消息边界:**服务端从网络流中按消息边界分离出消息内容。在包尾增加回车换行符进行分割,例如 FTP 协议。
  • **将消息分为消息头和消息体:**消息头中包含表示消息总长度(或者消息体长度)的字段。
  • 更复杂的应用层协议比如 Netty 中实现的一些协议都对粘包、拆包做了很好的处理。

参考资料:

计算机网络的部分图片:https://interviewguide.cn/

最后,最后
如果觉得有用,麻烦三连⭐️❤️支持一下呀,希望这篇文章可以帮到你,你的点赞是我持续更新的动力

你可能感兴趣的:(项目与比赛经历,c++,linux,计算机网络,面试,Web,Server)