当前位置: 首页 > news >正文

Linux中的 I/O 复用机制 select

第一部分:select 基本概念

1.1. I/O 复用的提出:并发处理的挑战

在传统的网络服务模型中,服务器为每一个客户端连接创建一个独立的线程或者进程来处理。这种模式在并发连接数量较少时或许尚能应对,但当并发量显著增大时,其弊端便暴露无遗。为每个连接都分配独立的执行单元(线程或进程)会消耗大量的系统资源,包括内存占用和CPU时间。更为关键的是,大量的线程或进程会导致频繁的上下文切换,这本身就是一项高昂的开销,严重时会拖垮服务器的整体性能。

正是为了应对这种由于传统并发模型效率低下而引发的挑战,I/O复用技术应运而生。select机制便是其中的一个典型代表。它提供了一种在单个线程或进程内管理多个I/O通道(如文件描述符)的能力。这种设计的初衷在于,通过集中管理I/O事件,显著减少对系统资源的占用和上下文切换的开销,从而在不牺牲并发处理能力的前提下,提升服务器的伸缩性和效率select不仅是一种技术实现,更是对早期服务器架构中性能瓶颈的一种战略性回应。

1.2. select 的定义与核心价值

select 是一种经典的I/O复用(I/O Multiplexing)机制。它允许一个单独的线程或进程监视多个文件描述符(File Descriptors, FDs)的状态变化。这些文件描述符可以代表多种I/O资源,例如网络套接字(sockets)、管道(pipes)、终端设备,甚至是普通文件select能够同时等待这些被监视的文件描述符中的任何一个变为“就绪”状态,例如可读、可写,或者发生某种异常情况。

select的核心价值在于其能够使程序在单一控制流(单个线程或进程)中异步地处理多个I/O操作,而无需为每一个I/O操作启动一个新的线程或进程,也无需让主程序在等待某个特定I/O操作完成时陷入阻塞。当没有任何一个被监视的文件描述符就绪时,调用select的进程或线程会进入休眠状态,直到至少有一个文件描述符状态发生变化,或者设定的超时时间到达。这种非阻塞的、事件驱动的方式非常适合处理高并发的I/O请求,因为它有效地避免了传统“一个连接一个线程/进程”模型所带来的高昂资源消耗和管理复杂性。通过将多个I/O任务的管理集中化,select使得单个线程能够高效地服务众多客户端,从而显著减少了系统资源的消耗。

1.3. select 的工作原理概览

select 的工作原理可以概括为:程序将一组感兴趣的文件描述符(FDs)以及希望关注的事件类型(可读、可写、异常)传递给内核。内核会代表程序监视这些文件描述符。当这些被监视的文件描述符中,有一个或多个的状态发生了程序所关注的变化时(例如,一个网络套接字接收到了新的数据从而变为可读,或者一个套接字的发送缓冲区有空间了从而变为可写),select 调用就会返回,并通知程序哪些文件描述符已经就绪。随后,程序可以针对这些就绪的文件描述符执行相应的I/O操作(如读取数据、发送数据等)。

为了实现这一机制,select 使用了一种名为 fd_set 的特定数据结构。fd_set 通常是一个位图(bitmap),其中每一位对应一个文件描述符。程序通过一系列宏操作(如 FD_SET, FD_CLR, FD_ZERO, FD_ISSET)来管理这个集合,将需要监视的文件描述符加入集合,或从集合中移除,以及在 select 返回后检查哪些文件描述符是就绪的。后续将对 fd_set 及其操作宏进行更详细的介绍。

第二部分:深入 select — — 函数原型与核心机制

2.1. select 函数原型详解

select 函数的POSIX标准原型如下所示:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数名类型描述输入/输出
nfdsint需要检查的文件描述符个数 (所有被监视的文件描述符中的最大描述符值 + 1)输入
readfdsfd_set *指向监视可读事件的文件描述符集合的指针。如果为NULL,则不监视可读事件。输入/输出
writefdsfd_set *指向监视可写事件的文件描述符集合的指针。如果为NULL,则不监视可写事件。输入/输出
exceptfdsfd_set *指向监视异常事件(如带外数据)的文件描述符集合的指针。如果为NULL,则不监视异常事件。输入/输出
timeoutstruct timeval *select 的超时时间。NULL表示永久阻塞;0表示非阻塞;否则为具体时间。输入
返回值解读

select 函数的返回值指示了调用的结果:

  • 返回值大于 0: 表示在被监视的三个文件描述符集合 (readfds, writefds, exceptfds) 中,总共有多少个文件描述符已经准备就绪。这个数值是所有就绪描述符的总和,而不是指有多少个集合非空。例如,如果一个FD同时可读又可写,它会被计数两次(如果同时在readfdswritefds中被监视并就绪)。

  • 返回值为 0: 表示在指定的 timeout 时间内,没有任何文件描述符准备就绪。这通常意味着超时发生。

  • 返回值为 -1: 表示调用过程中发生了错误。此时,全局变量 errno 会被设置以指示具体的错误类型。常见的错误包括:

    • EBADF: 某个文件描述符集合中包含了无效的文件描述符。
    • EINTR: 调用被信号中断。
    • EINVAL: nfds 参数为负数或过大,或者 timeout 结构中的时间值无效。
    • ENOMEM: 内核内存不足。

    对于任何系统调用,尤其是像 select 这样与底层I/O交互的调用,进行彻底的错误检查至关重要。当 select 返回-1时,应用程序必须检查 errno 的值,并根据错误类型采取适当的处理措施,如记录日志、重试操作或终止程序。忽略错误检查可能导致程序行为不可预测、数据损坏甚至崩溃。这种严谨的错误处理是构建稳定可靠的系统软件的基本要求。

2.2. 文件描述符集合 (fd_set) 与相关宏

fd_set 类型是 select 机制的核心数据结构。从概念上讲,它是一个位图(bitmask),其中每一位(bit)代表一个文件描述符。如果文件描述符 n 对应的位被设置,则表示 n 在这个集合中。fd_set 的大小通常由一个编译时常量 FD_SETSIZE 来定义,这个值在不同的系统上可能不同,但传统上常见的是1024或2048。这意味着一个 fd_set 默认情况下最多能表示 FD_SETSIZE 个文件描述符(通常是从0到 FD_SETSIZE-1)。

为了方便地操作 fd_set,系统提供了一组标准的宏:

宏名称描述示例用法 (概念性)
FD_ZERO(set)清空 (初始化) 一个文件描述符集合 setFD_ZERO(&read_fds);
FD_SET(fd, set)将文件描述符 fd 添加到集合 set 中。FD_SET(sockfd, &read_fds);
FD_CLR(fd, set)将文件描述符 fd 从集合 set 中移除。FD_CLR(sockfd, &read_fds);
FD_ISSET(fd, set)测试文件描述符 fd 是否存在于集合 set 中(即是否就绪)。if (FD_ISSET(sockfd, &tmp_fds))

2.3. select 的轮询机制

select 机制中,“轮询”(Polling)指的是当 select 函数被调用时,内核为了确定哪些文件描述符已经就绪,会遍历(scan)所有被应用程序指定要监视的文件描述符,并检查它们各自的状态。这个遍历检查的过程是 select 核心工作方式的一部分

具体来说,当你调用 select() 时,内核会接收你传入的 nfds 参数以及 readfdswritefdsexceptfds 这三个文件描述符集合。然后,对于从0到 nfds-1 的每一个文件描述符,如果它存在于某个传入的非空集合中,内核就会检查该文件描述符是否满足该集合所对应的条件(例如,对于 readfds 中的FD,检查其是否可读;对于 writefds 中的FD,检查其是否可写等)。

如果内核在遍历过程中发现有任何一个文件描述符满足了所请求的条件,select 就会修改相应的 fd_set 来标记这个(或这些)就绪的FD,并立即返回(或者在第一个就绪FD发生后,继续检查完所有FD再返回,具体行为可能因实现而异,但结果是所有就绪FD都会被标记)。如果遍历完所有被监视的文件描述符后,没有发现任何一个处于就绪状态,并且设置了超时时间,那么 select 会阻塞等待,直到某个文件描述符状态改变,或者超时时间到达。

这种线性扫描所有被监视文件描述符(或者更准确地说,是扫描从0到 nfds-1 范围内的、在集合中被标记的FD)的方式,是 select 机制的一个基本特征。然而,这也正是 select 的主要性能瓶颈所在,尤其是在需要监视大量文件描述符的场景下。因为无论最终有多少个文件描述符实际就绪(可能只有一个,甚至一个都没有),select 在每次被调用时,其内部操作的复杂度都与 nfds(即被监视的最大文件描述符加一)的大小成正比,即 O(nfds)。当 nfds 很大时(例如接近 FD_SETSIZE 的上限,如1024),即使只有少数几个FD活跃,内核仍然需要遍历检查大量的FD位,这会消耗可观的CPU时间。此外,每次调用 select 前后,应用程序层面还需要对 fd_set 进行清空、设置以及后续的遍历检查(使用 FD_ISSET),这些操作的开销也与 nfds 相关。

因此,虽然轮询机制使得 select 的概念相对简单直观,但也决定了它在处理超大规模并发连接(例如数千、数万个连接)时性能会显著下降。这也是为什么后续发展出了像 poll(解决了 FD_SETSIZE 的硬性限制,但轮询开销本质仍在)以及更高级的、基于回调或内核事件队列的机制如 epoll(Linux)和 kqueue(BSD系列),这些机制能够更有效地处理大量文件描述符,通常其复杂度与活跃连接数相关,而不是总连接数。

第三部分:select 实践

示例:使用 select 实现的 I/O 复用聊天服务器

这个示例展示了一个使用 select 实现的简单回显聊天服务器。它能够接受多个客户端连接,并回显从任一客户端收到的消息给发送方。

// 使用select实现I/O复用的聊天服务器端#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>#define BUF_SIZE 100  // 定义缓冲区大小// 错误处理函数
void error_handling(char *buf);int main(int argc, char *argv)
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct timeval timeout;   // 设置超时时间fd_set reads, cpy_reads;   // 用于select的文件描述符集合socklen_t adr_sz;int fd_max, str_len, fd_num, i;char buf;// 检查命令行参数是否正确if(argc!= 2) {printf("Usage : %s <port>\n", argv);exit(1);}// 创建服务器端socketserv_sock = socket(PF_INET, SOCK_STREAM, 0);if (serv_sock == -1)error_handling("socket() error");// 初始化服务器地址结构memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);   // 绑定到所有网络接口serv_adr.sin_port = htons(atoi(argv));   // 绑定端口号// 绑定socket到指定地址if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");// 开始监听客户端连接if(listen(serv_sock, 5) == -1)error_handling("listen() error");// 初始化文件描述符集合,清空并将服务器socket加入集合FD_ZERO(&reads);FD_SET(serv_sock, &reads); // 将监听套接字 serv_sock 加入到 reads 集合中fd_max = serv_sock;   // 当前最大的文件描述符是服务器socketwhile(1){// 复制reads集合,以防止在select过程中修改原始集合cpy_reads = reads;// 设置select的超时时间为5秒和5000微秒timeout.tv_sec = 5;timeout.tv_usec = 5000;// 调用select函数进行I/O复用,等待客户端的连接或数据// fd_max + 1 是 select 需要检查的文件描述符数量// &cpy_reads 是监视可读事件的集合// 最后两个0分别表示不监视可写事件和异常事件// &timeout 是超时设置if((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1) {error_handling("select() error"); // select出错则退出break;}if(fd_num == 0)   // 如果超时,没有文件描述符就绪,继续下一次循环continue;// 遍历所有可能的文件描述符(从0到fd_max),检查是否有活动for(i = 0; i < fd_max + 1; i++){if(FD_ISSET(i, &cpy_reads))   // 检查文件描述符i是否在cpy_reads中(即是否就绪){if(i == serv_sock)     // 如果是服务器监听socket (serv_sock) 就绪,表示有新的连接请求{adr_sz = sizeof(clnt_adr);// 接受连接,获得客户端socketclnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);if (clnt_sock == -1) {// 在实际应用中,这里应该有更细致的错误处理,而不是直接忽略// 例如记录日志,但不应让整个服务器因此崩溃perror("accept() error"); continue; }FD_SET(clnt_sock, &reads);   // 将新客户端的socket加入到主监视集合reads中// 更新最大的文件描述符if(fd_max < clnt_sock)fd_max = clnt_sock;printf("connected client: %d \n", clnt_sock);   // 输出连接的客户端信息}else      // 如果是其他已连接的客户端socket就绪,表示有数据可读{str_len = read(i, buf, BUF_SIZE);   // 从文件描述符i读取数据if(str_len == 0)       // 如果读取到的字节数为0,表示客户端关闭连接{FD_CLR(i, &reads);   // 从主监视集合reads中移除该客户端的socketclose(i);   // 关闭客户端socketprintf("closed client: %d \n", i); //输出断开连接的客户端信息// 注意:如果关闭的是fd_max,理论上应该重新计算fd_max。// 在这个简单示例中,fd_max只增不减(除非程序重启)。// 一个更健壮的实现会在FD_CLR后检查是否需要更新fd_max。// 例如:// if (i == fd_max) {//     int temp_max = 0;//     for (int k = 0; k <= fd_max; ++k) {//         if (FD_ISSET(k, &reads) && k > temp_max) {//             temp_max = k;//         }//     }//     fd_max = temp_max;// }}else if (str_len < 0) // 读取发生错误{// 处理读取错误,例如记录日志,关闭连接perror("read() error");FD_CLR(i, &reads);close(i);printf("read error on client: %d, closed.\n", i);}else // 读取到数据{write(i, buf, str_len);   // 回显接收到的消息给发送方}}}}}close(serv_sock);   // 关闭服务器监听socketreturn 0;
}// 错误处理函数
void error_handling(char *buf)
{fputs(buf, stderr);   // 将错误消息输出到标准错误fputc('\n', stderr);   // 输出换行符exit(1);   // 退出程序
}

代码注释解释与核心逻辑分析

  • 服务器创建和初始化

    • socket(): 创建一个TCP流套接字 (SOCK_STREAM) 用于服务器监听。
    • bind(): 将创建的套接字与服务器的IP地址(INADDR_ANY 表示本机所有IP地址)和用户指定的端口号绑定。
    • listen(): 使服务器套接字进入监听状态,并设置等待连接队列的最大长度(这里是5)。
  • I/O复用核心 - select

    • fd_set reads, cpy_reads;: reads 是主文件描述符集合,保存所有需要监视可读事件的FD(初始时只有监听套接字,之后会加入客户端套接字)。cpy_readsreads 的副本,实际传递给 select 函数,因为 select 会修改它。
    • FD_ZERO(&reads);: 清空 reads 集合。
    • FD_SET(serv_sock, &reads);: 将服务器的监听套接字 serv_sock 加入到 reads 集合中,以便 select 能够监视新的连接请求。
    • fd_max = serv_sock;: 初始化 fd_max,它是 select 第一个参数 nfds 的基础(nfds = fd_max + 1)。fd_max 必须是当前所有被监视FD中的最大值。
    • 主循环 while(1):
      • cpy_reads = reads;: 在每次调用 select 前,必须用主集合 reads 的内容重新填充工作集合 cpy_reads。这是因为 select 会修改 cpy_reads,只留下那些就绪的FD。如果不复制,下一次循环 select 将只监视上一次就绪的FD。
      • timeout.tv_sec = 5; timeout.tv_usec = 5000;: 设置 select 的超时时间为5秒5毫秒。这意味着如果5.005秒内没有任何FD就绪,select 也会返回。
      • fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout);: 调用 select。这里只关心可读事件 (&cpy_reads),不关心可写事件或异常事件 (后两个参数为0或NULL)。
      • 如果 fd_num == -1,表示 select 调用出错,程序通过 error_handling 退出。
      • 如果 fd_num == 0,表示超时,没有FD就绪,使用 continue 跳过后续处理,开始下一次循环迭代。
  • 处理客户端连接与数据

    • for(i = 0; i < fd_max + 1; i++): 循环遍历从0到 fd_max 的所有文件描述符。
    • if(FD_ISSET(i, &cpy_reads)): 使用 FD_ISSET 检查文件描述符 i 是否在 select 返回后仍然存在于 cpy_reads 集合中。如果是,则表示 i 对应的FD发生了可读事件。
      • if(i == serv_sock): 如果就绪的是监听套接字 serv_sock,表明有新的客户端连接请求。
        • clnt_sock = accept(serv_sock,...);: 调用 accept() 接受连接,返回一个新的已连接套接字 clnt_sock,代表与该客户端的通信通道。
        • FD_SET(clnt_sock, &reads);: 将这个新的 clnt_sock 加入到主监视集合 reads 中,以便在后续的 select 调用中监视它是否有数据可读。
        • if(fd_max < clnt_sock) fd_max = clnt_sock;: 如果新接受的 clnt_sock 大于当前的 fd_max,则更新 fd_max。这是确保 select 的第一个参数 nfds (fd_max + 1) 始终正确的关键。
      • else: 如果就绪的是一个已连接的客户端套接字(即 i!= serv_sock),表明该客户端发送了数据。
        • str_len = read(i, buf, BUF_SIZE);: 从该套接字 i 读取数据到缓冲区 buf
        • if(str_len == 0): 如果 read() 返回0,表示客户端已关闭连接。
          • FD_CLR(i, &reads);: 将该客户端套接字 i 从主监视集合 reads 中移除。
          • close(i);: 关闭该套接字。
          • 注意: 在一个更健壮的实现中,如果关闭的 i 正好是 fd_max,则需要重新遍历 reads 集合以找到新的 fd_max 值。此示例中为了简化,没有包含这部分逻辑。
        • else if (str_len < 0): 如果 read() 返回负值,表示读取时发生错误。此时也应关闭连接并从 reads 集合中移除。
        • else: 如果 read() 返回大于0的值 (str_len),表示成功读取到数据。
          • write(i, buf, str_len);: 将读取到的数据原样回显给发送该数据的客户端。
  • 错误处理函数 error_handling(): 一个简单的辅助函数,用于在发生严重错误时打印错误消息到标准错误流并终止程序。

总结:此服务器代码通过 select() 实现I/O复用,使其能够在一个单线程内有效地管理和响应多个客户端的连接请求和数据通信。它通过监视一个监听套接字和多个客户端套接字的文件描述符,非阻塞地处理网络事件,避免了为每个连接创建独立线程或进程所带来的开销。

第四部分:select 的优缺点评估

select 作为一种历史悠久且广泛应用的I/O复用机制,有其独特的优势,但也存在一些固有的局限性。

4.1. select 的优点

  1. 减少资源消耗: select 允许服务器使用单个线程或进程来处理多个客户端连接。这与为每个连接都创建一个新线程或新进程的传统模型相比,极大地减少了内存占用和CPU上下文切换的开销。上下文切换是一项昂贵的操作,过多的切换会显著降低系统整体性能。select 通过集中处理I/O事件,有效地规避了这个问题。

  2. 高效的 I/O 处理 (在一定规模内): 在单个线程内,select 能够让程序同时管理多个I/O操作,等待任何一个操作就绪。对于中等数量的并发连接(例如几百个),select 能够提供相当高效的I/O处理能力。程序不必因为等待某个特定的I/O而阻塞整个线程,从而提高了线程的利用率。

  3. 良好的跨平台性 (可移植性): select 是POSIX标准的一部分。因此,它在几乎所有的类Unix操作系统(包括Linux、macOS、各种BSD发行版)以及Windows(通过Winsock API提供类似功能)上都得到了支持。这种广泛的可用性使得基于 select 编写的程序具有良好的可移植性,可以相对容易地在不同系统间迁移。

  4. 适用于有限文件描述符数目的场景: 对于那些并发连接数相对较少、文件描述符总数通常在几百个以内(远未达到 FD_SETSIZE 上限)的中小型应用,select 提供了一种相对简单且易于理解和实现的I/O复用方案。其API和使用模式相对固定,学习曲线较为平缓。

4.2. select 的缺点

  1. 文件描述符数量限制 (FD_SETSIZE): select 使用 fd_set 结构来表示被监视的文件描述符集合。fd_set 的大小在编译时由常量 FD_SETSIZE 确定,这个值通常是1024或2048(具体取决于操作系统和编译环境)。这意味着 select 能够同时监视的文件描述符的最大数量受到了这个硬性限制。一旦应用需要处理的并发连接数超过 FD_SETSIZEselect 便不再适用。虽然可以通过修改头文件并重新编译内核或库来增大 FD_SETSIZE,但这并非标准做法且可能带来其他问题。

  2. 性能问题 (线性扫描/轮询开销): 这是 select 最主要的性能瓶颈。每次调用 select 时,内核都需要遍历(线性扫描)从0到 nfds-1 的所有文件描述符,以检查它们的状态,判断是否在传入的 fd_set 中被标记,以及是否就绪。这个过程的开销与 nfds(即最大文件描述符值+1)成正比,而不是与实际活跃的文件描述符数量成正比。当被监视的文件描述符数量非常大时(例如接近 FD_SETSIZE),即使只有少数几个FD是活跃的,内核仍然需要进行大量的检查工作。这种 O(nfds) 的时间复杂度使得 select 在处理大规模并发连接时性能会急剧下降。

  3. fd_set 的复制开销: 由于 select 函数会修改传入的 fd_set 参数(在返回时只保留就绪的FD),应用程序通常需要在每次调用 select 之前,将一个包含所有待监视FD的主 fd_set 复制到一个临时的 fd_set 中,然后将这个临时集合传递给 select。当 FD_SETSIZE 较大(例如1024或2048,对应的 fd_set 大小为128字节或256字节)且监视的FD数量也很多时,这个内存复制操作本身也会带来一定的CPU开销,尤其是在调用频率很高的情况下。

  4. 内核态与用户态之间的数据拷贝: 除了应用程序层面的 fd_set 复制,fd_set 数据本身也需要在用户态和内核态之间进行拷贝。当 select 被调用时,fd_set 从用户空间拷贝到内核空间;当 select 返回时,修改后的 fd_set 又从内核空间拷贝回用户空间。对于较大的 fd_set,这些拷贝也会消耗时间。

这些缺点,特别是文件描述符数量限制和线性扫描带来的性能问题,促使了后续更高级I/O复用技术的出现,如 poll(解决了 FD_SETSIZE 限制,但仍有类似的轮询开销)以及性能更优的 epoll(Linux特有)。

相关文章:

Linux中的 I/O 复用机制 select

第一部分&#xff1a;select 基本概念 1.1. I/O 复用的提出&#xff1a;并发处理的挑战 在传统的网络服务模型中&#xff0c;服务器为每一个客户端连接创建一个独立的线程或者进程来处理。这种模式在并发连接数量较少时或许尚能应对&#xff0c;但当并发量显著增大时&#xf…...

再议AOI算法

AOI算法优化 首先&#xff0c;需要知道AOI&#xff08;Area of Interest&#xff09;是什么。AOI通常用于游戏或者分布式系统中&#xff0c;用来管理玩家或对象的视野范围&#xff0c;只关注感兴趣区域内的其他对象&#xff0c;减少不必要的通信和数据传输。 整理思路 如文章…...

Unreal5 从入门到精通之如何实现 离线语音识别

文章目录 前言将语言服务器作为外部进程运行UE 项目设置开始捕获语音同时运行服务器进程和游戏进程将 SoundWave 而不是麦克风作为输入将数据发送到语言服务器节点的工作原理详细文档前言 今天我们要说的是一个语音转文本(STT)的插件 Offline Speech Recognition, 它支持离线…...

form-create-designer中$inject参数的数据结构及各项属性说明

FcDesigner 是一款基于Vue的开源低代码可视化表单设计器工具&#xff0c;通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单&#xff0c;提高开发者对表单的开发效率&#xff0c;节省开发者的时间。并广泛应用于在政务系统、OA系统、ERP系统、电商系统、流程管理等领域。 …...

WHAT - CSS 中的 min-height

文章目录 语法常见用途1. 防止元素被压缩得太小2. 配合 Flexbox 保证高度3. 用于内容区域动态撑高但不塌陷 与其他属性的区别提示 在 WHAT - CSS 中的 min-width 中我们已经详细介绍过 width。对于高度&#xff0c; CSS 同样提供一个 min-height. min-height 是 CSS 中用于设置…...

畅游Diffusion数字人(30):情绪化数字人视频生成

畅游Diffusion数字人(0)&#xff1a;专栏文章导航 前言&#xff1a;仅从音频生成此类运动极具挑战性&#xff0c;因为它在音频和运动之间存在一对多的相关性。运动视频的情绪是多元化的选择&#xff0c;之前的工作很少考虑情绪化的数字人生成。今天解读一个最新的工作FLOAT&…...

PLC系统中开关量与模拟量信号解析

引言 在现代工业自动化进程中&#xff0c;可编程逻辑控制器&#xff08;PLC&#xff09;凭借其强大的功能与灵活性&#xff0c;成为工业控制系统的核心设备。PLC能够高效、精准地控制工业生产流程&#xff0c;很大程度上依赖于其对开关量和模拟量信号的处理能力。深入理解这两…...

Qt中解决Tcp粘包问题

Qt中解决Tcp粘包问题 Qt中解决Tcp粘包问题——以文件发送为例服务器端客户端效果演示注意点 Qt中解决Tcp粘包问题——以文件发送为例 创建的工程如下图所示&#xff1a; 服务器端 界面的布局以及名称如下图所示&#xff1a; 并且在Qt中增加网络模块 QT core gui n…...

Qt调用librdkafka

Qt调用librdkafka Windows系统编译Qt使用的kafka(librdkafka) VS2017编译librdkafka 2.1.0 经过上面的步骤我已经编译好了librdkafka库,我编译的主要十release版的,需要debug版的小伙伴编译的时候要留意一下。 接下来就是调用我们编译的kafka库了。 一、环境介绍 Qt:…...

深入解析Node.js文件系统(fs模块):从基础到进阶实践

文章目录 引言一、核心能力解析1.文件读写操作2.文件复制方案对比3.文件监控机制 二、扩展知识体系1.高级文件操作2.性能优化策略3.安全实践指南 三、最佳实践总结 引言 在 Node.js 生态系统中&#xff0c;fs 模块是与文件系统交互的核心工具。本文将通过代码示例和实践经验&a…...

9、AI测试辅助-代码Bug分析提示词优化

AI测试辅助-优化代码Bug分析提示词 Bug分析1、优化代码2、根据报错结果定位 Bug分析 利用AI优化代码Bug&#xff0c;有两种方式&#xff0c;一种是优化潜在的问题&#xff0c;一种是根据执行后的报错进行查找定位优化。其中如何用好提示词是关键 1、优化代码 常见需要优化的…...

AI无法解决的Bug系列(一)跨时区日期过滤问题

跨时区开发中&#xff0c;React Native如何处理新西兰的日期过滤问题 有些Bug&#xff0c;不是你写错代码&#xff0c;而是现实太魔幻。 比如我最近给新西兰客户开发一个React Native应用&#xff0c;功能非常朴素&#xff1a;用户选一个日期范围&#xff0c;系统返回该范围内…...

leetcode 153. Find Minimum in Rotated Sorted Array

题目描述 分析 可以发现一个规律&#xff1a; 假如整个数组最后一个元素是x。 最小值左侧&#xff08;不含最小值自己&#xff09;的元素全部大于x。 最小值右侧&#xff08;包含最小值自己&#xff0c;不包含x&#xff09;的元素全部小于x。 如果整个数组是有序的&#x…...

Brave 连接 Websocket 失败

前提: websocket 的服务启动正常连接的url是: ws://localhost: 15000/[子url] 在 Brave 浏览器的 console 中看到错误: WebSocket connection to ws://localhost:15000/ws failed:解决方法&#xff08;Brave 浏览器专用&#xff09; 方法 1&#xff1a;关闭 Brave 的 Shiel…...

【设计模式】基于 Java 语言实现工厂模式

目录 一、简单工厂模式 1.1 简单工厂模式的介绍 二、工厂方法模式 2.1 工厂方法模式的介绍 2.2 工厂方法模式的基本实现 2.3 工厂方法模式的应用场景 三、抽象工厂 3.1 抽象工厂的概念 3.2 抽象工厂的基本结构 3.3 抽象工厂的基本实现 3.4 抽象工厂的应用场景 四、…...

94.LabelGrid 的遍历与属性编辑 Maui例子 C#例子

for (int i 0; i < LabelGrid.Children.Count; i) {if (LabelGrid.Children[i] is Label label){await MainThread.InvokeOnMainThreadAsync(() >{label.TextColor Colors.Gray;});} } await Task.Delay(1000); // 延迟1秒 if (currentValue 0) {currentValue 16; } …...

Https流式输出一次输出一大段,一卡一卡的-解决方案

【背景】 最近遇到一个奇怪的现象&#xff0c;前端vue&#xff0c;后端python&#xff0c;服务部署在服务器上面后&#xff0c;本来一切正常&#xff0c;但公司说要使用https访问&#xff0c;想着也没什么问题&#xff0c;切过去发现在没有更改任何代码的情况下&#xff0c;ht…...

【C# 自动化测试】Selenium显式等待机制详解

Selenium显式等待机制详解 一、显式等待的概念 在自动化测试中&#xff0c;等待机制是处理页面元素加载延迟的重要手段。显式等待允许我们在继续执行代码之前等待某个条件发生&#xff0c;这比固定的强制等待更灵活高效。 二、显式等待的实现代码 1. 核心等待方法 /// <…...

【Redis】哈希表结构

目录 1、背景2、哈希表【1】底层结构【2】哈希冲突【3】链地址法【4】传统rehash【5】渐进式rehash【6】rehash触发条件【7】特性 1、背景 redis中的hashtable&#xff08;哈希表&#xff09;是一种高效的键值对存储结构&#xff0c;主要用于实现redis的字典类型&#xff0c;接…...

Redisson中为什么用lua脚本不用事务

一文详解事务和lua脚本的区别 核心问题&#xff1a; 为什么 Redisson 在实现分布式锁、信号量等复杂对象时&#xff0c;倾向于使用 Lua 脚本&#xff0c;而不是 Redis 内建的事务 (MULTI/EXEC)&#xff1f; 结论概览&#xff1a; Lua 脚本为 Redisson 提供了更强的原子性保证、…...

成功解决!!!Ubuntu系统安装包时出现:dpkg: 处理归档XXX时出错

在Ubuntu系统中在安装新的包时&#xff0c;有时会报错连环依赖问题&#xff0c;常见的报错为&#xff1a;下列软件包有未满足的依赖关系&#xff1a;XXX依赖XXX 但是它不会被安装 E: 有未能满足的依赖关系。请尝试不指明软件包的名字来运行“apt --fix-broken install”(也可以…...

MySql数据库连接池

C数据库连接池 前言1.MySql API 函数讲解1.1 连接数据库的步骤1.2 MySQL C API1.2.1 初始化连接环境1.2.2 连接mysql服务器1.2.3 执行sql语句1.2.4 获取结果集1.2.5 得到结果集的列数1.2.6 获取表头 -> 列名(字段名)1.2.7 得到结果集中各个字段的长度(字节为单位)1.2.8 遍历…...

C++之fmt库介绍和使用(2)

C之fmt库介绍与使用(2) Author: Once Day Date: 2025年5月19日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 …...

Python的collections模块:数据结构的百宝箱

Python的collections模块&#xff1a;数据结构的百宝箱 对话实录 小白&#xff1a;处理数据时&#xff0c;Python自带的数据结构不够用&#xff0c;有更强大的工具吗&#xff1f; 专家&#xff1a;那可不能错过collections模块&#xff0c;它提供了许多高效实用的数据结构&am…...

吃透 Golang 基础:数据结构之数组

文章目录 吃透 Golang 基础&#xff1a;数据结构之数组概述初始化访问和赋值小结参考资料 吃透 Golang 基础&#xff1a;数据结构之数组 对于 Golang 当中的顺序数据结构&#xff0c;使用频率最高的当然是切片&#xff0c;因为切片非常的灵活。与之相对比&#xff0c;数组常常会…...

第三个小程序动工:一款结合ai的菜谱小程序

1.环境搭建&#xff0c;与初步运行 安装及使用 | Taro 文档 找到一个合适的文件夹&#xff0c;cmd D:\gitee>pnpm install -g tarojs/cli╭──────────────────────────────────────────╮│ …...

小程序涉及提供提供文本深度合成技术,请补充选择:深度合成-AI问答类目

一、问题描述 最近新项目AI咨询小程序审核上线&#xff0c;按照之前小程序的流程&#xff0c;之前审核&#xff0c;提示审核不通过&#xff0c;审核不通过的原因&#xff1a;小程序涉及提供提供文本深度合成技术 (如: AI问答) 等相关服务&#xff0c;请补充选择&#xff1a;深…...

数据结构测试模拟题(1)

1、约瑟夫问题 #include<bits/stdc.h> using namespace std; const int N25; int e[N],ne[N],head-1,idx1; int n,m; void add_to_head(int x){e[idx]x;ne[idx]head;headidx; } void add(int k,int x){e[idx]x;ne[idx]ne[k];ne[k]idx; } int main(){cin>>n>>…...

Elasticsearch高级面试题汇总及答案

Elasticsearch高级面试题汇总及答案 这套Elasticsearch面试题汇总大全,希望对大家有帮助哈~ 1、什么是Elasticsearch Analyzer? 分析器用于文本分析,它可以是内置分析器也可以是自定义分析器。 2、Elasticsearch 支持哪些配置管理工具? 1、 Ansible 2、 Chef 3、 Pu…...

界面控件DevExpress WinForms v24.2——PDF Viewer功能升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…...

Apache Apisix配置ip-restriction插件以限制IP地址访问

介绍 ip-restriction 插件可以通过将 IP 地址列入白名单或黑名单来限制对服务或路由的访问。 支持对单个 IP 地址、多个 IP 地址和类似 10.10.10.0/24 的 CIDR&#xff08;无类别域间路由&#xff09;范围的限制。 属性 参数名类型必选项默认值有效值描述whitelistarray[st…...

Maven 项目打包时添加本地 Jar 包

在 Maven 项目开发中&#xff0c;我们经常会遇到需要引入本地 Jar 包的场景&#xff0c;比如使用未发布到中央仓库的第三方库、公司内部自定义工具包&#xff0c;或者处理版本冲突的依赖项。本文将详细介绍如何通过 Maven 命令将本地 Jar 包安装到本地仓库&#xff0c;并在项目…...

JavaScript 性能优化:调优策略与工具使用

引言 在当今的 Web 开发领域&#xff0c;性能优化已不再是锦上添花&#xff0c;而是产品成功的关键因素。据 Google 研究表明&#xff0c;页面加载时间每增加 3 秒&#xff0c;跳出率将提高 32%。而移动端用户如果页面加载超过 3 秒&#xff0c;有 53% 的用户会放弃访问。性能…...

48、c# 中 IList 接⼝与List的区别是什么?

在 C# 中&#xff0c;IList 接口和 List 类在集合操作中扮演不同角色&#xff0c;主要区别体现在定义、功能、灵活性、性能及适用场景等方面。以下是详细对比&#xff1a; 1. 定义与本质 IList 接口 抽象契约&#xff1a;仅定义集合的基本操作&#xff08;如索引访问、添加、…...

在 Azure OpenAI 上使用 Elastic 优化支出和内容审核

作者&#xff1a;来自 Elastic Muthukumar Paramasivam&#xff0c;Bahubali Shetti 及 Daniela Tzvetkova 我们为 Azure OpenAI 正式发布包添加了更多功能&#xff0c;现在提供内容过滤监控和计费见解的增强&#xff01; 在之前的博客中&#xff0c;我们展示了如何使用 Elasti…...

Redis学习专题(三)主从复制

目录 引言&#xff1a; 1、搭建一主多从 1) 创建/hspredis目录, 并拷贝redis.conf 到 /hspredis 2) vi /hspredis/redis.conf , 进行如下设置 3) 创建3个文件/hspredis/redis6379.conf 、/hspredis/redis6380.conf 、/hspredis/redis6381.conf 并编辑 4) 启动三台redis服…...

设计模式之备忘录模式

在日常开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;需要保存对象的某个历史状态&#xff0c;以便将来恢复。这种需求最常见的例子就是“撤销操作”。在这种情况下&#xff0c;备忘录模式(Memento Pattern)就派上了用场。 目录 1. 概念 2. 代码实现 3. 总结 1. …...

深度学习-runner.run(data_loaders, cfg.workflow)内部执行过程

文件&#xff1a;~/catkin_ws/SparseDrive/projects/mmdet3d_plugin/apis/mmdet_train.py 完成数据加载器、优化器、运行器实例化后&#xff0c; RUNNERS.register_module() class IterBasedRunner(BaseRunner):"""Iteration-based Runner.This runner train m…...

嵌入式开发学习日志(linux系统编程--文件读写函数)Day24

一、系统编程 标准oi 【输入输出】 stdio.h 头文件 &#xff1a;stdio.h >标准输入输出头文件&#xff1b;/usr/include/stdio.h 二、文件操作 1、关于文件操作的步骤 &#xff08;1&#xff09;打开文件&#xff1b; &#xff08;2&#xff09;io操作&#xff0c;读写…...

DEBUG:Lombok 失效

DEBUG&#xff1a;Lombok 失效 问题描述 基于 Spring Boot 的项目中&#xff0c;编译时显示找不到 log 属性。查看对应的 class 类&#xff0c;Lombok 正常在编译时生成 log 属性。 同时存在另一个问题&#xff0c;使用Getter注解&#xff0c;但实际使用中该注解并没有生效&…...

Qt 控件发展历程 + 目标(1)

文章目录 声明简述控件的发展历程学习目标QWidget属性 简介&#xff1a;这篇文章只是一个引子&#xff0c;介绍一点与控件相关的但不重要的内容&#xff08;浏览浏览即可&#xff09;&#xff0c;这一章节最为重要的还是要把之后常用且重要的控件属性和作用给学透&#xff0c;学…...

按键精灵ios/安卓辅助工具高级函数OcrEx文字识别(增强版)脚本开发介绍

函数名称 OcrEx文字识别&#xff08;增强版&#xff09; 函数功能 返回指定区域内所有识别到的字符串、左上角坐标、区域宽高、可信度&#xff0c;无需自制字库&#xff0c;识别范围越小&#xff0c;效率越高&#xff0c;结果越准确 注意&#xff1a;安卓版按键APP需在设置…...

零基础入门Selenium自动化测试:自动登录edu邮箱

&#x1f31f; Selenium简单概述一下 Selenium 是一个开源的自动化测试工具&#xff0c;主要用于 Web 应用程序的功能测试。它能够模拟用户操作浏览器的行为&#xff08;如点击按钮、填写表单、导航页面等&#xff09;&#xff0c;应用于前端开发、测试和运维领域。 特点 跨…...

MySQL高频面试八连问(附场景化解析)

文章目录 "为什么订单查询突然变慢了&#xff1f;"——从这个问题开始说起一、索引的生死时速&#xff08;必考题&#xff01;&#xff09;二、事务的"套娃"艺术三、锁机制的相爱相杀四、存储引擎的抉择五、慢查询的破案技巧六、分页的深度优化七、高可用架…...

JVM 性能问题排查实战10连击

&#x1f5c2;️ 目录 前言&#xff1a;理论掌握只是起点&#xff0c;定位能力才是核心全局排查模型&#xff1a;三步法1️⃣Full GC 频繁触发&#xff1a;老年代压力过大2️⃣ OOM 爆炸&#xff1a;元空间泄漏 or 缓存未清理3️⃣ CPU 飙升却不是 GC&#xff1a;线程阻塞或热方…...

零基础深入解析 ngx_http_session_log_module

一、引言 在传统的 HTTP 日志中&#xff0c;每个请求都会被单独记录&#xff0c;这对于短连接、异步加载等场景非常直观&#xff1b;但在一些需要以“会话”为单位分析用户行为的场景下&#xff0c;如视频点播、多资源并行加载、长轮询等&#xff0c;单个请求日志难以准确反映…...

10.17 LangChain v0.3核心机制解析:从工具调用到生产级优化的实战全指南

LangChain v0.3 技术生态与未来发展 关键词:LangChain 工具调用, 聊天模型集成, @tool 装饰器, ToolMessage 管理, 多模态交互 使用聊天模型实现工具调用 LangChain v0.3 通过 工具调用(Tool Calling) 机制,将大模型与外部工具深度结合,形成闭环能力链。本节以 GPT-4、L…...

Android Framework学习七:Handler、Looper、Message

文章目录 简介LooperMessageMessageQueueHandlerFramework学习系列文章 简介 Looper当做一台传送装置&#xff0c;MessageQueue是传送带&#xff0c;传送带上放的是Message&#xff0c;Handler用于发送Message分发与接收处理。 Looper frameworks/base/core/java/android/app…...

分钟级降水预报API:精准预测每一滴雨的智慧科技

引言&#xff1a;天气预报进入"分钟时代" 在数字化生活高度发达的今天&#xff0c;人们对天气预报的精确度要求越来越高。传统的24小时预报或小时级预报已无法满足出行、物流、户外活动等场景的精细化需求。分钟级降水预报API的出现&#xff0c;标志着气象服务正式进…...

民政部等部门针对老人权益保障工作发布指导意见

​ 1 品牌资讯 佛慈制药&#xff1a;将探索开发特医食品等产品 李子园将丰富大健康产品矩阵适应银发族需求 京东健康2025年第一季度收入166.45亿元 宁美浩维获融资&#xff0c;致力提供健康管理方案 2 行业动态 固生堂合作华为&#xff0c;联合推动中医药智慧化转型 怡…...