最新资讯

  • 手写 Linux 并发服务器,fork, pthread与 epoll 模型实战(包含深层原理剖析)

手写 Linux 并发服务器,fork, pthread与 epoll 模型实战(包含深层原理剖析)

2026-02-01 03:26:50 栏目:最新资讯 4 阅读

1. 最简单的服务器模型

在学习 Linux 网络编程时,我们通常接触的第一个服务器模型,就是单线程阻塞模型。只看它的名字其实已经能够明白这个模型的原理了,它的逻辑简单清晰:先创建一个 socket,再绑定端口,然后监听,最后在一个死循环里等待客户端连接。

1.1 单线程阻塞echo服务器

我们先来写一个最最基础的 echo 服务器,它的功能非常简单:就是把客户端发来的任何消息原封不动地再发回去。这也是为什么叫他echo服务器的原因。

#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int listen_fd, conn_fd;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE];

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1)
    {
        perror("socket listen");
        exit(1);
    }
    
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//设置端口可复用
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT);
    
    if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    
    if(listen(listen_fd, 5) == -1)
    {
        perror("listen");
        exit(1);
    }
    
    printf("服务器已启动,等待连接...
");

    conn_fd = accept(listen_fd, NULL, NULL);//阻塞等待客户端链接
    if(conn_fd == -1)
    {
        perror("accept");
        exit(1);
    }
    printf("客户端已连接
");

    ssize_t n;
    while ((n = read(conn_fd, buffer, BUFFER_SIZE)) > 0) //这里是阻塞的
    {
        write(conn_fd, buffer, n);//回写
    }

    close(conn_fd);
    close(listen_fd);
    return 0;
}

这段代码虽然简单,但它完整地展示了 Linux TCP 服务器的经典的五个步骤:socket -> bind -> listen -> accept -> read/write

其中还有几个值得注意的细节,我简单说一下:

第一个细节:端口复用。在socket之后,我加入了setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 。这一步至关重要,它允许我们的服务器在异常退出后,能够立即重启并重新绑定 8080 端口,而不会遇到 "Address already in use" 的错误。这是所有服务器的必备配置

第二个细节:网络字节序。在填充地址结构体serv_addr时,PORTINADDR_ANY 都要被 htons/htonl 函数处理。这是为了解决大小端问题。不同架构的 CPU 存储多字节整数的顺序可能不同。TCP/IP 协议栈规定了统一的网络字节序(大端)。这两个函数就是用来保证我们的数据符合标准,避免跨平台通信时出现数据解析错误。

第三个细节:这个模型最大的特点就是阻塞。程序首先会阻塞在 accept(),等待第一个客户端连接。连接成功后,程序会进入 while 循环,阻塞在 read(),等待客户端发送数据。正是这两个阻塞点,导致了它一次只能服务一个客户端的致命缺陷,也为我们后续的并发模型方向埋下了伏笔。

此外,为了展示最简单的服务器的功能到底有多么粗糙,我特意将 accept() 函数放在循环外面,这也就意味着,这个服务器一生只能服务一个客户端,当这个客户端断开连接时,服务器的运行也会结束。

下面我们来看看这个最简单的服务器到底都有哪些缺陷。

1.2 实验:致命的瓶颈

这个服务器看起来是能跑的,但他有一个致命的缺陷。我们可以通过一个简单的实验来看一下:

先运行上面的示例程序,就会显示 “服务器已启动…” 这些字,然后再开一个终端输入下面的命令:

telnet localhost 8080

就会显示 “客户端已连接” 。此时我在客户端输入一个 “hello” ,然后按回车,可以看到服务器在接收到 “hello” 之后又把他原封不动的发回给客户端了:

到这里,单线程阻塞echo服务器的功能就已经看到了。但如果我们再开一个终端尝试连接服务器呢?会发生什么事?

这里,我又开了一个终端连接服务器,如果尝试过的读者会发现这次服务器的那个终端并没有弹出 “客户端已连接” 这个表示链接成功的消息。此外,还能发现在这个新开的终端连接服务器,看似和前面那个客户端是相同的场景,但我们在输入 “hello” 并按下回车后,服务器并没有返回消息。这已经能够证明我们在这个终端上其实并没有连接到服务器

为什么呢?

这里暴露了两个致命问题

第一是无法并发:服务器的主线程在 accept 之后,就一头扎进了与第一个客户端通信的 while 循环里。只要这个客户端不主动断开,程序就永远没有机会去处理新的连接请求。

第二是一次性服务:更糟糕的是,一旦第一个客户端断开连接,while 循环结束,整个服务器程序也会跟着退出。

这种单线程、单任务、一次性的模型,在任何实际应用中都是不可接受的。我们需要让服务器:能同时服务多个客户端 (并发)还能 7x24 小时运行,而不是服务完一个就退出

2. 多进程模型

为了解决单线程模型的两大缺陷,我们自然而然地想到了 Linux 提供的多任务机制。最经典、最简单的就是多进程

2.1 改进思路

前面的单线程阻塞模型问题主要出在无法并发无法多次服务,因此我们主要针对这两点进行改进。

父进程,也就是主进程,它在一个while循环里面,主要负责接收 accept() ,也就是接收客户端连接请求。

子进程,负责处理客户端发来的信息。

他们的分工,我在这里简要总结一下:父进程一旦接收到客户端连接请求,立刻fork出一个子进程,然后把这个客户端交给子进程去处理,父进程自己继续去等待其他客户端了连接。

从这个改进思路上来看,已经实现了并发多次服务的两个目标。下面我们来关注一下这个模型的实现。

2.2 多进程 echo 服务器

上面已经讲了改进思路,我们废话不多说,直接贴出代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() 
{
    int listen_fd, conn_fd;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE];
    pid_t pid;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_fd == -1)
	{
		perror("socket listen");
		exit(1);
	}

	memset(&serv_addr,0,sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(PORT);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
	if(bind(listen_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) == -1)
	{
		perror("bind");
		exit(1);
	}

    if(listen(listen_fd, 5) == -1)
	{
		perror("listen");
		exit(1);
	}

    printf("服务器已启动,等待连接...
");

    while (1) 
    { 
        conn_fd = accept(listen_fd, NULL, NULL);
        if (conn_fd < 0) 
		{
            perror("accept");
            continue; //出错了没事,继续等下一个
        }

        pid = fork();

        if(pid < 0) 
		{
            perror("fork");
            close(conn_fd);
            continue;
        }
		else if(pid == 0) //子进程
		{
            close(listen_fd); 

            printf("子进程%d为新客户端服务(fd=%d)...
", getpid(), conn_fd);

            ssize_t n;
            while (((n = read(conn_fd, buffer, BUFFER_SIZE))) > 0) 
			{
                write(conn_fd, buffer, n);//回写的字节数一定要是read读到的字节数
            }
            
            printf("客户端断开,子进程%d退出。
", getpid());
            close(conn_fd);
            exit(0); 
        } 
		else 
		{
            close(conn_fd); 
        }
    }

    close(listen_fd); // 理论上走不到这里,因为父进程一直在循环里面
    return 0;
}

运行代码,看看效果怎么样:

这次我运行服务器后,直接开了三个终端连接上服务器,可以看到,服务器所在终端已经弹出了客户端已连接成功的消息:

我们在来看看这三个客户端的情况:


通过运行结果可以看到,这三个客户端都可以收到服务器回传的消息,说明我们已经达到了开始的目标。

到这儿,可能有人会提出问题,为什么服务器端显示连接上的三个客户端的文件描述符 fd 都是 4 呢?其实这并不是发生了错误,而是一个正常现象,来听我详细解释:

当父进程调用 fork 时,内核会为子进程创建一个几乎一模一样的内存空间和上下文,在这里我们只关注于文件描述符表。每个进程都有自己独立的文件描述符表,在 fork 的那一瞬间,子进程也得到了和父进程一模一样的文件描述符表

好,理论讲完了,我们来关注一下过程,到底是什么原因导致了三个相同的文件描述符呢?

首先,在进程中,文件描述符的 0,1,2 已经默认被标准输入,标准输出和标准错误占据了,这时,进程进行 socket ,把返回的值(也就是3)赋给 listen_fd

然后,当第一个客户端连接时,父进程调用 accept ,返回一个新的 conn_fd ,由于 3 已经被占用了,所以 conn_fd 只能为 4。

之后,父进程调用fork创建第一个子进程(PID:11375),这个子进程得到了父进程完整的文件描述符表,子进程的fd=4也指向了和父进程相同的 TCP 连接

接着,printf("子进程%d为新客户端服务(fd=%d)... ", getpid(), conn_fd);打印出的fd自然就是 4 了,因为在父进程那就是 4,从来没变过。

再然后,父进程关闭了自己的 fd=4 ,因为这对他没用,同样 listen_fd对子进程也没用,所以子进程关了 listen_fd

这时,当第二个客户端(PID = 11422)请求连接时,父进程的fd=4又空出来了,于是在父进程这里,第二个客户端有拿到了 fd=4 这个文件描述符,然后再次fork,子进程拿到的也是文件描述符fd=4对应的第二个客户端…

所以说,父进程每次将客户端交给子进程后,就会关闭文件描述符 4 ,这个文件描述符 4 空闲出来,下一次有客户端连接用的又是文件描述符 4 ,因此才导致了上面的情况。所以说,这其实是正常现象。

2.3 僵尸进程

多进程模型完美解决了并发问题,看起来似乎无懈可击。但它引入了一个新的、更隐蔽的、可能导致严重后果的东西——**僵尸进程 **。

我们来分析一下上面的程序,如果客户端正常断开连接,那么对应的子进程会执行exit(0),子进程退出后,内核会保留它的 PID 和退出状态等信息,等待父进程过来收尸,但是,父进程此时正忙着在while循环里面accept新的连接,压根就没有时间。

最后,这些无人认领的尸体会越来越多,堆积在内核的进程表里。虽然它们不占很多内存,但会耗尽 PID 资源。当 PID 用完,系统将无法创建任何新进程,最终只能重启。

下面,我们来验证一下僵尸进程的存在:

我来解释一下这三张图,第一张图中我退出了 PID 为 11375 的子进程负责的客户端,然后又以相同的方式退出了其余的两个子进程负责的客户端。

第二张图显示了三个子进程已经执行exit(0);退出了。而父进程还在继续运行,此时父进程在accept等待新的客户端连接,并没有为子进程收尸,因此他们变成了僵尸进程。

第三张图是我查询僵尸进程的结果,图中显示,PID为 11375 , 11422 , 11490 的三个进程此时的状态(STAT)为 Z+,如果一个进程的状态为Z,表示这个进程是僵尸进程,如果是Z+,表示这个进程是一个属于前台进程组的僵尸进程。

一般情况下,如果想要清理一个僵尸进程,最好的办法是杀死它的父进程。但这种方法在这里显然是不行的。

试想一下,一台服务器正在运行,由于其中的几个进程退出后没有及时清理,产生了僵尸进程,而此时你想关闭这台服务器来进行清理僵尸进程,这等于直接断开正在与服务器通信的所有客户端,造成的损失是不可估量的。

下面我介绍一种比较优雅的方法,在服务器端不退出的情况下回收子进程。

2.4 SIGCHLD 信号

如何让父进程在不阻塞 accept 的情况下,及时回收子进程呢?答案是信号

当一个子进程终止时,内核会给其父进程发送一个 SIGCHLD 信号。我们只需要为父进程注册一个处理这个信号的函数即可。

我们需要引入头文件,然后在main函数的前面写一个sigchld_handler函数,此外,还要在main函数里面注册这个信号,放在main函数第一行就行。其他部分不需要改变。

为了简便,我这里只给出最关键的部分,补全到上面多进程模型的代码里面就行了:

#include  

void sigchld_handler(int sig) 
{
    while (waitpid(-1, NULL, WNOHANG) > 0);//非阻塞等待所有子进程
}

int main()
{
    signal(SIGCHLD, sigchld_handler);
    
    //........中间部分不变........
    
    return 0;
}

代码修改之后,我又重新运行了一次。这次我直接创建了三个客户端然后退出。此时父进程还在运行,去查一下发现并没有僵尸进程,这就说明我们的信号处理方法已经生效了。


2.5 补充知识

考虑到可能会有人好奇,从内核向父进程发送SIGCHLD信号到父进程回收僵尸进程,这中间到底是怎样运转的。我增加了这一节,下面详细解释一下。

当子进程执行完毕退出后,内核将其标记为僵尸进程,然后内核去查找该子进程的父进程,并向父进程发送SIGCHLD信号。

父进程此时大概率阻塞在 accept() 上,当SIGCHLD信号到达时,内核会中断父进程的行为,accept() 会立即返回 -1,并将 errno 设置为 EINTR。除此之外,内核还会修改父进程的程序计数器(PC)栈(STACK) ,使得父进程醒来后能从我们的信号处理函数sigchld_handler开始执行。当信号处理函数执行完 return 时,实际上是跳到了内核预设的一段跳板代码,这段代码会调用 sys_sigreturn 系统调用,通知内核恢复父进程之前的上下文,从而让进程醒来后继续从被SIGCHLD信号中断的地方执行。

而在信号处理函数内部,我们调用了 waitpid(-1, NULL, WNOHANG)。-1表示回收任何一个已经终止的子进程,WNOHANG表示非阻塞,没有僵尸进程回收的话就直接返回,防止父进程的main函数那边等太长时间。

为了方便大家理解这个过程,我们可以在前面代码的基础上进行修改,如下所示,在perror上面加了一个if判断:

         conn_fd = accept(listen_fd, NULL, NULL);
		if (conn_fd < 0) 
		{
			if(errno == EINTR)
			{
				printf("accept被SIGCHLD信号中断了,这是正常现象,程序继续运行...
");
			}
			perror("accept");
            continue; //出错了没事,继续等下一个
        }

但是就这样printf的内容是不会按照我们预想的那样打印出来的。因为在默认的signal情况下,内核会让父进程回到accept执行之前的状态,相当于重新运行accept,如果这次没有被SIGCHLD信号中断的话,accept也不会返回-1,errno也不会被置为EINTR,退一万步说,就算再次被SIGCHLD信号中断,下次父进程醒来还是要重新执行accept,也就是说在这种模式下printf的内容是永远不会被打印出来的。

要想看到printf的内容,证明errno确实被置为EINTR了,我们必须采用更高级的信号处理方式。为了方便大家复现,这次给出完整代码吧:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

void sigchld_handler(int sig)
{
	while(waitpid(-1,NULL,WNOHANG)>0);
}

int main() 
{
    //--------------------重点关注这里-----------------//
	struct sigaction sa;
	sa.sa_handler = sigchld_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;

	if (sigaction(SIGCHLD, &sa, NULL) == -1) 
	{
        perror("sigaction error");
        exit(1);
    }
    //------------------------------------------------//

    int listen_fd, conn_fd;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE];
    pid_t pid;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_fd == -1)
	{
		perror("socket listen");
		exit(1);
	}

	memset(&serv_addr,0,sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(PORT);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
	if(bind(listen_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) == -1)
	{
		perror("bind");
		exit(1);
	}

    if(listen(listen_fd, 5) == -1)
	{
		perror("listen");
		exit(1);
	}

    printf("服务器已启动,等待连接...
");

    while (1) { 
        conn_fd = accept(listen_fd, NULL, NULL);
        if (conn_fd < 0) 
		{
            //-------------------------------还有这里-----------------
			if(errno == EINTR)
			{
				printf("accept被SIGCHLD信号中断了,这是正常现象,程序继续运行...
");
			}
            //--------------------------------------------------------
			perror("accept");
            continue; //出错了没事,继续等下一个
        }

        pid = fork();

        if(pid < 0) 
		{
            perror("fork");
            close(conn_fd);
            continue;
        }
		else if(pid == 0) 
		{
            close(listen_fd); 

            printf("子进程%d为新客户端服务(fd=%d)...
", getpid(), conn_fd);

            ssize_t n;
            while (((n = read(conn_fd, buffer, BUFFER_SIZE))) > 0) 
			{
                write(conn_fd, buffer, n);
            }
            
            printf("客户端断开,子进程%d退出。
", getpid());
            close(conn_fd);
            exit(0); 
        } 
		else 
		{
            close(conn_fd); 
        }
    }

    close(listen_fd); // 理论上走不到这里
    return 0;
}

运行后结果如下,这次我们可以看到,printf的内容和perror的内容都被打印出来了,因为默认情况下 signal() 会隐含 SA_RESTART 标志,导致系统调用自动重启。我们把sa.sa_flags设为 0 表示不自动重启,我们才能在 accept 返回处捕获 EINTR

3. 多线程模型

在解决了僵尸进程后,我们的多进程服务器已经非常健壮了。但是,如果我们要面对成千上万个并发连接,每来一个连接就 fork() 一个进程,这种方法开销实在太大了

我们需要一种更轻量的方案——多线程

3.1 改进思路

进程拥有独立的内存空间(堆、栈、代码段)。创建一个进程,内核需要复制页表、文件描述符表等,成本昂贵。

线程共享进程的内存空间。创建一个线程,只需要分配一个小小的栈空间,成本极低。

了解了进程和线程的区别,我们的改进思路就非常明确了,如下:

主线程负责accept()等待客户端连接。每当一个客户端连接上,就pthread_create()一个工作线程去负责。

工作线程负责处理对应客户端的信息。

3.2 线程模型的优缺点

线程模型的优点:

线程创建速度比进程快 10~100 倍。

线程间共享全局变量和堆内存,不需要复杂的 IPC(管道、共享内存等)。

线程模型的缺点:

一个线程崩溃,整个进程(包括所有其他线程)都会挂掉。而多进程模型中,一个子进程崩了,不影响父进程和其他子进程。

共享资源(如全局变量)需要加锁,写不好容易死锁或数据竞争。

虽然比进程轻,但如果是 1 万个连接,依然需要 1 万个线程。操作系统的调度器会被累死,内存也会被撑爆。在 Linux 中,每个新线程的默认栈大小通常为 8 MB,1万个线程栈就是 80 GB。

3.3 小总结

整体上来看,多线程模型和多进程模型的思路其实是相同的,多线程模型虽然减轻了创建开销,但一个连接一个线程 的本质没有变。面对海量并发,我们必须彻底抛弃这种方式,转向一种全新的思维模式

下一章要介绍的就是 Linux 网络编程的终极武器——I/O 多路复用 (epoll)

4. I/O多路复用

虽然多线程模型减轻了创建和销毁的开销,但本质上它依然遵循一个连接一个线程的思路。当并发连接数上升到 1 万甚至 100 万时,这种模型会面临两个无法逾越的障碍:

第一个是内存爆炸,1 万个线程,每个线程栈 8MB,光栈空间就需要 80GB 内存。

第二个是调度崩溃,操作系统在成千上万个线程之间进行上下文切换,CPU 的时间全花在切换上,而不是干正事儿上。

因此,我们需要一种机制,能够用一个线程监控成千上万个连接。这就是 I/O 多路复用

4.1 从线性轮询到事件驱动

epoll 出现之前,Linux 使用 selectpoll。它们虽然能实现多路复用,但在内核实现上存在本质的缺陷。

selectpoll 的工作机制非常原始。每次调用它们,都需要将所有要监控的文件描述符集合从用户态完整拷贝到内核态

内核在检测时,会遍历整个集合**(O(N) 复杂度)。如果有事件发生,内核只是简单地标记有事件已经就绪,但不会告诉说明到底是哪一个。这也就是说,select 返回后,用户态程序还需要再遍历一遍**整个集合,才能找到真正需要处理的那个文件描述符。

当连接数达到 10万,但活跃连接只有 1 个时,select 依然要空转 10万次,这造成了大量的性能浪费。并且select 受限于 FD_SETSIZE (默认是 1024),是无法处理海量连接的。

epoll的出现完美解决了上面的问题。epoll在内核设计了两个核心的数据结构

第一个是红黑树epoll_create 会在内核开辟一块空间,当我们调用 epoll_ctl 添加 socket 时,内核会将这个 socket 节点插入到一棵红黑树中。红黑树的查找、插入、删除复杂度均为 O(logN),即使管理百万级连接,效率依然极高。且这棵树一直在内核里面,无需像 select 那样每次都要重复拷贝文件描述符集合。

第二个是就绪链表,内核为红黑树中的每个 socket 注册了一个回调函数。当网卡收到数据,中断处理程序触发回调,自动将该 socket 节点拷贝到一个**双向链表(也就是就绪链表)**中。

而**epoll_wait要干什么呢?它只需要检查那个就绪链表是否为空**,如果不为空,直接把链表里的节点复制回用户态。

下面我列个表格对比一下,这几种I/O多路复用的方式:

4.2 核心代码实现

下面是一个基于 epoll单线程并发服务器。请注意观察,代码中不再有 forkpthread_create,所有的连接都在同一个 while(1) 循环中被串行处理。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10

int main()
{
	int listen_fd,conn_fd,epoll_fd,n_read;
	struct sockaddr_in server_addr;
	char buffer[BUFFER_SIZE];

	struct epoll_event ev,events[MAX_EVENTS];

	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd == -1)
	{
		perror("socket listen");
		exit(1);
	}
	printf("监听socket创建成功
");

	int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//设置端口可复用

	memset(&server_addr,0,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("bind");
		exit(1);
	}
	printf("绑定成功
");

	if(listen(listen_fd,5) == -1)
	{
		perror("listen");
		exit(1);
	}

	epoll_fd = epoll_create1(0);
	if(epoll_fd == -1)
	{
		perror("epoll_create");
		exit(1);
	}
	printf("epoll实例创建成功
");

	ev.events = EPOLLIN;
	ev.data.fd = listen_fd;
	if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&ev) == -1)
	{
		perror("epoll_ctl");
		exit(1);
	}
	printf("listen_fd添加成功
");
	printf("服务器正在监听%d端口....
",PORT);

	while(1)
	{
		n_read = epoll_wait(epoll_fd,events,MAX_EVENTS,-1);//阻塞等待
		if(n_read == -1)
		{
			perror("epoll_wait");
			exit(1);
		}

		for(int i=0;i<n_read;i++)
		{
			if(events[i].data.fd == listen_fd)//有新的连接到来
			{
				conn_fd = accept(listen_fd,(struct sockaddr *)NULL,NULL);
				printf("接受新的连接,fd = %d
",conn_fd);
				ev.events = EPOLLIN;
				ev.data.fd = conn_fd;
				epoll_ctl(epoll_fd,EPOLL_CTL_ADD,conn_fd,&ev);
			}
			else
			{
				int client_fd = events[i].data.fd;
				ssize_t n = read(client_fd,buffer,BUFFER_SIZE);

				if(n == 0)
				{
					printf("客户端fd = %d关闭了连接
",client_fd);
					epoll_ctl(epoll_fd,EPOLL_CTL_DEL,client_fd,NULL);
					close(client_fd);
				}
				else if(n > 0)
				{
					write(client_fd,buffer,n);
				}
				else
				{
					perror("read");
					close(client_fd);
				}
			}
		}
	}
	close(listen_fd);
	return 0;
}

这段代码才用的是LT水平触发)模式,在while循环之前,我们需要把监听socket(listen_fd)添加到我们需要关注的事件中。

下面简要解释一下这段代码的核心逻辑。

进入while循环后,epoll_wait就会阻塞等待,当它返回时,events数组中就存放着真正有事件发生的文件描述符。相比于 select 需要遍历整个文件描述符集合,epoll 直接把需要处理的文件描述符单独拎出来给我们,这就是它在海量连接下性能不衰减的原因。

然后我们会进入for循环,它会把events数组中需要处理的文件描述符一个一个拿出来,判断他们到底是哪种类型的,是有新的连接到来,还是说旧的连接发消息了,这个判断过程体现在if判断中。

如果有新的连接到来,那我们就accept,并且把这个新链接上的文件描述符添加到我们需要关注的事件中。

如果是旧的连接在发消息,那我们就先read,把这个消息存放在buffer中。在这之后,我们必须要判断一下read的返回值,因为客户端退出会体现在这里。如果客户端退出,read会返回 0,这时我们要把相应的文件描述符从我们需要关注的事件中删除,然后关闭这个文件描述符。如果是正常读写,那我们就再给他回写过去。如果真的出错,那就提示就完事儿了。

4.3 效果展示与新的瓶颈

为了展示这个单线程epoll的效果怎么样,我直接开了三个客户端连接它,并且发送hello都可以正常回写,运行结果如下:

但是请看这里:

				else if(n > 0)
				{
					write(client_fd,buffer,n);
				}

这是我们在接收到客户端消息后进行的处理,只有一条简单的write系统调用。试想一下,如果这里的业务逻辑变成了视频解码,或者复杂的数据库查询,需要消耗 1 秒,会发生什么情况呢?

因为我们只有一个线程在死循环里处理任务。当它正在处理那个耗时 1 秒的任务时,epoll_wait 无法被调用,新的连接进不来,其他客户端的消息也卡在缓冲区里无法处理。整个服务器在这一秒时间里会处于 “假死” 状态。

单线程 epoll 虽然解决了 I/O 的并发,但无法利用多核 CPU 处理计算密集型任务。

因此,我们需要将 epoll (只负责 I/O)线程池 (只负责处理) 结合起来。这就是最终形态——Reactor 模型

5. epoll + 线程池

为了解决单线程 epoll 无法利用多核 CPU,且计算任务会阻塞 I/O 这个痛点,我们需要将 I/O 处理业务逻辑 彻底分离。这也是现代高性能服务器(如 Nginx,Netty)通用的 Reactor 模型

5.1 模型设计

我们引入线程池 来负责具体的业务计算,主线程只负责 I/O 调度

主线程:负责 epoll_waitaccept 新连接、read 数据。收到数据后,立刻封装成一个任务,扔进任务队列。

任务队列:连接主线程与工作线程的桥梁。这里我们复用了之前项目中手搓的高性能 RingBuffer。感兴趣的朋友可以去我前面发的文章看一下这个RingBuffer的具体实现,这里我就不再详细描述了。

工作线程:先从队列取任务,然后执行比较耗时的逻辑(如数据库查询、加密解密),最后将结果write 回客户端。它能充分利用多核 CPU 进行并行计算。

下面这张图是我画的一张示意图,主线程负责将任务pushRingBuffer,工作线程获取任务并处理完毕后,直接将响应结果写入客户端 Socket

5.2 Ring Buffer 实现

我直接将前面那篇《高并发异步日志库》的 RingBuffer 进行简单的修改,如下:

#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#include 
#include 

#define BUFFER_SIZE 1024

//任务结构体
typedef struct {
    int client_fd;           // 记录是谁发的请求
    char data[BUFFER_SIZE];  // 具体数据
} Task;

// 环形缓冲区管理器
typedef struct {
    Task *entries;  // 指向 malloc 的大数组
    int capacity;   // 总容量
    
    int head;
    int tail;
    int count;

    pthread_mutex_t mutex;
    pthread_cond_t cond_producer;
    pthread_cond_t cond_consumer;

    bool is_running; // 优雅退出
} RingBuffer;

void rb_init(RingBuffer *rb, int capacity);
void rb_push(RingBuffer *rb, const Task *entry);
int  rb_pop(RingBuffer *rb, Task *entry);
void rb_stop(RingBuffer *rb);
void rb_destroy(RingBuffer *rb);

#endif

ring_buffer.c文件里面的LOG也要改为TASK,但代码逻辑不需要改变。

5.3 核心代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "ring_buffer.h"

#define PORT 8080
#define MAX_EVENTS 10
#define THREADS_NUM 4  //四个线程处理任务

volatile sig_atomic_t  g_is_running = 1;
RingBuffer *g_rb = NULL;

void sig_handler(int sig)
{
	g_is_running = 0;

	if(g_rb)
	{
		rb_stop(g_rb);
	}
}

void *work_thread(void *arg)
{
	RingBuffer *rb = (RingBuffer *)arg;
	Task task;

	while(rb_pop(rb,&task))//优雅退出
	{
		printf("工作线程%ld拿到任务,处理fd = %d
",pthread_self(),task.client_fd);

		sleep(5);//模拟耗时的运算

        int len = strlen(task.data);
		for(int i=0;i<len;i++) 
		{
			task.data[i] = toupper(task.data[i]);
		}
		write(task.client_fd,task.data,strlen(task.data));
	}
	return NULL;
}

int main()
{
    signal(SIGPIPE, SIG_IGN);
	signal(SIGINT,sig_handler);

	RingBuffer rb;
	rb_init(&rb,10);
	g_rb = &rb;    //让全局指针指向main的rb

	pthread_t threads[THREADS_NUM];
	for(int i=0;i<THREADS_NUM;i++)
	{
		pthread_create(&threads[i],NULL,work_thread,(void *)&rb);
	}

	int listen_fd,conn_fd,epoll_fd,n_read;
	struct sockaddr_in server_addr;

	struct epoll_event ev,events[MAX_EVENTS];

	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd == -1)
	{
		perror("socket listen");
		exit(1);
	}
	printf("监听socket创建成功
");
    
    int opt = 1;
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	memset(&server_addr,0,sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("bind");
		exit(1);
	}
	printf("绑定成功
");

	if(listen(listen_fd,5) == -1)
	{
		perror("listen");
		exit(1);
	}

	epoll_fd = epoll_create1(0);
	if(epoll_fd == -1)
	{
		perror("epoll_create");
		exit(1);
	}
	printf("epoll实例创建成功
");

	ev.events = EPOLLIN;
	ev.data.fd = listen_fd;
	if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&ev) == -1)
	{
		perror("epoll_ctl");
		exit(1);
	}
	printf("listen_fd添加成功
");
	printf("服务器正在监听%d端口....
",PORT);

	while(g_is_running)
	{
		n_read = epoll_wait(epoll_fd,events,MAX_EVENTS,500);//500ms超时
		if(n_read < 0)
		{
			continue;
		}

		for(int i=0;i<n_read;i++)
		{
			if(events[i].data.fd == listen_fd)//有新的连接到来
			{
				conn_fd = accept(listen_fd,(struct sockaddr *)NULL,NULL);
				printf("接受新的连接,fd = %d
",conn_fd);
				ev.events = EPOLLIN;
				ev.data.fd = conn_fd;
				epoll_ctl(epoll_fd,EPOLL_CTL_ADD,conn_fd,&ev);
			}
			else
			{
				int client_fd = events[i].data.fd;
				Task new_task;
				new_task.client_fd = client_fd;

				ssize_t n = read(new_task.client_fd,new_task.data,BUFFER_SIZE-1);

				if(n == 0)
				{
					printf("客户端fd = %d 关闭了连接
",client_fd);
					epoll_ctl(epoll_fd,EPOLL_CTL_DEL,client_fd,NULL);
					close(client_fd);
				}
				else if(n > 0)
				{
					new_task.data[n] = '';
					rb_push(&rb,&new_task);
				}
				else
				{
					perror("read");
					close(client_fd);
				}
			}
		}
	}
	printf("主循环退出,准备关闭服务器
");
	for(int i=0;i<THREADS_NUM;i++)
	{
		pthread_join(threads[i],NULL);
	}

	printf("所有线程已经全部退出
");
	close(listen_fd);
	rb_destroy(&rb);
	printf("服务器已经关闭
");

	return 0;
}

这段核心代码虽然不长,但它诠释了 Reactor 模型的核心思想。下面我要解读一下关键的部分:

主线程运行在 epoll 事件循环中,当 epoll 通知有数据时,主线程调用 read 将数据从内核读到用户态内存。但它自己不进行任何业务处理,而是立刻将 client_fd 和数据封装成一个 TaskpushRingBuffer

4 个工作线程从 RingBuffer 中获取任务。如果队列为空,它们会挂起,不占用 CPU,等待条件变量唤醒。这里我特意用 sleep(5) 模拟了耗时的业务逻辑。在前面的单线程模型中,这会导致服务器卡死 5 秒。但在本模型中,这只是其中一个工作线程在睡觉,主线程依然在飞快地接收新请求,其他工作线程依然在忙自己的事情。业务处理完毕后,工作线程直接调用 write 将结果发回给客户端,到这里完成了闭环。

还有,可能有人注意到了,在 read 时,我使用了 BUFFER_SIZE - 1,预留了一个字节给字符串结束符 防止缓冲区溢出

如果客户端突然断开,工作线程还往里写数据,内核会直接发信号杀掉进程。因此我还在main 函数开头添加了 signal(SIGPIPE, SIG_IGN),防止因客户端意外断开导致服务器写入时崩溃。

此外,我还实现了优雅退出,当你在服务器端按下Ctrl+C时,程序并不会立刻退出,而是去执行sig_handler信号处理函数。返回后主线程会跳出while循环,因为在函数rb_stop中已经通知工作线程结束了,因此主线程在main函数中要进行回收,回收完之后关闭。

5.4 效果验证

为了验证这个模型的效果,运行服务器端后,我还是像前面一样开了三个客户端并连接上服务器。然后我先给这三个客户端每个都分别输入一段内容,但是先不按回车,等输入完了,飞快的切换客户端并按下回车。最终会发现,大约5秒后,这三个客户端是同时收到回复的,这完全说明了这个模型的并发性。

尽管每个任务都要睡 5 秒,但我们可以看到,主线程依然能流畅地接受新的连接请求,没有丝毫卡顿。

观察服务器端的日志,处理 fd=5, fd=6, fd=7 的是不同的工作线程。这说明线程池正在全速运转,正在进行真正的并行工作。


最后直接在服务器端按下Ctrl+C,可以看到优雅退出的信息,工作线程也会随之结束。

6. 标题有六个字

最后再回看正篇文章。如果用的是 fork ,1 万个连接就是 1 万个进程,光是上下文切换就够 CPU 吃一壶得了。而用我们现在的 Reactor 模型,1 万个连接在 epoll 眼里不过是红黑树上的 1 万个节点,只有真正活跃的那几十个连接才会触发回调。

我把完整的 Reactor 模型的源码放在 github 里面了,有需要的可以自取,感觉有帮助的朋友也可以点上一手关注

👉源码点击这里

本文地址:https://www.yitenyun.com/4403.html

搜索文章

Tags

#服务器 #python #pip #conda #人工智能 #微信 #ios面试 #ios弱网 #断点续传 #ios开发 #objective-c #ios #ios缓存 #远程工作 #Trae #IDE #AI 原生集成开发环境 #Trae AI #kubernetes #笔记 #平面 #容器 #linux #学习方法 香港站群服务器 多IP服务器 香港站群 站群服务器 #运维 #学习 #银河麒麟高级服务器操作系统安装 #银河麒麟高级服务器V11配置 #设置基础软件仓库时出错 #银河麒高级服务器系统的实操教程 #生产级部署银河麒麟服务系统教程 #Linux系统的快速上手教程 #hadoop #hbase #hive #zookeeper #spark #kafka #flink #科技 #深度学习 #自然语言处理 #神经网络 #分阶段策略 #模型协议 #华为云 #部署上线 #动静分离 #Nginx #新人首发 #飞牛nas #fnos #kylin #docker #arm #harmonyos #鸿蒙PC #大数据 #职场和发展 #程序员创富 #ARM服务器 # GLM-4.6V # 多模态推理 #fastapi #html #css #低代码 #爬虫 #音视频 #经验分享 #安卓 #PyTorch #模型训练 #星图GPU #tcp/ip #网络 #qt #C++ #物联网 #websocket #开源 #ide #java #开发语言 #前端 #javascript #架构 #语言模型 #大模型 #ai #ai大模型 #agent #github #git #langchain #数据库 #进程控制 #gemini #gemini国内访问 #gemini api #gemini中转搭建 #Cloudflare #unity #c# #游戏引擎 #MobaXterm #ubuntu #Conda # 私有索引 # 包管理 #ssh #AI编程 #数信院生信服务器 #Rstudio #生信入门 #生信云服务器 #aws #云计算 #word #umeditor粘贴word #ueditor粘贴word #ueditor复制word #ueditor上传word图片 #node.js #windows #RTP over RTSP #RTP over TCP #RTSP服务器 #RTP #TCP发送RTP #Reactor #内网穿透 #cpolar #云原生 #iventoy #VmWare #OpenEuler #后端 #ci/cd #jenkins #gitlab #区块链 #测试用例 #生活 #儿童书籍 #儿童诗歌 #童话故事 #经典好书 #儿童文学 #好书推荐 #经典文学作品 #风控模型 #决策盲区 #nginx #centos #svn #openHiTLS #TLCP #DTLCP #密码学 #商用密码算法 #c++ #算法 #牛客周赛 #flutter #缓存 #serverless #diskinfo # TensorFlow # 磁盘健康 #vscode #mobaxterm #计算机视觉 #Harbor #FTP服务器 #私有化部署 #自动化 #ansible #fabric #postgresql #http #项目 #高并发 #矩阵 #线性代数 #AI运算 #向量 #android #腾讯云 #microsoft #文心一言 #AI智能体 #驱动开发 #agi #log4j #ollama #sql #AIGC #java-ee #分布式 #华为 #iBMC #UltraISO #mcu #vue上传解决方案 #vue断点续传 #vue分片上传下载 #vue分块上传下载 #dify #多个客户端访问 #IO多路复用 #回显服务器 #TCP相关API #mcp #mcp server #AI实战 #pycharm #php #jar #Dell #PowerEdge620 #内存 #硬盘 #RAID5 #Ubuntu服务器 #硬盘扩容 #命令行操作 #VMware #阿里云 #企业开发 #ERP #项目实践 #.NET开发 #C#编程 #编程与数学 #flask #pytorch #select #spring cloud #spring #vue.js #mysql #json #PyCharm # 远程调试 # YOLOFuse #网络协议 #信息与通信 #prometheus #大模型学习 #AI大模型 #大模型教程 #大模型入门 #uni-app #小程序 #notepad++ #开源软件 #rocketmq #程序人生 #科研 #博士 #jmeter #功能测试 #软件测试 #自动化测试 #鸿蒙 #内存治理 #django #web #webdav #安全 #数据结构 #c语言 #嵌入式 #ecmascript #elementui #es安装 #Ansible # 自动化部署 # VibeThinker #udp #chatgpt #DeepSeek #AI #DS随心转 #散列表 #哈希算法 #leetcode #数学建模 #2026年美赛C题代码 #2026年美赛 #服务器繁忙 #企业微信 #spring boot #重构 #机器学习 #超算服务器 #算力 #高性能计算 #仿真分析工作站 #蓝桥杯 #jetty #scrapy #硬件工程 #powerpoint #Com #golang #redis #产品经理 #ui #团队开发 #墨刀 #figma #游戏 #mvp #个人开发 #设计模式 #jvm #MCP #MCP服务器 #钉钉 #机器人 #京东云 #性能优化 #深度优先 #DFS #vllm #Streamlit #Qwen #本地部署 #AI聊天机器人 #数据集 #进程 #LLM #FL Studio #FLStudio #FL Studio2025 #FL Studio2026 #FL Studio25 #FL Studio26 #水果软件 #vim #gcc #yum #FaceFusion # Token调度 # 显存优化 #计算机网络 #mmap #nio #rabbitmq #protobuf #课程设计 #正则 #正则表达式 #毕业设计 #shell #CPU利用率 #鸭科夫 #逃离鸭科夫 #鸭科夫联机 #鸭科夫异地联机 #开服 #Linux #TCP #线程 #线程池 #ffmpeg #酒店客房管理系统 #毕设 #论文 #todesk #阻塞队列 #生产者消费者模型 #服务器崩坏原因 #wsl #L2C #勒让德到切比雪夫 #数据仓库 #我的世界 #claude #操作系统 #AI产品经理 #大模型开发 #svm #amdgpu #kfd #ROCm #Android #Bluedroid #网络安全 #web安全 #守护进程 #复用 #screen #需求分析 #scala #测试工具 #压力测试 #全能视频处理软件 #视频裁剪工具 #视频合并工具 #视频压缩工具 #视频字幕提取 #视频处理工具 #系统架构 #程序员 #自动驾驶 #Canal #社科数据 #数据分析 #数据挖掘 #数据统计 #经管数据 #adb #ModelEngine #边缘计算 #debian #gpu算力 #DisM++ # 系统维护 #金融 #金融投资Agent #Agent #vue3 #天地图 #403 Forbidden #天地图403错误 #服务器403问题 #天地图API #部署报错 #autosar #AI论文写作工具 #学术论文创作 #论文效率提升 #MBA论文写作 #SSH # ProxyJump # 跳板机 #信息可视化 #claude code #codex #code cli #ccusage #Ascend #MindIE #stm32 #嵌入式硬件 #银河麒麟操作系统 #openssh #华为交换机 #信创终端 #ssl #大语言模型 #长文本处理 #GLM-4 #Triton推理 #线性回归 #智能手机 #opencv #语音识别 #幼儿园 #园长 #幼教 #数模美赛 #matlab #openresty #lua #n8n #电气工程 #C# #PLC #everything #sizeof和strlen区别 #sizeof #strlen #计算数据类型字节数 #计算字符串长度 #设备驱动 #芯片资料 #网卡 #AI写作 #树莓派4b安装系统 #单片机 #我的世界服务器搭建 #minecraft #YOLO #流量监控 #架构师 #软考 #系统架构师 #epoll #高级IO #MC #数组 #信号处理 #目标跟踪 #面试 #几何学 #拓扑学 #链表 #链表的销毁 #链表的排序 #链表倒置 #判断链表是否有环 #AB包 #react.js #硬件 #电脑 #LoRA # RTX 3090 # lora-scripts #grafana #pdf #fiddler #PowerBI #企业 #ddos #googlecloud #智慧校园解决方案 #智慧校园一体化平台 #智慧校园选型 #智慧校园采购 #智慧校园软件 #智慧校园专项资金 #智慧校园定制开发 #asp.net大文件上传 #asp.net大文件上传下载 #asp.net大文件上传源码 #ASP.NET断点续传 #asp.net上传文件夹 #测试流程 #金融项目实战 #P2P #webrtc #银河麒麟 #系统升级 #信创 #国产化 #ping通服务器 #读不了内网数据库 #bug菌问答团队 #arm开发 #Modbus-TCP #流程图 #论文阅读 #论文笔记 #SSM 框架 #孕期健康 #产品服务推荐 #推荐系统 #用户交互 #Windows 更新 #azure #数码相机 #Coze工作流 #AI Agent指挥官 #多智能体系统 #编辑器 #VS Code调试配置 #无人机 #Deepoc #具身模型 #开发板 #未来 #ida #研发管理 #禅道 #禅道云端部署 #asp.net #考研 #软件工程 #中间件 #tdengine #时序数据库 #制造 #涛思数据 #zabbix #1024程序员节 #STUN # TURN # NAT穿透 #RAID #RAID技术 #磁盘 #存储 #transformer #cnn #Node.js #漏洞检测 #CVE-2025-27210 #ROS #零售 #unity3d #服务器框架 #Fantasy #elasticsearch #visual studio code #振镜 #振镜焊接 #生信 #java大文件上传 #java大文件秒传 #java大文件上传下载 #java文件传输解决方案 #journalctl #里氏替换原则 #RAG #全链路优化 #实战教程 #wordpress #雨云 #whisper #LobeChat #vLLM #GPU加速 #selenium #游戏私服 #云服务器 #目标检测 #YOLO26 #YOLO11 #若依 #quartz #框架 #SSH Agent Forwarding # PyTorch # 容器化 #SSH反向隧道 # Miniconda # Jupyter远程访问 #abtest #流量运营 #用户运营 #homelab #Lattepanda #Jellyfin #Plex #Emby #Kodi #智能路由器 #oracle #其他 #iphone #凤希AI伴侣 #聚类 #堡垒机 #安恒明御堡垒机 #windterm #建筑缺陷 #红外 #双指针 #LabVIEW知识 #LabVIEW程序 #labview #LabVIEW功能 #tomcat #firefox #WEB #CMake #Make #C/C++ #Python #laravel #vps #漏洞 #微信小程序 # 公钥认证 #Playbook #AI服务器 #simulink #sqlserver #clickhouse ##程序员和算法的浪漫 #JAVA #Java #北京百思可瑞教育 #百思可瑞教育 #北京百思教育 #https #apache #流媒体 #NAS #飞牛NAS #监控 #NVR #EasyNVR #机器视觉 #6D位姿 #risc-v #ms-swift # 一锤定音 # 大模型微调 #deepseek #负载均衡 #ESXi #SSH公钥认证 # 安全加固 #cpp #Shiro #反序列化漏洞 #CVE-2016-4437 #vuejs #运营 #React安全 #漏洞分析 #Next.js #eBPF #RAGFlow #DeepSeek-R1 #联机教程 #局域网联机 #局域网联机教程 #局域网游戏 #EMC存储 #存储维护 #NetApp存储 #学习笔记 #jdk #Qwen3-14B # 大模型部署 # 私有化AI #搜索引擎 #vp9 #CFD #AutoDL #LangGraph #模型上下文协议 #MultiServerMCPC #load_mcp_tools #load_mcp_prompt #screen 命令 #paddlepaddle #macos #fpga开发 #LVDS #高速ADC #DDR # GLM-TTS # 数据安全 #支付 #Gunicorn #WSGI #Flask #并发模型 #容器化 #性能调优 #HeyGem # 远程访问 # 服务器IP配置 #MS #Materials #结构体 #2026AI元年 #年度趋势 #国产PLM #瑞华丽PLM #瑞华丽 #PLM #多线程 #性能调优策略 #双锁实现细节 #动态分配节点内存 #ai编程 #SMTP # 内容安全 # Qwen3Guard #X11转发 #Miniconda #llama #ceph #SAP #ebs #metaerp #oracle ebs #改行学it #创业创新 #5G #平板 #交通物流 #智能硬件 #HBA卡 #RAID卡 #框架搭建 #Chat平台 #ARM架构 #RustDesk # IndexTTS 2.0 # 远程运维 #蓝耘智算 #插件 #r-tree #VibeVoice # Triton # 语音合成 #glibc #C语言 #海外短剧 #海外短剧app开发 #海外短剧系统开发 #短剧APP #短剧APP开发 #短剧系统开发 #海外短剧项目 #tensorflow #可信计算技术 #winscp #智能体 #ONLYOFFICE #MCP 服务器 #智慧城市 #推荐算法 # 双因素认证 #GPU服务器 #8U #硬件架构 #log #NPU #CANN #前端框架 #cursor #PyTorch 特性 #动态计算图 #张量(Tensor) #自动求导Autograd #GPU 加速 #生态系统与社区支持 #与其他框架的对比 #cascadeur #设计师 #游戏美术 #游戏策划 #Docker #spine #进程创建与终止 #vue #OBC #H5 #跨域 #发布上线后跨域报错 #请求接口跨域问题解决 #跨域请求代理配置 #request浏览器跨域 #智能一卡通 #门禁一卡通 #梯控一卡通 #电梯一卡通 #消费一卡通 #一卡通 #考勤一卡通 #远程桌面 #远程控制 #llm #游戏机 #UDP的API使用 #bash #tcpdump #ngrok #embedding #3d #IndexTTS 2.0 #本地化部署 #求职招聘 #车辆排放 #Spring AI #STDIO协议 #Streamable-HTTP #McpTool注解 #服务器能力 #paddleocr #Anything-LLM #IDC服务器 #贴图 #材质 #工具集 #ProCAST2025 #ProCast #脱模 #顶出 #应力计算 #铸造仿真 #变形计算 #ssm #mybatis #版本控制 #Git入门 #开发工具 #代码托管 #pencil #pencil.dev #设计 #个人博客 #lvs #sqlite #intellij-idea #database #idea #nas #Triton # CUDA #p2p #openclaw #910B #SSH保活 #远程开发 #分类 #海外服务器安装宝塔面板 #powerbi #翻译 #开源工具 #状态模式 #嵌入式编译 #ccache #distcc #计算机 #连锁药店 #连锁店 #puppeteer #openlayers #bmap #tile #server # GLM-4.6V-Flash-WEB # 显卡驱动备份 #简单数论 #埃氏筛法 #openEuler #Hadoop #客户端 #DIY机器人工房 #nacos #银河麒麟aarch64 #uvicorn #uvloop #asgi #event #迁移重构 #数据安全 #代码迁移 #.net #yolov12 #研究生life #restful #ajax #Claude #视频去字幕 #文生视频 #CogVideoX #AI部署 #TensorRT # 推理优化 #零代码平台 #AI开发 #Karalon #AI Test #信令服务器 #Janus #MediaSoup #esp32教程 #SA-PEKS # 关键词猜测攻击 # 盲签名 # 限速机制 #模版 #函数 #类 #笔试 #图像处理 #yolo #Jetty # CosyVoice3 # 嵌入式服务器 #rust # 服务器IP # 端口7860 #高品质会员管理系统 #收银系统 #同城配送 #最好用的电商系统 #最好用的系统 #推荐的前十系统 #JAVA PHP 小程序 #万悟 #联通元景 #镜像 #就业 #OSS #idm #echarts # 云服务器 #逻辑回归 #健身房预约系统 #健身房管理系统 #健身管理系统 #Fun-ASR # 硬件配置 # 语音识别 #算力一体机 #ai算力服务器 #ThingsBoard MCP #青少年编程 #wps # 高并发部署 #代理 #SMP(软件制作平台) #EOM(企业经营模型) #应用系统 # 服务器IP访问 # 端口映射 #CSDN #寄存器 #mariadb #gateway #Comate #遛狗 #bug #运维开发 #Rust #微服务 #wpf #arm64 #ue4 #ue5 #DedicatedServer #独立服务器 #专用服务器 #tornado #webpack #学术写作辅助 #论文创作效率提升 #AI写论文实测 #C++ UA Server #SDK #Windows #跨平台开发 #项目申报系统 #项目申报管理 #项目申报 #企业项目申报 #eclipse #servlet #UOS #海光K100 #统信 #reactjs #web3 #maven #长文本理解 #glm-4 #推理部署 #串口服务器 #Modbus #MOXA #GATT服务器 #蓝牙低功耗 #FASTMCP # WebUI #Go并发 #高并发架构 #Goroutine #系统设计 #Dify #鲲鹏 #密码 #CUDA #电商 #部署 #人脸识别 #人脸核身 #活体检测 #身份认证与人脸对比 #微信公众号 #昇腾300I DUO #产品运营 #1panel #vmware #集成测试 #贪心算法 #c++20 #ip #说话人验证 #声纹识别 #CAM++ #vnstat #汇编 # 远程连接 #ICPC #typescript #npm #压枪 #VPS #搭建 #土地承包延包 #领码SPARK #aPaaS+iPaaS #数字化转型 #智能审核 #档案数字化 #PTP_1588 #gPTP #农产品物流管理 #物流管理系统 #农产品物流系统 #农产品物流 #matplotlib #安全架构 #unix #攻防演练 #Java web #红队 #dubbo #opc ua #opc #anaconda #虚拟环境 #SSH跳板机 # Python3.11 #东方仙盟 #gitea #API限流 # 频率限制 # 令牌桶算法 #VSCode # SSH #黑群晖 #虚拟机 #无U盘 #纯小白 #指针 #网站 #截图工具 #批量处理图片 #图片格式转换 #图片裁剪 #进程等待 #wait #waitpid #蓝湖 #Axure原型发布 #TCP服务器 #开发实战 #ambari #Android16 #音频性能实战 #音频进阶 #单元测试 #markdown #建站 #门禁 #梯控 #智能梯控 #结构与算法 #源代码管理 #turn #黑客技术 #渗透测试 #网安应急响应 #微PE # GLM # 服务连通性 #可撤销IBE #服务器辅助 #私钥更新 #安全性证明 #双线性Diffie-Hellman #muduo库 #uv #uvx #uv pip #npx #Ruff #pytest #扩展屏应用开发 #android runtime # 高并发 #CTF #数据恢复 #视频恢复 #视频修复 #RAID5恢复 #流媒体服务器恢复 #TLS协议 #HTTPS #漏洞修复 #运维安全 #SSE # AI翻译机 # 实时翻译 #聊天小程序 #Tokio #昇腾 #排序算法 #插入排序 #策略模式 #服务器解析漏洞 #TFTP #RSO #机器人操作系统 #NFC #智能公交 #服务器计费 #FP-增长 #Anaconda配置云虚拟环境 #MQTT协议 #能源 #汽车 #vivado license #CVE-2025-68143 #CVE-2025-68144 #CVE-2025-68145 #html5 #weston #x11 #x11显示服务器 #交互 #GB/T4857 #GB/T4857.17 #GB/T4857测试 #集成学习 #证书 #kmeans #数字孪生 #三维可视化 #Proxmox VE #虚拟化 #esb接口 #走处理类报异常 #树莓派 #N8N # 数字人系统 # 远程部署 #chrome #dreamweaver #smtp #smtp服务器 #PHP #intellij idea #WinDbg #Windows调试 #内存转储分析 #fs7TF #AI+ #coze #AI入门 #AI赋能 #计组 #数电 #导航网 #浏览器自动化 #python #cosmic #rustdesk #连接数据库报错 #运维工具 #YOLOFuse # Base64编码 # 多模态检测 #智能家居 #npu #React #Next #CVE-2025-55182 #RSC #DNS #SSH免密登录 #Discord机器人 #云部署 #程序那些事 #SFTP #HCIA-Datacom #H12-811 #题库 #最新题库 #系统安全 #ipmitool #BMC # 黑屏模式 # TTS服务器 #C #JumpServer #bootstrap #移动端h5网页 #调用浏览器摄像头并拍照 #开启摄像头权限 #拍照后查看与上传服务器端 #摄像头黑屏打不开问题 #处理器 #静脉曲张 #腿部健康 #SPA #单页应用 #web3.py #上下文工程 #langgraph #意图识别 #swagger #IndexTTS2 # 阿里云安骑士 # 木马查杀 #分布式数据库 #集中式数据库 #业务需求 #选型误 #ESP32 #传感器 #MicroPython #RK3576 #瑞芯微 #硬件设计 #teamviewer #文件IO #输入输出流 #数据采集 #浏览器指纹 #麒麟OS #CosyVoice3 # IP配置 # 0.0.0.0 # 大模型 # 模型训练 #网络配置实战 #Web/FTP 服务访问 #计算机网络实验 #外网访问内网服务器 #Cisco 路由器配置 #静态端口映射 #网络运维 #RPA #影刀RPA #AI办公 #elk #jupyter #Socket网络编程 # 目标检测 #异步编程 #系统编程 #Pin #http服务器 #chat #edge #迭代器模式 #观察者模式 #twitter #机器人学习 #iot #CLI #JavaScript #langgraph.json #UDP套接字编程 #UDP协议 #网络测试 #raid #raid阵列 #SRS #直播 #milvus #springboot #知识库 #web server #请求处理流程 #Host #SSRF #知识 #agentic bi #论文复现 #远程连接 #VoxCPM-1.5-TTS # 云端GPU # PyCharm宕机 #娱乐 #敏捷流程 #政务 #pjsip #语音生成 #TTS #音乐分类 #音频分析 #ViT模型 #Gradio应用 #IO #鼠大侠网络验证系统源码 #AI赋能盾构隧道巡检 #开启基建安全新篇章 #以注意力为核心 #YOLOv12 #AI隧道盾构场景 #盾构管壁缺陷病害异常检测预警 #隧道病害缺陷检测 # 水冷服务器 # 风冷服务器 #rdp #Nacos #AI生成 # outputs目录 # 自动化 #学术生涯规划 #CCF目录 #基金申请 #职称评定 #论文发表 #科研评价 #顶会顶刊 #蓝牙 #LE Audio #BAP #go #Clawdbot #个人助理 #数字员工 #Kuikly #openharmony #ComfyUI # 推理服务器 #libosinfo #SEO优化 #宝塔面板部署RustDesk #RustDesk远程控制手机 #手机远程控制 #esp32 arduino #HistoryServer #Spark #YARN #jobhistory #sglang #安全威胁分析 #源码 #闲置物品交易系统 #内存接口 # 澜起科技 # 服务器主板 #IPv6 #Fluentd #Sonic #日志采集 #动态规划 #模拟退火算法 # REST API #性能 #优化 #RAM #flume #mongodb #x86_64 #数字人系统 #EN4FE #windows11 #系统修复 #自由表达演说平台 #演说 #文件传输 #电脑文件传输 #电脑传输文件 #电脑怎么传输文件到另一台电脑 #电脑传输文件到另一台电脑 #gpu #nvcc #cuda #nvidia #国产开源制品管理工具 #Hadess #一文上手 #UDP #prompt #YOLOv8 # Docker镜像 #RXT4090显卡 #RTX4090 #深度学习服务器 #硬件选型 #群晖 #音乐 #IntelliJ IDEA #Spring Boot #neo4j #NoSQL #SQL #OPCUA #环境搭建 #pandas #mamba #高考 #多模态 #微调 #超参 #LLamafactory #SMARC #ARM #工程实践 #ipv6 # 代理转发 #duckdb #图像识别 #空间计算 #原型模式 #Java程序员 #Java面试 #后端开发 #Spring源码 #Spring #SpringBoot #devops #cesium #可视化 #国产操作系统 #麒麟 #V11 #kylinos #TURN # WebRTC # HiChatBox #web服务器 #排序 #Linux多线程 #LangFlow # 智能运维 # 性能瓶颈分析 # GPU租赁 # 自建服务器 #list #aiohttp #asyncio #异步 #网络编程 #I/O模型 #并发 #水平触发、边缘触发 #多路复用 #软件 #本地生活 #电商系统 #商城 #MinIO服务器启动与配置详解 #.netcore #SSH复用 # 远程开发 #H3C # 模型微调 #磁盘配额 #存储管理 #文件服务器 #形考作业 #国家开放大学 #系统运维 #自动化运维 #DHCP #AI大模型应用开发 #注入漏洞 #Aluminium #Google #实体经济 #商业模式 #软件开发 #数智红包 #商业变革 #创业干货 #语义搜索 #嵌入模型 #Qwen3 #AI推理 #材料工程 #智能电视 #挖漏洞 #攻击溯源 #编程 #因果学习 # ControlMaster #tcp/ip #网络 #Tracker 服务器 #响应最快 #torrent 下载 #2026年 #Aria2 可用 #迅雷可用 #BT工具通用 #net core #kestrel #web-server #asp.net-core #safari #AI技术 #Zabbix #语音合成 #b树 #le audio #低功耗音频 #通信 #连接 #memory mcp #Cursor #docker-compose #Tetrazine-Acid #1380500-92-4 #postman #windbg分析蓝屏教程 #云开发 #Buck #NVIDIA #交错并联 #DGX #KMS 激活 #AI智能棋盘 #Rock Pi S #高仿永硕E盘的个人网盘系统源码 #游戏程序 #IFix #视觉检测 #云计算运维 #asp.net上传大文件 #递归 #线性dp #xss #webgl #c++高并发 #百万并发 #Termux #Samba #SSH别名 #CS2 #debian13 #BoringSSL #gerrit #GB28181 #SIP信令 #视频监控 #WT-2026-0001 #QVD-2026-4572 #smartermail #信创国产化 #达梦数据库 #excel #TTS私有化 # IndexTTS # 音色克隆 #ShaderGraph #图形 #http头信息 #uip #k8s #VMware Workstation16 #服务器操作系统 #音诺ai翻译机 #AI翻译机 # Ampere Altra Max # 边缘计算 #支持向量机 #启发式算法 #screen命令 #GPU ##租显卡 #系统管理 #服务 # 离线AI #全文检索 #银河麒麟服务器系统 # ARM服务器 # 大模型推理 #Kylin-Server #服务器安装 #短剧 #短剧小程序 #短剧系统 #微剧 #管道Pipe #system V #技术美术 #用户体验 #文件上传漏洞 #区间dp #二进制枚举 #图论 #大学生 #大作业 #H5网页 #网页白屏 #H5页面空白 #资源加载问题 #打包部署后网页打不开 #HBuilderX #A2A #GenAI #域名注册 #新媒体运营 #网站建设 #国外域名 #DDD #tdd #VMWare Tool #easyui #服务器开启 TLS v1.2 #IISCrypto 使用教程 #TLS 协议配置 #IIS 安全设置 #服务器运维工具 #esp32 #mosquito #心理健康服务平台 #心理健康系统 #心理服务平台 #心理健康小程序 #题解 #图 #dijkstra #迪杰斯特拉 #AI-native #dba #国产化OS #react native #程序开发 #程序设计 #计算机毕业设计 #SSH跳转 # GPU集群 #nodejs #云服务器选购 #Saas #CPU #计算几何 #斜率 #方向归一化 #叉积 #智能体从0到1 #新手入门 #samba # 批量管理 #NSP #下一状态预测 #aigc #ASR #SenseVoice #outlook #错误代码2603 #无网络连接 #2603 #mtgsig #美团医药 #美团医药mtgsig #美团医药mtgsig1.2 #性能测试 #LoadRunner #Socket #套接字 #I/O多路复用 #字节序 #测试覆盖率 #可用性测试 #DAG #具身智能 #练习 #基础练习 #循环 #九九乘法表 #计算机实现 # Qwen3Guard-Gen-8B #工厂模式 #HarmonyOS APP #lstm #旅游 #Moltbook #AI电商客服 #随机森林 #飞书 #经济学 #网路编程 #银河麒麟部署 #银河麒麟部署文档 #银河麒麟linux #银河麒麟linux部署教程 #声源定位 #MUSIC #JNI #晶振 #测评 #CCE #Dify-LLM #Flexus #resnet50 #分类识别训练 #媒体 #OpenManage #AI视频创作系统 #AI视频创作 #AI创作系统 #AI视频生成 #AI工具 #AI创作工具 #TRO #TRO侵权 #TRO和解 #Python3.11 #Xshell #Finalshell #生物信息学 #组学 #AI 推理 #NV #Spire.Office #隐私合规 #网络安全保险 #法律风险 #风险管理 #memcache #大剑师 #nodejs面试题 #ServBay #C2000 #TI #实时控制MCU #AI服务器电源 #r语言 #Llama-Factory # 树莓派 # ARM架构 #单例模式 #服务器IO模型 #非阻塞轮询模型 #多任务并发模型 #异步信号模型 #多路复用模型 #远程访问 #远程办公 #飞网 #安全高效 #配置简单 #快递盒检测检测系统 #ranger #MySQL8.0 #统信UOS #win10 #qemu #远程软件 #领域驱动 #STDIO传输 #SSE传输 #WebMVC #WebFlux #ansys #ansys问题解决办法 #逆向工程 #工业级串口服务器 #串口转以太网 #串口设备联网通讯模块 #串口服务器选型 #blender #vertx #vert.x #vertx4 #runOnContext #visual studio # Connection refused #入侵 #日志排查 #智能体来了 #智能体对传统行业冲击 #行业转型 #HarmonyOS #Apple AI #Apple 人工智能 #FoundationModel #Summarize #SwiftUI #防火墙 #gRPC #注册中心 #odoo #win11 #企业级存储 #网络设备 #muduo #TcpServer #accept #高并发服务器 #Smokeping #路由器 #xeon #pve #Redis #分布式锁 #galeweather.cn #高精度天气预报数据 #光伏功率预测 #风电功率预测 #高精度气象 #appche #视觉理解 #Moondream2 #多模态AI #c #zotero #WebDAV #同步失败 #代理模式 #大模型应用 #API调用 #PyInstaller打包运行 #服务端部署 #勒索病毒 #勒索软件 #加密算法 #.bixi勒索病毒 #数据加密 # 轻量化镜像 #实时音视频 #业界资讯 #欧拉 #量子计算 #WinSCP 下载安装教程 #FTP工具 #服务器文件传输 #JT/T808 #车联网 #车载终端 #模拟器 #仿真器 #开发测试 #copilot #硬盘克隆 #DiskGenius #mapreduce #Langchain-Chatchat # 国产化服务器 # 信创 #opc模拟服务器 #儿童AI #图像生成 #报表制作 #职场 #数据可视化 #用数据讲故事 #AE #手机h5网页浏览器 #安卓app #苹果ios APP #手机电脑开启摄像头并排查 #Keycloak #Quarkus #AI编程需求分析 #hibernate #生产服务器问题查询 #日志过滤 #ArkUI #ArkTS #鸿蒙开发 #服务器线程 # SSL通信 # 动态结构体 # 自动化运维 #AITechLab #cpp-python #CUDA版本 #节日 #ZooKeeper #ZooKeeper面试题 #面试宝典 #深入解析 #大模型部署 #mindie #大模型推理 #Ubuntu #ESP32编译服务器 #Ping #DNS域名解析 #n8n解惑 #KMS #slmgr #可再生能源 #绿色算力 #风电 #ARM64 # DDColor # ComfyUI #游戏服务器断线 # keep-alive #地理 #遥感 #POC #问答 #交付 #面向对象 #xlwings #Excel #taro #AI应用编程 #dlms #dlms协议 #逻辑设备 #逻辑设置间权限 #七年级上册数学 #有理数 #有理数的加法法则 #绝对值 #广播 #组播 #并发服务器 #主板 #总体设计 #电源树 #框图 #Minecraft #Minecraft服务器 #PaperMC #我的世界服务器 #前端开发 # 服务器迁移 # 回滚方案 #Archcraft #nfs #iscsi #clamav #eureka #文件管理 #rtsp #转发 #kong #Kong Audio #Kong Audio3 #KongAudio3 #空音3 #空音 #中国民乐 #范式 #企业存储 #RustFS #对象存储 #高可用 #三维 #3D #三维重建 #榛樿鍒嗙被 #命令模式 #CVE-2025-61686 #路径遍历高危漏洞 #scanf #printf #getchar #putchar #cin #cout #ET模式 #非阻塞 #模块 #强化学习 #策略梯度 #REINFORCE #蒙特卡洛 #AI应用 #百度 #ueditor导入word #L6 #L10 #L9 #戴尔服务器 #戴尔730 #装系统 #junit #KMS激活 #gpt #API #coffeescript #软件需求 #OCR #文字检测 #数据访问 #LED #设备树 #GPIO #composer #symfony #java-zookeeper #poll #vrrp #脑裂 #keepalived主备 #高可用主备都持有VIP #传统行业 #个性化推荐 #BERT模型 #mssql #lucene #warp #sentinel #DooTask #Prometheus #日志分析 #交换机 #三层交换机 #高斯溅射 #nmodbus4类库使用教程 #UEFI #BIOS #Legacy BIOS #Puppet # IndexTTS2 # TTS #个人电脑 #MC群组服务器 #职场发展 #claude-code #高精度农业气象 #漏洞挖掘 #Ward #sklearn # 权限修复 #ICE #文本生成 #CPU推理 #WAN2.2 # 鲲鹏 #4U8卡 AI 服务器 ##AI 服务器选型指南 #GPU 互联 #GPU算力 #编程助手 #卷积神经网络 #温湿度监控 #WhatsApp通知 #IoT #MySQL #视频 #Moltbot #统信操作系统 #nosql #超时设置 #客户端/服务器 #挖矿 #Linux病毒 #人形机器人 #人机交互 #xml #电梯 #电梯运力 #电梯门禁 #vncdotool #链接VNC服务器 #如何隐藏光标 #Gateway #认证服务器集成详解 #FHSS #uniapp #合法域名校验出错 #服务器域名配置不生效 #request域名配置 #已经配置好了但还是报错 #uniapp微信小程序 #bond #服务器链路聚合 #网卡绑定 #CNAS #CMA #程序文件 #数据报系统 #华为od #华为机试 # GPU服务器 # tmux #wireshark #网络安全大赛 #idc #算力建设 #效率神器 #办公技巧 #自动化工具 #Windows技巧 #打工人必备 #智能制造 #供应链管理 #工业工程 #库存管理 #RK3588 #RK3588J #评估板 #核心板 #嵌入式开发 #SSH密钥 #dynadot #域名 #ETL管道 #向量存储 #数据预处理 #DocumentReader #后端框架 #Cpolar #国庆假期 #服务器告警 #MCP服务器注解 #异步支持 #方法筛选 #声明式编程 #自动筛选机制 #pxe #hdfs #free #vmstat #sar #网络攻击模型 #pyqt # 网络延迟 #clawdbot #QQbot #QQ #FRP # OTA升级 # 黄山派 #内网 #CMC #WRF #WRFDA #公共MQTT服务器 #代理服务器 #Matrox MIL #二次开发 #雨云服务器 #教程 #MCSM面板 # DIY主机 # 交叉编译 #Spring AOP #跳槽 #工作 #0day漏洞 #DDoS攻击 #漏洞排查 #sql注入 #懒汉式 #恶汉式 #人大金仓 #Kingbase #租显卡 #训练推理 # 服务器配置 # GPU #多进程 #python技巧 #轻量化 #低配服务器 #ftp #sftp #CA证书 #OpenHarmony #CS336 #Assignment #Experiments #TinyStories #Ablation #工程设计 #预混 #扩散 #燃烧知识 #层流 #湍流 #numpy #余行补位 #意义对谈 #余行论 #领导者定义计划 # 批量部署 #星际航行 # 键鼠锁定 #cpu #RWK35xx #语音流 #实时传输 #node #反向代理 #rag #Syslog #系统日志 #日志监控 #Autodl私有云 #深度服务器配置 #ARMv8 #内存模型 #内存屏障 #参数估计 #矩估计 #概率论 #cocos2d #图形渲染 #三种参数 #参数的校验 #fastAPI #人脸识别sdk #视频编解码 #canvas层级太高 #canvas遮挡问题 #盖住其他元素 #苹果ios手机 #安卓手机 #调整画布层级 #测速 #iperf #iperf3 #stl #IIS Crypto #麦克风权限 #访问麦克风并录制音频 #麦克风录制音频后在线播放 #用户拒绝访问麦克风权限怎么办 #uniapp 安卓 苹果ios #将音频保存本地或上传服务器 #express #cherry studio # child_process #分子动力学 #化工仿真 #Exchange #决策树 #铁路桥梁 #DIC技术 #箱梁试验 #裂纹监测 #四点弯曲 #scikit-learn #运动 #仙盟创梦IDE #GLM-4.6V-Flash-WEB # AI视觉 # 本地部署 #基础语法 #标识符 #常量与变量 #数据类型 #运算符与表达式 #程序定制 #毕设代做 #课设 #Linly-Talker # 数字人 # 服务器稳定性 #外卖配送 #百度文库 #爱企查 #旋转验证码 #验证码识别 #转行 #开关电源 #热敏电阻 #PTC热敏电阻 #AI Agent #开发者工具 #语义检索 #向量嵌入 #实在Agent #边缘AI # Kontron # SMARC-sAMX8 #gnu #小艺 #搜索 #glances #SQL注入主机 #Coturn #电子电气架构 #系统工程与系统架构的内涵 #Routine #人脸活体检测 #live-pusher #动作引导 #张嘴眨眼摇头 #苹果ios安卓完美兼容 #格式工厂 #Beidou #北斗 #SSR #阿里云RDS #信息安全 #信息收集 # AI部署 #VMware创建虚拟机 #远程更新 #缓存更新 #多指令适配 #物料关联计划 #防毒面罩 #防尘面罩 #Qwen3-VL # 服务状态监控 # 视觉语言模型 #m3u8 #HLS #移动端H5网页 #APP安卓苹果ios #监控画面 直播视频流 #隐函数 #常微分方程 #偏微分方程 #线性微分方程 #线性方程组 #非线性方程组 #复变函数 #新浪微博 #传媒 #身体实验室 #健康认知重构 #系统思维 #微行动 #NEAT效应 #亚健康自救 #ICT人 #UDP服务器 #recvfrom函数 # 环境迁移 #思爱普 #SAP S/4HANA #ABAP #NetWeaver #xshell #host key #日志模块 #dash #投标 #标书制作 #实时检测 #bytebase #SQL调优 #EXPLAIN #慢查询日志 #分布式架构 #西门子 #汇川 #Blazor #spring ai #oauth2 #rtmp #夏天云 #夏天云数据 #华为od机试 #华为od机考 #华为od最新上机考试题库 #华为OD题库 #华为OD机试双机位C卷 #od机考题库 # 局域网访问 # 批量处理 #江协 #瑞萨 #OLED屏幕移植 #运维 #MinIO # 高温监控 #AI工具集成 #容器化部署 #css3 #rsync # 数据同步 #一周会议与活动 #ICLR #CCF #claudeCode #content7 # 串口服务器 # NPort5630 #基金 #股票 #YOLO识别 #YOLO环境搭建Windows #YOLO环境搭建Ubuntu #bigtop #hdp #hue #kerberos #Python办公自动化 #Python办公 #科普 #docker安装seata #ossinsight #超算中心 #PBS #lsf # ms-swift #PN 结 #adobe #数据迁移 #fork函数 #进程创建 #进程终止 #moltbot #gmssh #宝塔 #小智 #系统安装 #期刊 #SCI #session #okhttp #计算机外设 #boltbot #remote-ssh #健康医疗 #Taiji #OpenAI #故障 #tekton #二值化 #Canny边缘检测 #轮廓检测 #透视变换 #DuckDB #协议 #Arduino BLDC #核辐射区域探测机器人 #2025年 #AI教程 #自动化巡检 #istio #服务发现 #jquery #JADX-AI 插件 #starrocks