【计算机网络 - 基础问题】每日 3 题(五十四)

✍个人博客:https://blog.csdn.net/Newin2020?type=blog

📣专栏地址:http://t.csdnimg.cn/fYaBd

📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~

❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

📝推荐参考地址:https://www.xiaolincoding.com/(这个大佬的专栏非常有用!)

160. SO_LINGER 选项

cpp 复制代码
#include<sys/socket.h>
struct linger
{
    int l_onoff;        //开启(非0)还是关闭(0)该选项
    int l_linger;       //滞留时间
};

根据 linger 结构体中两个成员变量的不同值,close 系统调用可能产生如下 3 种行为之一:

  • l_onoff 等于 0 (关闭):此时 SO_LINGER 选项不起作用,close 用默认行为来关闭 socket。
  • l_onoff 不为 0 (开启),l_linger 等于 0:此时 close 系统调用立即返回,TCP 模块将丢弃被关闭的 socket 对应的 TCP 发送缓冲区中残留的数据,同时给对方发送一个复位报文段(RST)。因此,这种情况给服务器提供了异常终止一个连接的方法。
  • l_onoff 不为 0 (开启),l_linger 大于 0:此时 close 的行为取决于两个条件:一是被关闭的 socket 对应的 TCP 发送缓冲区是否还有残留的数据;二是该 socket 是阻塞的,还是非阻塞的。对于阻塞的 socket,close 将等待一段长为 l_linger 的时间,直到 TCP 模块发送完所有残留数据并得到对方的确认。如果这段时间内 TCP 模块没有发送完残留数据并得到对方的确认,那么 close 系统调用将返回 -1 并设置 errno 为 EWOULDBLOCK。如果 socket 是非阻塞的,close 将立即返回,此时我们需要根据其返回值和 errno 来判断残留数据是否已经发送完毕。

161. 什么是 I/O 多路复用?

最基础的 TCP 的 Socket 编程,它是阻塞 I/O 模型,基本上只能一对一通信,那为了服务更多的客户端,我们需要改进网络 I/O 模型。

比较传统的方式是使用多进程/线程模型,每来一个客户端连接,就分配一个进程/线程,然后后续的读写都在对应的进程/线程,这种方式处理 100 个客户端没问题,但是当客户端增大到 10000 个时,10000 个进程/线程的调度、上下文切换以及它们占用的内存,都会成为瓶颈。

阻塞等待

BIO模型

非阻塞,忙轮询

NIO 模型

162. I/O 多路复用的 API

为了解决上面这个问题,就出现了 I/O 的多路复用,可以只在一个进程里处理多个文件的 I/O,Linux 下有三种提供 I/O 多路复用的 API,分别是:select、poll、epoll。

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0 ~ 1023 的文件描述符。

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

epoll 通过两个方面解决了 select/poll 的问题。

1、epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配。

2、epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket ),大大提高了检测的效率。

cpp 复制代码
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...)

int epfd = epoll_create(...);
epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中

while(1) {
    int n = epoll_wait(...);
    for(接收到数据的socket){
        //处理
    }
}

而且,epoll 支持边缘触发和水平触发的方式,而 select/poll 只支持水平触发,一般而言,边缘触发的方式会比水平触发的效率高。

简单总结一下:

  1. select:
    1. select 使用一个包含所有文件描述符的位图数据结构(通常是 fd_set)。这个位图的大小通常由 FD_SETSIZE 宏定义决定,这个宏的默认值是 1024。
    2. 当调用 select 时,内核会遍历位图中的每个文件描述符,检查是否有可读、可写、或异常事件。这个遍历是线性的,因此性能受到文件描述符数量的限制。
    3. select 是水平触发,它会一直通知应用程序,直到所有就绪事件被处理。
  2. poll:
    1. poll 使用一个存放在用户态的数组来注册文件描述符。数组中的每个元素都包含了文件描述符和等待的事件。
    2. 当调用 poll 时,内核会遍历这个数组,检查每个文件描述符是否有就绪事件。和 select 一样,这个遍历也是线性的,因此性能受到文件描述符数量的限制。
  3. epoll:
    1. epoll 使用一个事件表(event table)来注册文件描述符,事件表是内核空间的数据结构。
    2. 当调用 epoll_create 时,内核会创建一个事件表,并返回一个文件描述符,应用程序可以使用这个文件描述符来操作事件表。
    3. epoll 使用事件通知机制。当文件描述符就绪时,内核会将这个事件添加到事件表中,然后将一个就绪队列中的事件通知应用程序。
    4. 应用程序可以调用 epoll_wait 来等待事件就绪,并获得就绪的事件列表,这个操作是阻塞的,直到有事件就绪。
    5. epoll 可以使用水平触发或边缘触发模式。在边缘触发模式下,只有在状态发生变化时才会通知应用程序。
相关推荐
速盾cdn23 分钟前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin52112323 分钟前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin26 分钟前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
Theodore_10221 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
zy张起灵2 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
PersistJiao2 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
冰帝海岸2 小时前
01-spring security认证笔记
java·笔记·spring
----云烟----3 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024063 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·3 小时前
java基础面试题笔记(基础篇)
java·笔记·python