【嵌入式软件工程师面经】Linux网络编程Socket

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 【嵌入式软件工程师面经】Linux网络编程Socket

一、什么是IO多路复用

       IO多路复用是一种有效的系统调用策略,它允许单个线程监视多个文件描述符,等待一个或多个输入/输出(I/O)通道变得就绪,也就是说,它们可以执行无阻塞的读写操作。传统的同步I/O操作要求执行一个系统调用来完成一个操作,比如读取或写入数据。当操作阻塞时(例如,如果数据还没有准备好被读取),调用线程会挂起,直到操作完成。这就意味着如果一个程序想要同时从多个来源读数据,它可能会因为等待一个操作完成而浪费CPU时间。

1.1 IO多路复用通过以下系统调用实现:

       select: 这个系统调用允许程序监视多个文件描述符,以了解是否有任何一个变成可读、可写或有异常待处理。当至少有一个文件描述符就绪时,select调用返回。

       poll: 类似于select,但是提供了更丰富的事件描述,并且解决了select的一些限制(如监视文件描述符数量的限制)。

       epoll: 这是在Linux系统中实现的,比select和poll有更好的性能,尤其是在处理大量文件描述符时。epoll可以在一组文件描述符中高效地管理多个事件,并且只通知用户程序哪些文件描述符实际上有事件发生。

       这些系统调用的优点是可以让线程在执行其他任务的同时等待I/O操作的完成。因此,在处理I/O密集型应用程序时,IO多路复用可以大幅提高效率,特别是在网络服务器和客户端应用程序中,它允许一个单线程来管理多个并发的客户端连接。

二、epool中et和lt的区别与实现原理

 epoll 是 Linux 下多路复用IO接口 select/poll 的增强版本。它在处理大量并发连接时能够提供更好的性能。epoll 支持两种触发模式:LT(Level Triggered,默认模式)和 ET(Edge Triggered)。

2.1 LT(Level Triggered)模式

       在 LT 模式下,epoll 将告诉你一个文件描述符是否就绪,而不关心之前是否已经告诉过你。这意味着,只要文件描述符还有数据可读,每次 epoll_wait 调用都会返回该文件描述符。这种行为类似于 select 和 poll 的传统行为。LT 模式较为简单,因为在任何时候,只要有数据可以处理,就会通知应用程序。

2.2 ET(Edge Triggered)模式

       与 LT 不同,ET 是一种高性能模式,只会在状态变化时通知一次,即文件描述符从不可读变为可读,或者从不可写变为可写时。如果你在处理完一个文件描述符后,没有把所有的数据都读取出来(比如,执行了一个非阻塞读操作),epoll 就不会再次告诉你该文件描述符就绪。

 ET 模式通常与非阻塞IO一起使用,以避免在读写操作时发生阻塞。由于 ET 模式只在状态发生变化时通知一次,因此应用程序必须确保它正确地处理了所有的数据,否则可能会丢失就绪事件的通知。ET 模式可以减少 epoll 事件被触发的次数,从而减少应用程序的CPU使用率,提高性能。

2.3 实现原理:

 epoll 的核心是一个事件表,这个表由内核维护。使用 epoll_create 创建一个 epoll 实例后,你可以使用 epoll_ctl 向这个事件表中添加或删除文件描述符。然后,使用 epoll_wait 等待事件的发生并返回。

  • 在 LT 模式下,只要文件描述符的状态与你请求监视的事件相匹配,epoll_wait 就会返回该文件描述符。
  • 在 ET 模式下,epoll_wait 只在监视的文件描述符的状态从不匹配变为匹配时返回。这就要求你在收到通知后处理所有的事件,因为你不会再为同一个状态得到第二次通知。

       在内部实现上,ET 模式比 LT 更复杂,因为它要求内核跟踪更多的状态信息,确保每个事件只通知一次。不过,因为它减少了事件通知的频率,所以对于高负荷的系统来说,这种额外的复杂度是值得的。

三、tcp连接建立的时候3次握手,断开连接的4次握手的具体过程

       TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在数据开始传输之前,TCP使用一个称为三次握手(Three-way Handshake)的过程来建立一个连接,在数据传输完成后,它使用四次挥手(Four-way Handshake)来终止这个连接。

3.1 TCP三次握手过程(建立连接)

       1.SYN:客户端发送一个TCP的SYN(同步序列编号)标志设置为1的数据包到服务器以初始化一个连接。客户端会选择一个初始的序列号(ISN)。

       2. SYN-ACK:服务器接收到这个SYN包后,如果接受连接请求,它将回复一个确认包,这个数据包中SYN和ACK(确认回应)标志位均被设置为1。服务器也会选择自己的初始序列号,并将确认序号设置为客户端的ISN加1,表示确认收到了客户端的SYN。

       3. ACK:最后,客户端再发送一个ACK包作为应答,这个ACK包的ACK标志位设置为1,序列号为客户端的ISN加1,确认号为服务器的ISN加1。

       完成了这三次握手,一个TCP连接就建立了,随后数据可以开始传输。

3.2 TCP四次挥手过程(断开连接)

       1. FIN:当一个端点完成它的数据发送任务之后,它会发送一个FIN(结束)标志位设置为1的数据包,以请求关闭连接。

       2. ACK:另一端收到这个FIN消息后,会发送一个ACK包作为应答,并且将确认序号设置为收到的序列号加1。

       3. FIN:这一端如果也完成了数据发送,会发送一个FIN包来确认连接的关闭。

       4. ACK:最初发送FIN请求的端点收到这个最后的FIN之后,会发送一个ACK作为最后的确认,并且会进入TIME_WAIT状态。这个状态会持续一个固定时间(通常是两个最大段生命周期MSL),以确保对方收到了最终的ACK包。

       在这个四次挥手过程结束之后,两边的端点都会关闭连接。重要的是要注意,TCP的连接关闭是一个双向的过程,每个方向都需要独立地进行关闭。

四、connect方法会阻塞,请问有什么方法可以避免其长时间阻塞?

       connect 方法通常会阻塞调用线程,直到TCP三次握手完成或者超时发生。然而,在某些应用场景中,长时间的阻塞是不可接受的,特别是在客户端需要处理多个并发连接或者在用户界面需要保持响应状态时。要避免 `connect` 导致的长时间阻塞,可以采用以下几种方法:

4.1 设置套接字为非阻塞模式:

       在调用 `connect` 之前,可以将套接字设置为非阻塞模式。在非阻塞模式下,`connect` 将立即返回,通常是 `EINPROGRESS` 错误码。这意味着连接尝试还在继续,并不表示失败。之后,你可以使用 `select`、`poll` 或 `epoll` 等多路复用系统调用来检测套接字何时变为可写状态,这通常意味着连接已经建立或者出现了错误。在这之后,可以调用 `getsockopt` 来获取套接字的 `SO_ERROR` 选项以确定 `connect` 调用是否成功。

4.2  设置套接字选项以缩短超时时间

       你可以通过设置套接字的选项来减少 `connect` 调用的默认超时时间。这可以通过设置 `SO_SNDTIMEO` 套接字选项来实现。

4.3  利用异步I/O(例如:`posix` 的 `aio_*` 函数或 `Linux` 的 `io_uring`):

       异步I/O允许你发起一个操作,然后立即返回,操作完成时你将得到通知。在 `Linux` 中,`io_uring` 是一种高效的异步I/O实现。

4.4. 使用多线程或多进程:

       在一个单独的线程或进程中调用 `connect` 方法,这样即使它阻塞,也不会影响到其他线程或进程的执行。这种方法会增加应用程序的复杂度,并需要处理多线程和多进程的同步和通信。

4.5  利用高级I/O框架或库:

       使用如 `Boost.Asio`(对于C++),`Twisted`(对于Python),`Node.js`(对于JavaScript)等异步网络库可以帮助你处理套接字的异步连接,这些库通常提供了非阻塞操作和异步回调功能。

       选择哪种方法取决于你的应用程序要求以及你的开发环境。例如,如果你的应用需要处理大量的并发连接而且对性能要求很高,可能需要选择非阻塞模式配合 `epoll`(在Linux系统上)或者使用某种异步I/O模型。如果只是偶尔需要建立连接,而且希望代码简单,那么使用多线程可能就足够了。

五、网络中,如果客户端突然掉线或者重启,服务器端怎么样才能立刻知道?

       在网络通讯中,如果客户端突然掉线或者重启,服务器端并不会立即知道,因为TCP/IP协议本身并没有提供这样的即时检测机制。这是因为TCP设计时考虑到网络的不稳定性,一些短暂的连接丢失并不会立即导致TCP连接失败。

以下几种方法可以帮助服务器端更快地检测到客户端的掉线或重启情况:

5.1 Keepalive 机制:

        TCP协议支持Keepalive机制,可以通过发送“保活”消息来探测对端主机是否还在。如果在预设的时间内没有收到响应,TCP会认为对方已经崩溃或不可达,并终止连接。这个机制在很多系统中默认是关闭的,或者默认的Keepalive间隔设置得很长(例如,可能是几个小时),因此需要在应用层或系统配置中适当设置。

5.2 应用层心跳:

        在应用层实现心跳机制是检测对端是否存活的常用方法。应用程序可以定期发送轻量级的心跳数据包到对端。如果在预定的超时时间内没有收到心跳响应,可以认为对端已下线。这种方法的优点是可以由应用程序自定义心跳间隔和超时时间,使其更灵活和及时。

5.3异常检测:

        如果客户端突然掉线,任何试图发送到客户端的数据都会失败。通过捕获这些失败的尝试,服务器端可以推断出客户端可能已经掉线。例如,在发送数据时可能会遇到ECONNRESET、ETIMEDOUT或EPIPE等错误,这些都是网络中断的迹象。

5.4 主动探测:

       服务器可以定期或在特定条件下主动向客户端发送数据包,如果发送失败或响应超时,则可以认为客户端已经掉线。

5.5 操作系统通知: 、

       在某些操作系统中,可以设置网络接口状态的变化通知,比如Linux的netlink套接字可以用来检测网络状态的变化。

六、网络编程中设计并发服务器,使用多进程 与 多线程 ,请问有什么区别?

       在网络编程中,设计并发服务器时,可以选择使用多线程或多进程来处理多个客户端的连接。每种方法都有其优缺点,其适用性取决于特定的应用场景和系统环境。

6.1 多进程:

  1. 隔离性: 每个进程有自己独立的内存空间,这意味着进程之间的隔离性很好,一个进程的崩溃不会直接影响到其他进程。
  2. 资源消耗: 创建新进程通常比创建新线程需要更多的资源,因为每个进程都有自己的地址空间,上下文切换的成本也较高。
  3. 通信复杂性: 进程间通信(IPC)机制通常比线程间通信更复杂,因为需要跨越不同的内存空间。
  4. 稳定性: 由于隔离性强,多进程模型通常更稳定,特别是在高负载的情况下。
  5. 可扩展性: 多进程模型在多核处理器上能更好地扩展,因为操作系统通常能够有效地在多个处理器间分配进程。

6.2 多线程:

  1. 资源共享: 线程之间共享进程的内存空间,这使得线程间通信和资源共享变得更容易和高效。
  2. 创建开销: 创建线程的资源消耗和上下文切换的成本通常远低于进程,因此多线程服务器能更快地响应新的连接请求。
  3. 同步机制: 由于线程间共享数据,同步机制(如互斥锁、信号量等)是必不可少的,以避免竞态条件和数据不一致。
  4. 隔离性: 线程间隔离性较差,一个线程的崩溃可能会导致整个进程(因此是所有线程)的崩溃。
  5. 内存占用: 相比于多进程模型,多线程模型通常具有更小的内存占用。

6.3 结合使用:

       有时候,可以将两者结合起来使用,例如采用预先分配的线程池来减少线程创建和销毁的开销,同时使用一定数量的进程来利用多核处理器的能力。

6.4 选择依据:

       如果应用程序需要高度的稳定性和隔离性,可能倾向于使用多进程模型。

       如果应用程序的性能要求较高,尤其是在内存和上下文切换开销方面,可能会选择多线程模型。

       如果是在微服务或容器化环境中,进程可以被视为轻量级的,此时可以更灵活地选择

七、网络编程的一般步骤

7.1 对于TCP连接:

       TCP(传输控制协议)是一种面向连接、可靠的传输层协议。

7.1.1 服务器端:

  1. 创建套接字(Socket): 使用socket()函数创建一个TCP套接字。
  2. 绑定(Bind): 使用bind()函数将套接字绑定到服务器的IP地址和端口号。
  3. 监听(Listen): 使用listen()函数使服务器套接字进入监听状态,等待客户端的连接请求。
  4. 接受连接(Accept): 使用accept()函数接受来自客户端的连接请求。该函数会为每个连接到服务器的客户端创建一个新的套接字。
  5. 接收和发送数据(Receive/Send): 使用recv()和send()(或read()和write())函数在已建立的连接上接收和发送数据。
  6. 关闭连接: 当通信结束时,使用close()函数关闭套接字。

7.1.2 客户端:

  1. 创建套接字(Socket): 使用socket()函数创建一个TCP套接字。
  2. 连接(Connect): 使用connect()函数发起到服务器的连接请求。
  3. 接收和发送数据(Receive/Send): 使用recv()和send()(或read()和write())函数在建立的连接上接收和发送数据。
  4. 关闭连接: 使用close()函数关闭连接。

7.2 对于UDP连接:

       UDP(用户数据报协议)是一种无连接、不可靠的传输层协议。

7.2.1 服务端和客户端:

  1. 创建套接字(Socket): 使用socket()函数创建一个UDP套接字。
  2. 绑定(Bind): 服务端会使用bind()函数将套接字绑定到特定的IP地址和端口号。客户端也可以绑定,但这通常是可选的。
  3. 发送和接收数据(Sendto/Recvfrom): 使用sendto()和recvfrom()函数进行数据的发送和接收。与TCP不同,每次发送或接收都需要指定对方的地址信息。
  4. 关闭套接字: 使用close()函数关闭套接字。

八、TCP的重发机制是怎么实现的?

       TCP(传输控制协议)是一种面向连接的、可靠的协议,它确保通过网络发送的数据能够准确无误地到达目的地。为了保证数据的可靠传输,TCP实现了一套复杂的确认应答(ACK)和重发机制。

8.1 序列号和确认应答

       序列号(Sequence Number): TCP将每个字节的数据都标记上一个序列号,并在发送的每个数据包中包含这个序列号。

       确认应答(Acknowledgment): 接收方在收到数据后,会发送一个ACK数据包,表明它期望接收下一个序列号的数据。这个ACK数据包包含了接收方已经成功接收数据的确认信息。

8.2 超时重传

       超时计时器(Retransmission Timeout,RTO): 当发送方发送数据后,它会启动一个计时器等待ACK的返回。如果在计时器到期之前没有收到ACK,发送方会认为数据包丢失,并会重发该数据包。

       动态计算RTO: RTO的初始值是根据网络的往返时间(Round-Trip Time,RTT)来动态计算的。TCP通过观察发送数据包和收到相应ACK的时间差来估算RTT,并使用这个估算值来设置RTO。

8.3 快速重传

       冗余ACK(Duplicate Acknowledgment): 如果接收方收到一个失序的数据包(意味着有一些数据在中途丢失了),它会发送一个冗余ACK,告诉发送方最后一个按顺序接收的数据包的序列号。

       三次冗余ACK: 如果发送方连续收到三个或更多相同的冗余ACK,它会认为该序列号后面的数据包已经丢失,并立即重发丢失的数据包,而不是等待RTO超时。这个过程被称为快速重传。

8.4 拥塞控制

       慢启动和拥塞避免: 在快速重传后,TCP会进入慢启动或拥塞避免模式,这意味着它将减少网络中的数据包数量,以避免网络拥塞。

       拥塞窗口大小: TCP使用一个拥塞窗口来控制在任一时刻发送到网络中的数据量。在检测到丢包和执行重传后,拥塞窗口的大小会调整以响应网络状态的变化。

九、TCP为什么不是两次连接?而是三次握手?

       TCP 的三次握手(three-way handshake)是建立一个可靠的连接所必需的步骤,它确保双方都明确彼此的接收和发送能力已就绪。三次握手的过程涉及三个阶段:SYN(同步),SYN-ACK(同步-确认应答)和 ACK(确认应答)

那么,为什么不只用两次握手呢?

主要原因是为了防止旧的重复连接初始化的数据包突然又传送到服务器,导致错误的连接建立。如果只有两次握手,就可能会出现以下情况:

  • 客户端发送的一个旧的连接请求(SYN)由于在网络中滞留而延迟发送,然后它启动了一个新的连接请求。
  • 服务器接收到这个旧的 SYN 请求并回应(SYN-ACK),创建了一个连接对象等待客户端的最后确认。
  • 客户端不会回应这个旧的连接请求的 SYN-ACK,因为它不是它期待的那个连接,所以服务器上的这个连接对象就一直处于半开状态(half-open state),浪费资源,并且如果这种情况经常发生,可能导致服务拒绝攻击(DoS)。

       通过三次握手,即使服务器发送了初始的 SYN-ACK 响应,也需要客户端的最后确认(ACK)。如果这个确认没有到达,服务器将不会维持这个连接。这在最后一步可以有效地避免创建无效的连接,保证了TCP连接的可靠性和稳定性。

十、tcp流, udp的数据报,之间有什么区别,为什么TCP要叫做数据流?

10.1 TCP(传输控制协议):

  1. 连接导向: TCP是一种基于连接的协议,意味着通信的两个端点必须首先建立连接才能开始传输数据。
  2. 可靠性: TCP提供可靠的数据传输服务,确保数据无差错且不丢失地到达目的地。这是通过使用序列号、确认应答和重发机制来实现的。
  3. 数据流: TCP被描述为一个“数据流”(stream),这是因为它允许发送方和接收方之间传输一个连续的数据流,而不必关注数据的边界。这意味着发送方可以持续发送数据,而TCP会根据需要将数据切割成TCP段(segments)进行传输。接收方收到这些段后会重新组合成初始的数据流。
  4. 流量控制和拥塞控制: TCP还提供流量控制和拥塞控制机制,确保网络中的数据流动不会超过接收方或网络本身的处理能力。

10.2 UDP(用户数据报协议):

  1. 无连接: 与TCP不同,UDP是一种无连接的协议。它不需要在数据传输之前建立连接,数据可以被立即发送到目的地。
  2. 不可靠性: UDP不保证数据的可靠传输。数据包(称为数据报)可能会丢失或顺序错误,且UDP不提供重发机制。
  3. 数据报文: UDP以数据报文(datagrams)的形式发送信息。每个数据报文都是独立传输的,并且有明确的边界,与其他数据报文完全隔离。
  4. 轻量级: 因为UDP没有建立连接、没有可靠性保证和流量控制,它的头部开销更小,传输效率通常比TCP高,但这是以牺牲可靠性为代价的。

10.3 为什么TCP叫做数据流?

       TCP被称为“数据流”协议,这是因为它提供了一种流式传输机制。应用程序写入TCP套接字的数据可以被切分成多个TCP段,这些段基于它们的大小和网络的当前状况被独立发送。接收端的TCP层会将这些段按序列号重新组合,提供给接收应用程序一个连续的数据流。这种连续性意味着发送和接收应用程序不需要关心消息的界限,可以自由地以任何大小和任何时刻发送和接收数据。

十一、socket在什么情况下可读?

       在Socket编程中,"可读"有几个不同的含义,取决于上下文和你所使用的特定API(如select、poll、epoll等)。

  1. 数据到达:如果有新的数据到达Socket,它就会变得可读。这意味着应用程序可以从Socket读取数据,而不会被阻塞(即,可以进行非阻塞读取)。
  2. 连接建立:对于服务端来说,在监听Socket上,可读表示一个新的连接请求到来,服务端可以调用accept来接受这个连接。
  3. 连接关闭:如果对端正常关闭了连接(例如,对端的Socket执行了close操作),Socket也会变得可读,并且读操作会返回0,表明到达了文件结束符(EOF)。
  4. 错误或异常:如果连接出现错误或异常,Socket同样可能会报告它是可读的,以便应用程序可以获取错误信息或处理异常情况。
  5. 半关闭状态:在TCP中,如果对端执行了半关闭(例如,关闭了写入方向的连接而保留读取方向),则Socket可能变为可读,以允许接收方读取剩余的数据。

       在使用类似select或poll这样的系统调用时,这些函数会等待多个文件描述符中的一个或多个成为"可读"、"可写"或有"异常"条件发生。如果select调用返回并指示某个Socket可读,应用程序通常应该立即读取该Socket,以免错过数据。这些系统调用在实现多路IO复用时非常有用,它们可以让你的程序同时监视多个文件描述符的状态变化。

十二、说说IO多路复用优缺点?

       IO多路复用是一种允许单个进程或线程监视多个文件描述符(FDs)以等待I/O事件(如读或写可用性)的技术。在Unix-like系统中,select、poll和epoll是常用的IO多路复用机制。

12.1 优点:

  1. 资源效率: 使用IO多路复用,单个进程/线程可以管理多个并发IO操作。这比为每个IO操作创建单独的线程或进程更为资源高效,因为线程和进程都有相对较高的开销。
  2. 可扩展性: IO多路复用可以增加程序处理并发连接的能力,对于需要处理大量并发连接的服务器(如Web服务器或文件服务器)尤其有用。
  3. CPU效率: 由于减少了线程/进程的数量,相较于线程模型,多路复用可以减少上下文切换的开销,从而提高CPU效率。
  4. 无阻塞和异步操作: IO多路复用使得应用可以无阻塞地执行IO操作,同时等待多个IO事件,可以更有效地使用系统资源,提高应用性能。

12.2 缺点:

  1. 编程复杂性: 使用IO多路复用需要更加复杂的编程模型。程序员必须仔细设计事件循环并处理各种IO事件,这可能比简单的阻塞IO或每连接一个线程的模型更难编写和调试。
  2. 限制和开销: 一些多路复用系统调用(如select)有FD集合大小的限制,并且在处理大量FD时可能会有性能问题。尽管如epoll这样的现代机制克服了这些限制,但不同的多路复用API在不同的情况下会有各自的性能特点和限制。
  3. 操作系统依赖: 不同的操作系统可能提供不同的多路复用机制。例如,epoll是Linux特有的,而kqueue是BSD系统的多路复用解决方案。这可能导致跨平台编码时的兼容性问题。
  4. 事件类型限制: 某些多路复用API可能对可以监视的事件类型有限制。例如,某些实现可能不能监视某些特定类型的FDs(如某些类型的特殊文件)。
  5. 缓冲区管理: 在某些情况下,多路复用可能需要程序员更细致地管理缓冲区(如读写操作可能不会完成所有请求的字节)。

       IO多路复用可以提供高效的并发IO处理方式,特别是在需要处理大量并发连接的场景中。然而,它也带来了更高的编程复杂性,并且需要更深入的操作系统特性理解。选择是否使用IO多路复用,以及使用哪种多路复用技术,通常取决于应用的具体需求和预期负载。

十三、说说什么是select机制和他的优缺点

  select机制是一种I/O多路复用的接口,它允许程序同时监控多个文件描述符(FDs),等待一个或多个FDs成为"可读"、"可写"或出现异常条件。当程序使用select时,它可以挂起执行,直到一个或多个条件满足,然后继续处理I/O。

13.1 如何工作:

       程序员会创建三个文件描述符集合:读集合、写集合和异常集合。然后调用select函数,并传入这些集合以及可能的超时时间。select会阻塞,直到以下情况之一发生:

       一个或多个文件描述符准备好I/O操作(例如,数据可读、写缓冲区有空间可写入数据)。

       发生异常条件。

       超时发生(如果指定了超时时间)。

 select返回后,程序员需要遍历文件描述符集合来找出哪些FDs触发了事件,并进行相应的读写或异常处理。

13.2 优点:

  1. 跨平台: select机制广泛支持在各种操作系统上,是编写跨平台网络程序的一种常见选择。
  2. 简单易用: select相对容易理解和使用,因为它的接口比较简单。
  3. 统一的接口: 使用select, 程序可以在同一个地方处理不同类型的I/O,包括TCP、UDP和普通文件。

13.2 缺点:

  1. 受限的文件描述符数量: 传统的select实现使用固定大小的位图来跟踪文件描述符集合,通常受制于操作系统定义的FD_SETSIZE限制,这限制了它可以监视的文件描述符的最大数量。
  2. 效率问题: 当文件描述符数量较大时,select的效率会下降,因为每次调用select时都需要遍历整个文件描述符集合,这在数量庞大时会导致性能瓶颈。
  3. 可伸缩性问题: 与epoll等更现代的I/O多路复用机制相比,select的可伸缩性较差,不适合处理数千个并发连接。
  4. 修改传入的集合: select返回时,会修改传入的文件描述符集合以反映哪些FDs准备好了操作。因此,如果你想重新使用这些集合,需要在每次调用select之前重新初始化它们。
  5. 重复工作: 由于每次调用select都需要重新指定整个文件描述符集和超时,这可能导致不必要的重复工作,特别是在文件描述符集经常不变的情况下。

       由于select的这些局限性,在现代应用中通常会选择epoll(在Linux上)或kqueue(在BSD系统上)这样的更先进的I/O多路复用机制,它们提供了更好的性能和更高的可伸缩性。

十四、说说什么是epoll机制和他的优缺点

   epoll 是 Linux 系统中高效的 I/O 事件通知机制,它是 select 和 poll 的现代替代品,设计用于处理大量并发文件描述符的情况。epoll可以看作是可伸缩的文件描述符状态通知系统。

14.1 工作原理:

 epoll使用一组函数来管理它的工作流程:

   epoll_create:创建一个 epoll 实例。

    epoll_ctl:向 epoll 实例添加、修改或移除文件描述符。

    epoll_wait:等待事件发生,它返回一组准备好的文件描述符。

       与 select 和 poll 不同,epoll 使用事件驱动的方式,只关心活跃的文件描述符,而不是轮询整个描述符集合。

14.2 优点:

  1. 效率和可伸缩性: epoll 在处理大量文件描述符时更有效率,它不需要像 select 或 poll 那样每次调用都传递整个列表。一旦一个文件描述符被添加到 epoll 实例,不必在每次调用 epoll_wait 时重新扫描它。
  2. 没有固定的限制: epoll 不受 FD_SETSIZE 的限制,它可以处理的文件描述符数量远远超过 select 所能够管理的数量。
  3. 边缘触发和水平触发: epoll 支持两种模式:边缘触发 (ET) 和水平触发 (LT),提供了更多的灵活性。边缘触发可以减少事件被触发的次数,从而降低了CPU的使用率。
  4. 更少的资源复制: 在返回准备好的文件描述符时,epoll 直接从内核空间传递信息到用户空间,减少了大量的数据复制。

14.3 缺点:

  1. 平台依赖性: epoll 是 Linux 特有的,它不能直接在其他操作系统上使用,这限制了代码的移植性。
  2. 复杂性: epoll 提供的灵活性和高性能可能以增加编码复杂度为代价。尤其是在边缘触发模式下,开发者必须小心处理 I/O 操作,确保非阻塞和正确的事件处理。
  3. 可能的资源泄漏: 如果不正确地管理 epoll 文件描述符,可能导致资源泄漏。例如,在程序结束时忘记关闭 epoll 文件描述符,会导致相关资源没有被释放。
  4. 潜在的“惊群效应”: 在多线程或多进程模型中,大量线程/进程在同一事件上被唤醒,即“惊群效应”,虽然较新版本的 Linux 提供了一些解决方案,但在某些使用场景中这仍然是一个问题。

  epoll 是现代高性能网络服务中的首选 I/O 多路复用技术,特别是在需要处理大量并发连接时。尽管有一些缺点,但是对于专注于 Linux 平台的应用程序来说,它提供了很好的性能优势。

十五、epoll需要在用户态和内核态拷贝数据么?

       `epoll`机制确实需要在用户态(user space)和内核态(kernel space)之间拷贝数据,但这种拷贝是高度优化的。当使用`epoll_wait`系统调用时,内核会将那些就绪的文件描述符的事件集合从内核态复制到用户态。这个拷贝动作只发生在有事件发生时,即有文件描述符就绪时,而不是像`select`或`poll`那样在每次调用时都进行整个集合的复制。

       这个拷贝是必要的,因为用户程序需要知道哪些文件描述符已经就绪,以及对应的I/O事件类型。不过,由于`epoll`使用了基于事件的系统,在文件描述符上的事件只有在状态改变时才会被报告,因此这种拷贝通常比`select`或`poll`的拷贝操作要少,性能上也更优。

       在内核实现层面,`epoll`的数据结构被设计得尽可能高效,以减少用户态与内核态之间的数据传输开销。例如,一些现代操作系统使用了所谓的零拷贝技术(zero-copy),来最大限度地减少数据在用户态和内核态之间的拷贝操作,虽然完全的零拷贝对于`epoll_wait`来说并不可行,因为它至少需要传递事件信息。

十六、epoll的实现知道么?在内核当中是什么样的数据结构进行存储,每个操作的时间复杂度是多少?

16.1 epoll的实现什么?

 epoll 的实现是基于高效的数据结构来管理大量的文件描述符(fd)。在内核中,epoll 主要利用了三种数据结构:红黑树(rbtree)、双向链表(linked list)和一个就绪事件列表。

  1. 红黑树: 当你使用 epoll_ctl 添加一个新的文件描述符到 epoll 实例时,内核会在红黑树上创建一个节点来存储该文件描述符及其相关信息。红黑树是一种自平衡的二叉搜索树,用来管理和查找大量的文件描述符。它保证了插入、删除和查找操作的最坏情况时间复杂度为 O(log n),其中 n 是树中元素的数量。这意味着即使是大量的文件描述符,操作仍然可以非常快速地完成。
  2. 双向链表: 当一个文件描述符上的事件变得就绪(例如,一个socket变得可读),它会被添加到一个就绪链表中。在调用 epoll_wait 时,内核会检查这个双向链表,并将所有就绪的事件复制到用户态。这个双向链表的好处是添加和移除节点的时间复杂度是 O(1),非常快速。
  3. 就绪事件列表: 用户调用 epoll_wait 时,内核会检查就绪链表,将就绪的事件从链表中移除并复制到用户态提供的事件缓冲区中。这个操作的时间复杂度依赖于就绪事件的数量,而不是监视的文件描述符总数。

16.2 各操作时间复杂度:

  epoll_create:O(1) - 创建一个新的 epoll 实例,通常只是分配内存和初始化数据结构。

  epoll_ctl 添加/删除:O(log n) - 添加或删除文件描述符需要在红黑树中进行操作。

  epoll_ctl 修改:O(1),如果文件描述符已经在红黑树上,修改与该 fd 关联的事件典型地是一个 O(1) 操作。

   epoll_wait:O(1) - 事实上是常量时间操作,因为它只返回就绪的事件,不需要扫描全部文件描述符。然而,复制就绪事件数据到用户空间的时间复杂度与就绪事件的数量成正比。

  epoll 的这种实现方式使得它特别适合于处理大量的并发连接,因为它的性能几乎不会随着文件描述符数量的增加而显著下降。这也是 epoll 比传统的 select 和 poll 更加高效的原因。

十七、其他常见问题:

17.1 什么是Socket?

       Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口背后,对用户来说只需要调用这些简单的接口就可以实现网络通信的编程。

17.2 Socket通信的基本流程是怎样的?

       对于TCP和UDP,基本流程略有不同。TCP通常涉及服务器端的监听、接受连接、数据传输和关闭连接四个步骤;而UDP不需要建立和断开连接,只是发送和接收数据报文。

17.3 TCP和UDP的区别是什么?

       TCP是面向连接、可靠的、基于字节流的传输层通信协议,而UDP是面向非连接、不可靠的、基于数据报的传输协议。

17.4 何为阻塞和非阻塞Socket?

       阻塞Socket在进行IO操作时,如果没有数据可读或者无法立即写入数据,调用会被挂起,直到有数据可读或可写;非阻塞Socket在同样情况下会立即返回,不会等待。

17.5 什么是Socket的TIME_WAIT状态?

       TIME_WAIT是TCP连接断开时的一种状态,当一个连接的一方执行主动关闭,并发送了FIN后,就会进入TIME_WAIT状态。这种状态通常会持续2倍的MSL(最大段生存时间)。

17.6 如何处理或避免TIME_WAIT状态过多问题?

       可以通过设置SO_REUSEADDR选项来使得一个Socket可以绑定到一个处于TIME_WAIT状态的端口上。

17.7 什么是Nagle算法?

       Nagle算法是一种改善网络通信效率的算法,通过减少发送小包的数量来提高TCP/IP网络的效率。

17.8 如何实现Socket的多路复用?

       通过使用select、poll或epoll等系统调用,可以实现在单个线程中监视多个文件描述符的可读或可写事件。

17.9 epoll和select/poll的区别是什么?

       select和poll通过轮询来处理多个Socket,它们每次调用都需要遍历整个文件描述符集合;而epoll使用一种事件驱动的方式,只关心活跃的文件描述符,这使得它在处理大规模并发连接时更加高效。

17.10 如何处理TCP粘包?

        TCP粘包问题可以通过固定长度的消息、消息长度前缀、特殊分隔符或实现应用层协议来解决。

17.11 Socket编程中如何检测对端Socket的关闭?

       通过对Socket进行read操作,如果返回0,则表示对端已经关闭了连接。

17.12 如何优化高负载下的Socket服务器?

       优化的策略包括使用IO多路复用技术、负载均衡、TCP参数调整、应用层缓存、减少系统调用次数等。

17.13 描述TCP三次握手和四次挥手的过程。

        TCP三次握手包括SYN、SYN-ACK和ACK的交换,而四次挥手包括FIN和ACK的交换。

17.14 如何检测和处理网络编程中的死锁问题?

       死锁问题可以通过设计避免循环等待的系统、设置超时机制和检测死锁并采取措施解决来处理。

17.15 什么是Socket中的backlog参数?

        backlog参数定义了等待接受的传入连接队列的最大长度,影响着服务器端能够处理的并发连接数。

17.16 如何使用send和recv函数,并解释它们的返回值意义。

        send用于发送数据,而recv用于接收数据。它们的返回值表示实际读写的字节数,或者在出错时返回-1。

17.17 如何设置和理解Socket选项,比如TCP_NODELAY和SO_LINGER?

        TCP_NODELAY用于控制Nagle算法,而SO_LINGER用于控制close操作的行为。

17.18 在网络编程中如何处理字节序或大小端问题?

        字节序问题通常通过网络字节序转换函数(如htonl、htons、ntohl、ntohs)来处理。

17.19 什么是异步IO和信号驱动IO?它们与非阻塞IO和同步IO有何不同?

       异步IO和信号驱动IO允许应用程序在没有数据可读写的情况下继续执行,只在数据就绪时得到通知。

17. 20 描述Linux下的TCP/IP协议栈和Socket API之间的关系。

        Socket API为应用程序提供了一个编程接口,用以发送或接收数据包,而TCP/IP协议栈负责实际的数据处理和网络层通信。

17.21 如何排查一个网络程序的性能瓶颈?

        通常通过分析网络流量、监测系统资源(CPU、内存、IO)和使用性能分析工具(如gprof、valgrind等)。

17.22 什么是SSL/TLS,为什么和如何在Socket编程中使用它?

       SSL/TLS提供了加密的网络数据传输,它在应用层和传输层之间提供了一层安全保障。在Socket编程中使用它需要SSL库如OpenSSL。

17. 23 解释TCP的拥塞控制机制是如何工作的。

        涉及慢启动、拥塞避免、快速重传和快速恢复等TCP拥塞控制算法的工作原理。

17. 24 描述Linux中TCP/IP的协议栈处理一个网络包的完整流程。

       从网络包到达网卡开始,经过中断处理、内核网络层的处理,最后到达用户空间的Socket。

17.25 什么是滑动窗口协议?

       滑动窗口是TCP用来实现流量控制的一种机制,它允许发送方在接收方确认前发送一定数量的数据包,提高传输效率。

17.26 在Socket编程中,何时会使用shutdown而不是close?

       shutdown可以允许Socket进行有序的关闭,分别关闭读或写的一半连接,而close会立即终止Socket的读写操作。

17.27 解释网络编程中的零拷贝技术。

       零拷贝技术通过减少数据在用户空间和内核空间之间拷贝的次数来提高性能,例如使用sendfile系统调用。

17.28 什么是IO复用模型中的水平触发和边缘触发?

       水平触发和边缘触发是epoll系统调用中的概念,决定了在文件描述符状态改变时应用程序的响应方式。

17.29 描述如何使用TCP/IP模型中的各层来构建一个Socket连接。

        包括应用层选择协议(HTTP、FTP等),传输层选择传输协议(TCP或UDP),网络层处理IP寻址和路由,链路层处理与物理网络的接口。

17.30 什么是域名系统(DNS)?

        DNS是将人类可读的域名转换为机器可读的IP地址的系统。

17.31 如何使用netstat、tcpdump等工具来调试网络连接问题?

       netstat可以用来查看网络连接和路由表,tcpdump可以用于捕获和分析网络包。

17.32 在网络编程中,如何实现安全的数据传输?

       除了SSL/TLS,还涉及到数据加密、认证、密钥交换机制和安全协议(如IPSec)。

17.33 HTTP和HTTPS有什么区别?

       HTTPS是HTTP的安全版本,它在HTTP和TCP之间增加了一层SSL/TLS加密层。

17.34 描述WebSocket协议及其如何与传统的HTTP协议相比。

        WebSocket提供了全双工通信机制,使得客户端和服务器可以同时发送数据,减少了HTTP的请求-响应模型所带来的延迟。

17.35 如何优化长连接和短连接在不同场景下的使用?

       根据应用需求,比如实时性、资源消耗和网络条件等,选择合适的连接策略。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
12天前
|
负载均衡 网络协议 算法
|
19天前
|
安全 网络安全 数据安全/隐私保护
|
11天前
|
负载均衡 网络协议 算法
|
15天前
|
域名解析 网络协议 安全
|
20天前
|
网络协议 安全 网络安全
|
21天前
|
运维 监控 网络协议
|
24天前
|
存储 网络安全 数据安全/隐私保护
|
3天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
13 2
|
1月前
|
运维 监控 网络协议
|
1月前
|
运维 定位技术 网络虚拟化

热门文章

最新文章