【应用】07.高并发服务器+总结

【嵌入式八股】一、语言篇https://www.nowcoder.com/creation/manager/columnDetail/mwQPeM

【嵌入式八股】二、计算机基础篇https://www.nowcoder.com/creation/manager/columnDetail/Mg5Lym

【嵌入式八股】三、硬件篇https://www.nowcoder.com/creation/manager/columnDetail/MRVDlM

【嵌入式八股】四、嵌入式Linux篇(本专栏)https://www.nowcoder.com/creation/manager/columnDetail/MQ2bb0

总结

alt

alt

alt

alt

alt

alt

alt

高并发服务器

多进程/多线程并发服务器

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

alt

  1. 多进程并发服务器:每个客户端请求都将创建一个新的进程,这个进程负责处理该客户端的请求。因为每个进程都是独立的,所以它们之间的内存空间是隔离的。这意味着在进程间共享数据需要使用进程间通信(IPC)技术,如管道、信号、共享内存、套接字等。由于进程切换的开销比较大,因此多进程并发服务器的性能通常比多线程并发服务器要差。
  2. 多线程并发服务器:在多线程并发服务器中,每个客户端请求都将创建一个新的线程,这个线程负责处理该客户端的请求。由于所有线程都属于同一个进程,它们共享同一个地址空间,可以轻松地共享数据,不需要进行进程间通信。由于线程切换的开销比进程切换的开销小得多,因此多线程并发服务器的性能通常比多进程并发服务器要好。但是,多线程编程需要注意线程安全问题,例如数据共享、竞态条件、死锁等。

多路IO转接服务器

结合Linux内核中的阻塞非阻塞同步异步来学习

alt

118.什么是IO多路复用

IO多路复用是一种高效的IO操作方式,它可以同时监听多个文件描述符(socket)的可读、可写、异常等事件,并在有事件发生时通知应用程序进行处理。常见的IO多路复用机制有select、poll、epoll等。

在传统的IO模型中,每个文件描述符都需要对应一个线程来处理,这会导致系统资源的浪费和线程切换的开销。而使用IO多路复用机制,可以将多个文件描述符的IO事件集中到一个线程中处理,减少了系统调用和线程切换的次数,提高了系统的吞吐量和响应性能

例如,在一个聊天室服务器中,需要同时监听多个客户端连接的读写事件,如果每个客户端连接都对应一个线程来处理,会导致线程数过多,而使用IO多路复用机制,则可以将多个客户端连接的事件集中到一个线程中处理,减少了系统资源的浪费和线程切换的开销。

119.说说IO多路复用优缺点?

优点:

  1. 减少系统调用和线程切换的次数:传统的IO模型中,每个文件描述符都需要对应一个线程来处理,会导致线程数过多,而使用IO多路复用机制,则可以将多个文件描述符的IO事件集中到一个线程中处理,减少了系统调用和线程切换的次数,提高了系统的吞吐量和响应性能。
  2. 支持异步IO操作:IO多路复用机制可以支持异步IO操作,即在IO操作完成前不会阻塞线程,提高了系统的并发性能和响应速度。
  3. 提高系统的稳定性:使用IO多路复用机制可以避免因线程阻塞而导致的系统崩溃等问题,提高了系统的稳定性和可靠性。
  4. 节省系统资源:使用IO多路复用机制可以避免线程数过多而导致的系统资源浪费和线程切换的开销,节省了系统资源。

缺点:

  1. 学习成本较高:IO多路复用机制的实现比较复杂,需要掌握多个系统调用和数据结构,学习成本相对较高。
  2. 可读性差:使用IO多路复用机制编写的代码可读性较差,需要使用状态机等技术来管理多个文件描述符的状态。
  3. 不支持数据的完整性保证:使用IO多路复用机制时,多个文件描述符的事件处理是交替进行的,无法保证数据的完整性和一致性。
120.select是什么?

在 Linux 网络编程中,select 是一种系统调用,用于在多个文件描述符上等待数据可读、数据可写或出现异常情况。

select 函数会阻塞当前线程,直到指定的文件描述符上有数据可读、数据可写或出现异常情况。在返回之前,select 会修改文件描述符集合,指示哪些文件描述符上发生了事件。因此,通过在 select 调用之前设置文件描述符集合,程序可以监视多个文件描述符上的事件。

下面是 select 函数的原型和参数说明:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:要监视的最大文件描述符值加一。
  • readfds:指向可读文件描述符集合的指针。
  • writefds:指向可写文件描述符集合的指针。
  • exceptfds:指向出现异常情况的文件描述符集合的指针。
  • timeout:select 函数的超时时间,如果为 NULL,则一直阻塞直到有事件发生。

在函数返回后,可以使用下面的宏函数检查文件描述符集合中的事件:

  • FD_ISSET(fd, fdset):检查文件描述符 fd 是否在 fdset 集合中。
  • FD_SET(fd, fdset):将文件描述符 fd 加入到 fdset 集合中。
  • FD_CLR(fd, fdset):将文件描述符 fd 从 fdset 集合中移除。
  • FD_ZERO(fdset):清空 fdset 集合。

需要注意的是,select 函数的效率并不高,因为它需要轮询多个文件描述符,而且在文件描述符集合较大时性能会受到影响。因此,在需要同时监视大量文件描述符的场景下,通常会使用更高效的事件驱动框架,如 epoll 。

121.说说select机制的优缺点

Select 机制是一种操作系统中的多路复用技术,它允许一个进程同时监视多个文件描述符,从而实现对多个 I/O 操作的并发处理。它的主要优点和缺点如下:

优点:

  1. 节省系统资源:使用 select 机制可以避免创建多个进程或线程,从而减少系统的开销,提高系统的效率。
  2. 可移植性:select 机制在不同的操作系统中都有实现,这使得应用程序可以在不同的系统平台上运行,提高了应用程序的可移植性。
  3. 可以同时处理多个 I/O 事件:select 机制可以同时监视多个文件描述符,并在其中任意一个文件描述符就绪时返回,这样就可以处理多个 I/O 事件,提高了系统的并发性能。

缺点:

  1. 执行效率相对较低:select 机制需要轮询所有的文件描述符,这样会带来一定的系统开销,从而降低了系统的执行效率。(可添加自定义数组提高效率)。每次调用select,都需要把监听的文件描述符集合fd_set从用户态拷贝到内核态,从算法角度来说就是O(n)的时间开销
  2. 代码实现较为复杂:select 机制的代码实现较为复杂,需要考虑多个文件描述符的状态,并进行相应的处理,这会增加程序的编写难度。
  3. 可扩展性受限:select 机制的最大文件描述符数是有限制的(限制为1024),这会影响其可扩展性,当需要同时监视大量文件描述符时,可能需要使用其他的多路复用技术。
122.TCP通讯中,select到读事件,但是读到的数据量是0,为什么,如何解决?

select 返回0代表超时(数据段在传输过程中可能会发生丢失、重复、延迟等情况)。select出错返回-1。

select到读事件,但是读到的数据量为0,说明对方已经关闭了socket的读端。

本端关闭读即可。当select出错时,会将接口置为可读又可写。这时就要通过判断select的返回值为-1来区分。

123.poll是什么

poll是一种I/O多路复用机制,可以用来同时监控多个文件描述符(包括socket)上的读写事件,并在其中一个或多个文件描述符上发生事件时通知应用程序进行处理。

poll与select类似,但是有以下优点:

  1. 没有文件描述符数量限制:select在某些平台上会存在文件描述符数量的限制,而poll没有这个限制,可以同时监听任意数量的文件描述符。
  2. 效率更高:当需要同时监听的文件描述符数量较大时,select的效率会变得比较低下,而poll能够更加高效地进行事件监听。

poll的原理是将所有待监听的文件描述符存放在一个数组中,然后调用poll函数进行监听,poll函数会一直阻塞,直到有文件描述符上有事件发生,或者超时,或者出错才会返回。当有事件发生时,poll函数会返回一个包含有事件的文件描述符的结构体数组,应用程序可以通过遍历该数组来确定哪些文件描述符上有事件发生,并进行相应的处理。

在使用poll函数时,需要首先创建一个包含有待监听的文件描述符的结构体数组,然后将该数组传递给poll函数进行监听。当有文件描述符上有事件发生时,poll函数会将该文件描述符的事件加入到结构体数组中,并返回该结构体数组。

124.poll优缺点

poll是一种I/O多路复用机制,与select类似,但相比select,poll有以下优缺点:

优点:

  1. 可以同时监听大量文件描述符:poll没有文件描述符数量的限制,可以同时监听任意数量的文件描述符,适用于同时处理大量并发连接的场景。
  2. 监听效率更高:当需要同时监听的文件描述符数量较大时,select的效率会变得比较低下,而poll能够更加高效地进行事件监听。
  3. 结构体更简单:poll函数返回的是一个结构体数组,相比于select函数需要对三个不同的fd_set结构体进行操作,poll结构更简单易用。

缺点:

  1. 等待事件时会阻塞:poll函数会一直阻塞,直到有文件描述符上有事件发生,或者超时,或者出错才会返回。
  2. 每次调用poll都需要将所有的描述符复制到内核:当监听的文件描述符比较多时,每次调用poll函数都需要将所有的文件描述符从用户态复制到内核态,效率较低。
125.epoll是什么?

epoll是一种高效的I/O多路复用机制,是Linux特有的系统调用,相比于select和poll,它具有以下优点:

  1. 支持大量的并发连接:epoll可以处理上万个连接而不会受到系统资源的限制。
  2. 高效:在处理大量连接时,epoll的效率远高于select和poll。
  3. 内核与用户空间内存交互减少:在调用epoll_wait()时,它只需要向内核传递一次事件表的地址,而不需要每次调用都向内核传递,这减少了内核与用户空间之间的内存交互次数。
  4. 事件被唤醒时会立即返回:当epoll监听的文件描述符发生变化时,epoll_wait()会立即返回,而不像select和poll一样需要逐个遍历文件描述符,这减少了CPU的开销。
126.epoll的原理是什么?

epoll是Linux内核中用于高效处理大量IO事件的机制,其主要原理如下:

  1. 内核维护一个事件表,其中每个事件对应一个文件描述符和对应的IO事件,如读、写、异常等。
  2. 应用程序使用epoll_create函数创建一个epoll句柄,用于管理事件表和事件监听。
  3. 应用程序使用epoll_ctl函数向epoll句柄中添加需要监听的文件描述符和对应的IO事件,同时可以设置是否为边缘触发模式。
  4. 应用程序使用epoll_wait函数等待事件的发生,此时内核会检查所有注册的文件描述符上的IO事件是否发生,如果有事件发生,则将其添加到就绪事件队列中,然后返回给应用程序。
  5. 应用程序使用epoll_wait返回的就绪事件队列中的文件描述符进行IO操作,如读、写等。
  6. 应用程序使用epoll_ctl函数删除已经不需要监听的文件描述符。

epoll的主要特点是采用了事件驱动的方式,能够高效处理大量的IO事件,同时支持边缘触发和水平触发两种模式,能够更加精细地控制事件的监听和响应。此外,epoll还使用了基于红黑树的数据结构,能够快速定位和处理就绪事件,进一步提高了处理性能和效率。

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

epoll的实现是基于红黑树和双向链表来实现的。

当应用程序调用epoll_create()函数创建一个epoll句柄时,内核会为其分配一个红黑树和一个双向链表。红黑树用来存储监听的文件描述符以及它们的事件,而双向链表则用来存储已经准备好I/O操作的文件描述符。

当应用程序调用epoll_ctl()函数注册文件描述符时,内核会将文件描述符添加到红黑树中,并注册相关的事件。当该文件描述符上的I/O事件发生时,内核会将其加入到双向链表中。

当应用程序调用epoll_wait()函数等待事件时,内核会遍历红黑树上的所有文件描述符,以检查是否有I/O事件发生,如果有,则将其加入到双向链表中,并返回给应用程序。

因为红黑树是一种自平衡的二叉查找树,因此对于每个操作的时间复杂度都是O(log n)。双向链表的操作时间复杂度为O(1)。

128.说一下epoll的优缺点

epoll是一种高效的I/O多路复用机制,相比于传统的select和poll,具有以下优点:

  1. 支持较大的并发连接数:epoll使用红黑树存储连接,可以高效处理大量连接。
  2. 更快的连接事件处理:在使用select和poll时,每次需要轮询所有连接,效率较低。而epoll采用回调机制,只有活跃的连接才会触发回调函数,避免了无用的扫描。
  3. 更快的数据读写:在使用select和poll时,每次需要调用recv或send来读写数据,而在使用epoll时,可以采用edge-triggered模式,只有在数据可读或可写时才会触发回调函数,可以减少不必要的系统调用次数,提高读写效率。
  4. 没有最大连接数限制:select和poll的最大连接数受限于文件描述符的数量,而epoll没有这个限制。

epoll的缺点主要在以下几个方面:

  1. 对于连接稳定、并发量不大的应用场景,与传统的I/O多路复用机制相比,epoll的优势不是很明显。
  2. 对于操作系统内核版本较旧的系统,不一定支持epoll机制,因此在这些系统上无法使用epoll。
129.epoll需要在用户态和内核态拷贝数据么?

在注册监听事件时从用户态将数据传入内核态;当返回时需要将就绪队列的内容拷贝到用户空间。

在使用epoll的ET(Edge Triggered)模式下,当数据可读或可写时,内核会通知应用程序,此时应用程序需要使用recv或send系统调用读取或写入数据。这个过程中会有一次用户态和内核态之间的数据拷贝。但是,当使用epoll的LT(Level Triggered)模式时,当数据可读或可写时,内核会持续通知应用程序,直到所有数据读取或写入完成,此时可能会有多次用户态和内核态之间的数据拷贝。因此,使用epoll时,数据拷贝的次数与使用select或poll时基本相同,取决于具体的使用模式。不过,相比于select和poll,在高并发场景下,epoll的效率更高。

130.epool中et和lt的区别与实现原理

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。

131.epoll反应堆是什么

epoll反应堆是基于epoll机制实现的高效事件驱动模型,可以在单线程下同时处理大量的并发连接和网络事件。它的核心思想是将所有的I/O事件都交给一个线程处理,这个线程通过epoll机制监听事件,并在事件触发时进行处理。

epoll反应堆通常包含两个关键组件:事件注册表和事件触发器。事件注册表用于注册所有需要监听的文件描述符,每个文件描述符对应一个事件结构体,该结构体包含了文件描述符、读事件、写事件和事件状态等信息。事件触发器则负责等待事件的发生,并将触发的事件分发给注册表中对应的事件结构体,然后再将这些事件交给处理器进行处理。

epoll反应堆具有高性能、高可扩展性和高灵活性的特点,可以应用于高并发网络编程、高性能服务器等场景。

132.select、poll、epoll区别
  1. select:

select支持的文件描述符数量有限,一般为1024个

挨个检测满足条件的fd,需要自己添加业务逻辑(数组存放满足条件的fd,找的更快,不用把所有文件描述符轮一遍),提高了编码难度

  1. poll:

自带数组/链表结构,可以增加需要监听的文件描述符,突破监听上限受文件描述符限制

无法直接定位满足监听事件的文件描述符

select和poll的缺点在于,当文件描述符数量较多时,轮询的效率会降低,而且每次轮询时需要将整个数组或链表遍历一遍,效率不高。

  1. epoll:

epoll采用了事件通知的方式,当文件描述符就绪时,内核会向应用程序发送一个事件通知,应用程序只需要处理这些事件即可。还提供了两种工作模式:LT(Level Triggered)和ET(Edge Triggered)。LT模式是默认模式,当文件描述符就绪时会持续通知应用程序,直到应用程序将该文件描述符处理完毕;而ET模式只在文件描述符状态改变时通知应用程序,如果应用程序没有将该文件描述符处理完毕,下一次就不会再收到通知。

使用了红黑树的数据结构来存储需要监听的文件描述符,可以快速的插入、删除和查找文件描述符,因此效率较高。同时,epoll支持的文件描述符数量没有限制。

线程池并发服务器

alt

133.什么是线程池?

线程池是一种常见的多线程并发编程技术,它是一组线程的集合,这些线程预先创建并初始化,并被放入一个队列中等待任务。当有新任务到来时,线程池中的某个线程会被唤醒并处理该任务,任务处理完后,线程又会回到线程池中等待下一次任务。通过线程池技术,我们可以实现高效、可伸缩的并发处理,提高系统的并发处理能力,降低系统的开销和复杂度。

线程池的主要组成部分包括任务队列、线程池管理器和工作线程。任务队列用于存储所有需要处理的任务,线程池管理器用于管理线程池的创建、销毁和线程的调度等操作,工作线程则是线程池中的执行单位,它们从任务队列中取出任务并执行任务。线程池通常采用预创建线程的方式,通过线程复用的方式避免了线程频繁创建和销毁所带来的开销

134.线程池优缺点?

线程池的主要优点包括:

  1. 提高并发性能:线程池可以预先创建一定数量的线程,避免线程频繁创建和销毁的开销,从而提高系统的并发性能
  2. 提高资源利用率:线程池可以重复利用线程,避免了线程频繁创建和销毁所带来的资源浪费。
  3. 提高程序可靠性:线程池可以限制系统中线程的数量,避免线程数量过多导致系统崩溃。
  4. 提高程序的可维护性:线程池可以将线程的管理和调度集中在一起,提高程序的可维护性。

线程池的主要缺点包括:

  1. 编程复杂度较高:线程池需要对任务队列、线程池管理器和工作线程进行管理和调度,编程复杂度相对较高。
  2. 调试困难:线程池中的多个线程并行执行,调试困难,需要使用锁和信号量等同步机制来保证线程安全。
  3. 对资源的占用问题:线程池中的线程需要占用一定的系统资源,如果线程池的数量过多,会对系统资源造成压力。
135.如何实现线程池?
  1. 设置一个生产者消费者队列,作为临界资源
  2. 初始化n个线程,并让其运行起来,加锁去队列取任务运行
  3. 当任务队列为空的时候,所有线程阻塞
  4. 当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程
136.如何防止同时产生大量的线程?

为了防止同时产生大量的线程,可以使用线程池来管理线程。线程池维护一定数量的线程,当有任务到达时,从线程池中取出一个空闲的线程来执行任务。如果所有线程都在执行任务,新任务就会被加入任务队列等待。这样可以避免频繁地创建和销毁线程,从而提高了程序的性能和稳定性。

线程池的具体实现方法有很多种,常见的实现方式包括:

  1. 固定大小线程池:线程池中的线程数量固定不变,当有新任务到来时,如果线程池中有空闲线程,则立即将任务交给空闲线程执行;否则将任务加入任务队列等待。这种方式可以避免线程数量过多导致的资源浪费和系统性能下降。
  2. 可变大小线程池:线程池中的线程数量不固定,根据任务的多少自动调整线程池中的线程数量。当任务数量增加时,线程池中的线程数量也会相应增加;当任务数量减少时,多余的线程会被销毁。这种方式可以根据实际情况灵活调整线程池中的线程数量,提高了程序的性能和资源利用率。
  3. 阻塞队列线程池:任务队列使用阻塞队列实现,当任务队列为空时,线程会自动阻塞等待新任务的到来;当任务队列已满时,新任务会被拒绝。这种方式可以避免线程池中的线程因为频繁地加锁、解锁而导致的性能下降。
  4. 定时任务线程池:线程池中的线程可以执行定时任务,定时任务的执行周期可以根据需要设定。这种方式可以方便地实现定时任务,提高了程序的灵活性和可维护性。

libevent

137.简介libevent

libevent是一个基于事件驱动的网络编程库,提供了高性能、可移植、跨平台的网络编程接口。它支持多种操作系统(包括Linux、FreeBSD、OpenBSD、NetBSD、Mac OS X等)和多种网络协议(包括TCP、UDP、HTTP、DNS等),可以用于开发高并发、高吞吐量的网络应用程序。

libevent的核心思想是事件驱动和异步IO,通过监听文件描述符上的事件(如读、写、异常等)来触发相应的回调函数,从而实现非阻塞式的网络编程。同时,它还提供了定时器、信号处理、线程池等功能,方便开发者编写高效可靠的网络应用程序。

138.大规模连接上来,并发模型怎么设计

针对大规模连接上来的并发模型设计,一般需要考虑以下几个方面:

  1. IO多路复用:使用IO多路复用技术,如select、poll、epoll等,可以监听多个连接的IO事件,从而实现高并发的网络通信。
  2. 线程池:针对大量的并发连接,使用线程池来管理工作线程,减少线程创建和销毁的开销,并利用多核CPU的优势提高系统的并发能力。
  3. 事件驱动:采用事件驱动的方式,基于事件回调函数实现异步处理,避免同步处理的阻塞等待,提高系统的吞吐量。
#C++##嵌入式##校招##面试##八股#
【嵌入式八股】嵌入式Linux 文章被收录于专栏

查阅整理上千份嵌入式面经,将相关资料汇集于此,主要包括: 0.简历面试 1.语言篇 2.计算机基础 3.硬件篇 4.嵌入式Linux【本专栏】 (建议PC端查看)

全部评论
嵌入式现在还有发展空间吗
点赞 回复 分享
发布于 2023-03-30 17:09 广西
点赞 回复 分享
发布于 2023-04-02 09:57 辽宁

相关推荐

11-09 01:22
已编辑
东南大学 Java
高级特工穿山甲:羡慕,我秋招有家企业在茶馆组织线下面试,约我过去“喝茶详谈”😢结果我去了发现原来是人家喝茶我看着
点赞 评论 收藏
分享
offer多多的六边形战士很无语:看了你的博客,感觉挺不错的,可以把你的访问量和粉丝数在简历里提一下,闪光点(仅个人意见)
点赞 评论 收藏
分享
评论
4
16
分享
牛客网
牛客企业服务