• 服务器并发实现的五种方法

服务器并发实现的五种方法

2025-05-24 13:57:10 2 阅读

文章目录

  • 前言
  • 一、单线程 / 进程
  • 二、多进程并发
  • 三、多线程并发
  • 四、IO多路转接(复用)select
  • 五、IO多路转接(复用)poll
  • 六、IO多路转接(复用)epoll

前言

关于网络编程相关知识可看我之前写过的文章:

  • TCP网络通信和网络编程应用示例

一、单线程 / 进程

在TCP通信过程中,服务器端启动之后可以同时和多个客户端建立连接,并进行网络通信,在代码中经常会用到三个引起程序阻塞的函数,分别是:

  • accept():如果服务器端没有新客户端连接,阻塞当前进程/线程,如果检测到新连接解除阻塞,建立连接
  • read():如果通信的套接字对应的读缓冲区没有数据,阻塞当前进程/线程,检测到数据解除阻塞,接收数据
  • write():如果通信的套接字写缓冲区被写满了,阻塞当前进程/线程(这种情况比较少见)

如果需要和发起新的连接请求的客户端建立连接,那么就必须在服务器端通过一个循环调用 accept() 函数,另外已经和服务器建立连接的客户端需要和服务器通信,发送数据时的阻塞可以忽略,当接收不到数据时程序也会被阻塞,这时候就会非常矛盾,被accept()阻塞就无法通信,被 read() 阻塞就无法和客户端建立新连接。因此得出一个结论,基于上述处理方式,在单线程/单进程场景下,服务器是无法处理多连接的,解决方案也有很多,常用的有三种:

  • 使用多线程实现
  • 使用多进程实现
  • 使用IO多路转接(复用)实现
  • 使用IO多路转接 + 多线程实现

二、多进程并发

如果要编写多进程版的并发服务器程序,首先要考虑,创建出的多个进程都是什么角色,这样就可以在程序中对号入座了。在Tcp服务器端一共有两个角色,分别是:监听和通信,监听是一个持续的动作,如果有新连接就建立连接,如果没有新连接就阻塞。关于通信是需要和多个客户端同时进行的,因此需要多个进程,这样才能达到互不影响的效果。进程也有两大类:父进程和子进程,通过分析我们可以这样分配进程:

  • 父进程:

    • 负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
    • 创建子进程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
    • 回收子进程资源:子进程退出回收其内核PCB资源,防止出现僵尸进程
  • 子进程:负责通信,基于父进程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。

    • 发送数据:send() / write()
    • 接收数据:recv() / read()

在多进程版的服务器端程序中,多个进程是有血缘关系,对应有血缘关系的进程来说,还需要想明白他们有哪些资源是可以被继承的,哪些资源是独占的,以及一些其他细节:

  • 子进程是父进程的拷贝,在子进程的内核区PCB中,文件描述符也是可以被拷贝的,因此在父进程可以使用的文件描述符在子进程中也有一份,并且可以使用它们做和父进程一样的事情。
  • 父子进程有用各自的独立的虚拟地址空间,因此所有的资源都是独占的
  • 为了节省系统资源,对于只有在父进程才能用到的资源,可以在子进程中将其释放掉,父进程亦如此。
  • 由于需要在父进程中做accept()操作,并且要释放子进程资源,如果想要更高效一下可以使用信号的方式处理

服务器代码:

#include 			/* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define SERVER_PORT 8888
#define BACKLOG     10

// 信号处理函数
void callback(int num)
{
    while(1)
    {
        pid_t pid = waitpid(-1, NULL, WNOHANG);
        if(pid <= 0)
        {
            printf("子进程正在运行, 或者子进程被回收完毕了
");
            break;
        }
        printf("child die, pid = %d
", pid);
    }
}

int childWork(int cfd);


int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;

	int iRet;
	int iAddrlen;
	int iClientNum = 0;
	int cnt = 0;

	int iRcvLen;
	int iSendLen;
	unsigned char ucSendBuf[1000];
	unsigned char ucRcvBuf[1000];
	
	/* 1. socket */
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0); 
	
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port  	  = htons(SERVER_PORT);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	/* 2. bind   */
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr_in));

	/* 3. listen */
	iRet = listen(iSocketServer, BACKLOG);

	// 注册信号的捕捉
	// 用于父进程管理子进程的退出状态,避免僵尸进程
	struct sigaction act;
	act.sa_flags = 0;
	act.sa_handler = callback;
	sigemptyset(&act.sa_mask);
	sigaction(SIGCHLD, &act, NULL);
	

	while (1)
	{	
		/* 4. accept */
		iAddrlen = sizeof(struct sockaddr_in);
		iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrlen);

		if (iSocketClient == -1)
		{
			if (errno == EINTR)
			{
				continue;
			}
			perror("accept");
			exit(0);
		}

		iClientNum++;
		printf("get connect from client %d:%s
", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));

		/* 创建一个子进程 */
		pid_t pid = fork();
		if (pid == 0)
		{
	
			// 子进程 -> 和客户端通信
			// 通信的文件描述符cfd被拷贝到子进程中
			// 子进程不负责监听
			close(iSocketServer);
			while(1)
			{
				int ret = childWork(iSocketClient);
				if(ret <= 0)
				{
					break;
				}
			}
			// 退出子进程
			close(iSocketClient);
			exit(0);
		}	
		else if (pid > 0)
		{
            // 父进程不和客户端通信
            close(iSocketClient);
		}
	}
	
	return 0;
}


// 5. 和客户端通信
int childWork(int cfd)
{

    // 接收数据
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    int len = read(cfd, buf, sizeof(buf));
    if(len > 0)
    {
        printf("客户端say: %s
", buf);
        write(cfd, buf, len);
    }
    else if(len  == 0)
    {
        printf("客户端断开了连接...
");
    }
    else
    {
        perror("read");
    }

    return len;
}

客户端代码:

#include 			/* See NOTES */
#include 
#include 
#include 
#include 
#include 

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketClientAddr;

	int iRet;
	int iClientNum = 0;
	int iSendLen;
	int iRcvLen;
	
	unsigned char ucSendBuf[1000];
	unsigned char ucRcvBuf[1000];

	if (argc != 2)
	{
		printf("Usage:%s IP
", argv[0]);
		return -1;
	}
	
	/* 1. socket  */
	iSocketClient = socket(AF_INET, SOCK_STREAM, 0); 

	if (-1 == iSocketClient)
	{	
		printf("socket error!
");
		return -1;
	}
	
	tSocketClientAddr.sin_family = AF_INET;
	tSocketClientAddr.sin_port   = htons(SERVER_PORT);

	/* 2. inet_aton */
	iRet = inet_aton(argv[1], &tSocketClientAddr.sin_addr);
	if (0 == iRet)
	{
		printf("inet_aton error!
");
		return -1;
	}
	memset(tSocketClientAddr.sin_zero, 0, 8);
	
	/* 3. connect  */
	iRet = connect(iSocketClient, (struct sockaddr *)&tSocketClientAddr, sizeof(struct sockaddr_in));
	if (-1 == iRet)
	{	
		printf("connect error!
");
		return -1;
	}

	while (1)
	{	
		/* 用来读取终端输入的一行数据 */
		if (fgets(ucSendBuf, 999, stdin))
		{
			/* 发送该行数据给服务器 */
			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
			
			/* 接收服务器发过来的数据 */
			iRcvLen = recv(iSocketClient, ucRcvBuf, 999, 0); 
			if (iRcvLen > 0)
			{
				ucRcvBuf[iRcvLen] = '';
				printf("get msg from server:%s
", ucRcvBuf);
			}
		}
	}
	
	close(iSocketClient);

	return 0;
}

在上面的示例代码中,父子进程中分别关掉了用不到的文件描述符(父进程不需要通信,子进程也不需要监听)。如果客户端主动断开连接,那么服务器端负责和客户端通信的子进程也就退出了,子进程退出之后会给父进程发送一个叫做 SIGCHLD的信号,在父进程中通过sigaction()函数捕捉了该信号,通过回调函数callback()中的waitpid()对退出的子进程进行了资源回收。

另外还有一个细节要说明一下,这是父进程的处理代码:

int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
while(1)
{
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
        if(cfd == -1)
        {
            if(errno == EINTR)
            {
                // accept调用被信号中断了, 解除阻塞, 返回了-1
                // 重新调用一次accept
                continue;
            }
            perror("accept");
            exit(0);
 
        }
 }

如果父进程调用accept() 函数没有检测到新的客户端连接,父进程就阻塞在这儿了,这时候有子进程退出了,发送信号给父进程,父进程就捕捉到了这个信号SIGCHLD, 由于信号的优先级很高,会打断代码正常的执行流程,因此父进程的阻塞被中断,转而去处理这个信号对应的函数callback(),处理完毕,再次回到accept()位置,但是这是已经无法阻塞了,函数直接返回-1,此时函数调用失败,错误描述为accept: Interrupted system call,对应的错误号为EINTR,由于代码是被信号中断导致的错误,所以可以在程序中对这个错误号进行判断,让父进程重新调用accept(),继续阻塞或者接受客户端的新连接。

三、多线程并发

编写多线程版的并发服务器程序和多进程思路差不多,考虑明白了对号入座即可。多线程中的线程有两大类:主线程(父线程)和子线程,他们分别要在服务器端处理监听和通信流程。根据多进程的处理思路,就可以这样设计了:

  • 主线程:

    • 负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
    • 创建子线程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
    • 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。
  • 子线程:负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。

    • 发送数据:send() / write()
    • 接收数据:recv() / read()

在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的,下面来分析一些其中的一些细节:

  • 同一地址空间中的多个线程的栈空间是独占的
  • 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。

服务器代码:

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


#define SERVER_PORT 8888
#define BACKLOG     10

struct SockInfo
{
    int fd;                      // 通信
    pthread_t tid;               // 线程ID
    struct sockaddr_in addr;     // 地址信息
};

struct SockInfo infos[128];


void* working(void* arg)
{
    while(1)
    {
        struct SockInfo* info = (struct SockInfo*)arg;
        // 接收数据
        char buf[1024];
        int ret = read(info->fd, buf, sizeof(buf));
        if(ret == 0)
        {
            printf("客户端已经关闭连接...
");
            info->fd = -1;
            break;
        }
        else if(ret == -1)
        {
            printf("接收数据失败...
");
            info->fd = -1;
            break;
        }
        else
        {        
        	printf("客户端say: %s
", buf);
            write(info->fd, buf, strlen(buf) + 1);
        }
    }
    return NULL;
}


int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;

	int iRet;
	
	/* 1. socket */
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0); 
	
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port  	  = htons(SERVER_PORT);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	/* 2. bind   */
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr_in));

	/* 3. listen */
	iRet = listen(iSocketServer, BACKLOG);

	int len = sizeof(struct sockaddr);
	
	// 数据初始化
    int max = sizeof(infos) / sizeof(infos[0]);
    for(int i = 0; i < max; i++)
    {
        memset(&infos[i], 0, sizeof(infos[i]));
        infos[i].fd = -1;
        infos[i].tid = -1;
    }


	while (1)
	{	
		// 创建子线程
		struct SockInfo* pinfo;
		for(int i = 0; i < max; i++)
		{
			if(infos[i].fd == -1)
			{
				pinfo = &infos[i];
				break;
			}
			if(i == max-1)
			{
				sleep(1);
				i--;
			}
		}
		
		/* 4. accept */
		int iSocketClient = accept(iSocketServer, (struct sockaddr*)&pinfo->addr, &len);
		printf("parent thread, iSocketClient: %d
", iSocketClient);
		
		if(iSocketClient == -1)
		{
			perror("accept");
			exit(0);
		}
		pinfo->fd = iSocketClient;
		pthread_create(&pinfo->tid, NULL, working, pinfo);
		pthread_detach(pinfo->tid);
	}

	close(iSocketServer);
	
	return 0;
}

客户端代码:

#include 			/* See NOTES */
#include 
#include 
#include 
#include 
#include 

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketClientAddr;

	int iRet;
	int iClientNum = 0;
	int iSendLen;
	int iRcvLen;
	
	unsigned char ucSendBuf[1000];
	unsigned char ucRcvBuf[1000];

	if (argc != 2)
	{
		printf("Usage:%s IP
", argv[0]);
		return -1;
	}
	
	/* 1. socket  */
	iSocketClient = socket(AF_INET, SOCK_STREAM, 0); 

	if (-1 == iSocketClient)
	{	
		printf("socket error!
");
		return -1;
	}
	
	tSocketClientAddr.sin_family = AF_INET;
	tSocketClientAddr.sin_port   = htons(SERVER_PORT);

	/* 2. inet_aton */
	iRet = inet_aton(argv[1], &tSocketClientAddr.sin_addr);
	if (0 == iRet)
	{
		printf("inet_aton error!
");
		return -1;
	}
	memset(tSocketClientAddr.sin_zero, 0, 8);
	
	/* 3. connect  */
	iRet = connect(iSocketClient, (struct sockaddr *)&tSocketClientAddr, sizeof(struct sockaddr_in));
	if (-1 == iRet)
	{	
		printf("connect error!
");
		return -1;
	}

	while (1)
	{	
		/* 用来读取终端输入的一行数据 */
		if (fgets(ucSendBuf, 999, stdin))
		{
			/* 发送该行数据给服务器 */
			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
			
			/* 接收服务器发过来的数据 */
			iRcvLen = recv(iSocketClient, ucRcvBuf, 999, 0); 
			if (iRcvLen > 0)
			{
				ucRcvBuf[iRcvLen] = '';
				printf("get msg from server:%s
", ucRcvBuf);
			}
		}
	}
	
	close(iSocketClient);

	return 0;
}

编译运行结果:

四、IO多路转接(复用)select

服务器代码:

#include 
#include 
#include 
#include 
#include 

#define SERVER_PORT 8888
#define BACKLOG     10


int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;

	int iRet;

	/* 1. socket */
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0); 
	
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port  	  = htons(SERVER_PORT);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	/* 2. bind   */
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr_in));

	/* 3. listen */
	iRet = listen(iSocketServer, BACKLOG);
	

	// 将监听的fd的状态检测委托给内核检测
	int maxfd = iSocketServer;
	// 初始化检测的读集合
	fd_set rdset;
	fd_set rdtemp;
	// 清零
	FD_ZERO(&rdset);
	// 将监听的iSocketServer设置到检测的读集合中
	FD_SET(iSocketServer, &rdset);
	// 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据
	// 如果有数据, select解除阻塞返回
	// 应该让内核持续检测

	while (1)
	{	
		// 默认阻塞
		// rdset 中是委托内核检测的所有的文件描述符
		rdtemp = rdset;
		int num = select(maxfd + 1, &rdtemp, NULL, NULL, NULL);
		// rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0
		// 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了
		// 判断
		// 有没有新连接
		if(FD_ISSET(iSocketServer, &rdtemp))
		{
			// 接受连接请求, 这个调用不阻塞
			int cliLen = sizeof(tSocketClientAddr);
			int iSocketClient = accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, &cliLen);

			// 得到了有效的文件描述符
			// 通信的文件描述符添加到读集合
			// 在下一轮select检测的时候, 就能得到缓冲区的状态
			FD_SET(iSocketClient, &rdset);
			// 重置最大的文件描述符
			maxfd = iSocketClient > maxfd ? iSocketClient : maxfd;
		}

		// 没有新连接, 通信
		for(int i = 0; i < maxfd + 1; i++)
		{
			// 排除监听服务器套接字,仅处理客户端套接字
			if(i != iSocketServer && FD_ISSET(i, &rdtemp))
			{
				// 接收数据
				char buf[10] = {0};
				// 一次只能接收10个字节, 客户端一次发送100个字节
				// 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
				// 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
				//	循环会一直持续, 知道缓冲区数据被读完位置
				int len = read(i, buf, sizeof(buf));
				if(len == 0)
				{
					printf("客户端关闭了连接...
");
					// 将检测的文件描述符从读集合中删除
					FD_CLR(i, &rdset);
					close(i);
				}
				else if(len > 0)
				{
					// 收到了数据
		        	printf("客户端say: %s
", buf);
					// 发送数据
					write(i, buf, strlen(buf)+1);
				}
				else
				{
					// 异常
					perror("read");
				}
			}
		}
	}

	close(iSocketServer);
	
	return 0;
}

客户端代码与之前一样

五、IO多路转接(复用)poll

poll的机制与select类似,与select在本质上没有多大差别,使用方法也类似:

  • 内核对应文件描述符的检测也是以线性的方式进行轮询,根据描述符的状态进行处理;
  • poll和select检测的文件描述符集合会在检测过程中频繁的进行用户区和内核区的拷贝,它的开销随着文件描述符数量的增加而线性增大,从而效率也会越来越低;

不同点:

  • select检测的文件描述符个数上限是1024,poll没有最大文件描述符数量的限制;
  • select可以跨平台使用,poll只能在Linux平台使用;

poll函数的函数原型如下:

#include 
// 每个委托poll检测的fd都对应这样一个结构体
struct pollfd {
    int   fd;         /* 委托内核检测的文件描述符 */
    short events;     /* 委托内核检测文件描述符的什么事件 */
    short revents;    /* 文件描述符实际发生的事件 -> 传出 */
};

struct pollfd myfd[100];
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

函数参数:

  • fds: 这是一个 struct pollfd类型的数组, 里边存储了待检测的文件描述符的信息,这个数组中有三个成员:

    • fd:委托内核检测的文件描述符
    • events:委托内核检测的fd事件(输入、输出、错误),每一个事件有多个取值
    • revents:这是一个传出参数,数据由内核写入,存储内核检测之后的结果
  • nfds: 这是第一个参数数组中最后一个有效元素的下标 + 1(也可以指定参数1数组的元素总个数)

  • timeout: 指定poll函数的阻塞时长

    • -1:一直阻塞,直到检测的集合中有就绪的文件描述符(有事件产生)解除阻塞
    • 0:不阻塞,不管检测集合中有没有已就绪的文件描述符,函数马上返回
    • 大于0:阻塞指定的毫秒(ms)数之后,解除阻塞
  • 函数返回值:

    • 失败: 返回-1
    • 成功:返回一个大于0的整数,表示检测的集合中已就绪的文件描述符的总个数

服务器代码:

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

#define SERVER_PORT 8080
#define BACKLOG     10

int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;


	int iRet;
	
	/* 1. socket */
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0); 
	
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port  	  = htons(SERVER_PORT);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	/* 2. bind   */
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr_in));

	/* 3. listen */
	iRet = listen(iSocketServer, BACKLOG);

	// 检测 -> 读缓冲区, 委托内核去处理
	// 数据初始化, 创建自定义的文件描述符集
	struct pollfd fds[1024];
	// 初始化
	for(int i = 0; i < 1024; i++)
	{
		fds[i].fd = -1;
		fds[i].events = POLLIN;
	}
	fds[0].fd = iSocketServer;

	int maxfd = 0;
	
	while (1)
	{	
		// 委托内核检测
		iRet = poll(fds, maxfd + 1, -1);
		if(iRet == -1)
		{
			perror("poll");
			exit(0);
		}

		// 内核检测之后的结果为真
		if(fds[0].revents & POLLIN)
		{
			// 接收连接请求
			int len = sizeof(tSocketClientAddr);
			// 这个accept是不会阻塞的
			int iSocketClient = accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, &len);
			// 委托内核检测iSocketClient的读缓冲区
			int i;
			for(i = 0; i < 1024; i++)
			{
				if(fds[i].fd == -1)
				{
					fds[i].fd = iSocketClient;
					break;
				}
			}
			maxfd = i > maxfd ? i : maxfd;
		}
		
		// 通信, 有客户端发送数据过来
		for(int i = 1; i <= maxfd; i++)
		{
			// 如果在集合中, 说明读缓冲区有数据
			if(fds[i].revents & POLLIN)
			{
				char buf[128];
				int ret = read(fds[i].fd, buf, sizeof(buf));
				if(ret == -1)
				{
					perror("read");
					exit(0);
				}
				else if(ret == 0)
				{
					printf("对方已经关闭了连接...
");
					close(fds[i].fd);
					fds[i].fd = -1;
				}
				else
				{
					printf("客户端say: %s
", buf);
					write(fds[i].fd, buf, strlen(buf)+1);
				}
			}
		}

	}

	close(iSocketServer);
	
	return 0;
}

客户端代码与之前一样

六、IO多路转接(复用)epoll

epoll 全称 eventpoll,是 linux 内核实现IO多路转接/复用(IO multiplexing)的一个实现。IO多路转接的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。epoll是select和poll的升级版,相较于这两个前辈,epoll改进了工作方式,因此它更加高效。

  • 对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的。
  • select和poll每次都会线性扫描整个待检测集合,集合越大速度越慢,epoll使用的是回调机制,效率高,处理效率也不会随着检测集合的变大而下降
  • select和poll工作过程中存在内核/用户空间数据的频繁拷贝问题,在epoll中内核和用户区使用的是共享内存(基于mmap内存映射区实现),省去了不必要的内存拷贝。
  • 程序猿需要对select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,通过epoll可以直接得到已就绪的文件描述符集合,无需再次检测
  • 使用epoll没有最大文件描述符的限制,仅受系统中进程能打开的最大文件数目限制

当多路复用的文件数量庞大、IO流量频繁的时候,一般不太适合使用select()poll(),这种情况下select()poll()表现较差,推荐使用epoll()

在epoll中一共提供是三个API函数,分别处理不同的操作,函数原型如下:

#include 
// 创建epoll实例,通过一棵红黑树管理待检测集合
int epoll_create(int size);
// 管理红黑树上的文件描述符(添加、修改、删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

服务器代码:

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


#define SERVER_PORT 8888
#define BACKLOG     10

int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;

	int iRet;
	int iAddrlen;
	int iClientNum = 0;
	int cnt = 0;

	int iRcvLen;
	int iSendLen;
	unsigned char ucSendBuf[1000];
	unsigned char ucRcvBuf[1000];
	
	/* 1. socket */
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0); 
	
	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port  	  = htons(SERVER_PORT);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	// 设置端口复用
	int opt = 1;
	setsockopt(iSocketServer, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	if(iRet == -1)
	{
		perror("bind error");
		exit(1);
	}


	/* 2. bind   */
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr_in));
	if(iRet == -1)
	{
		perror("listen error");
		exit(1);
	}
	


	/* 3. listen */
	iRet = listen(iSocketServer, BACKLOG);

	// 现在只有监听的文件描述符
	// 所有的文件描述符对应读写缓冲区状态都是委托内核进行检测的epoll
	// 创建一个epoll模型
	int epfd = epoll_create(100);
	if(epfd == -1)
	{
		perror("epoll_create");
		exit(0);
	}

	// 往epoll实例中添加需要检测的节点, 现在只有监听的文件描述符
	struct epoll_event ev;
	ev.events = EPOLLIN;	// 检测 iSocketServer 的读缓冲区是否有数据
	ev.data.fd = iSocketServer;
	iRet = epoll_ctl(epfd, EPOLL_CTL_ADD, iSocketServer, &ev);
	if(iRet == -1)
	{
		perror("epoll_ctl");
		exit(0);
	}

	struct epoll_event evs[1024];
	int size = sizeof(evs) / sizeof(struct epoll_event);
	
	// 持续检测
	while (1)
	{	
		// 调用一次, 检测一次
		int num = epoll_wait(epfd, evs, size, -1);
		for(int i = 0; i < num; i++)
		{
			// 取出当前的文件描述符
			int curfd = evs[i].data.fd;
			// 判断这个文件描述符是不是用于监听的
			if(curfd == iSocketServer)
			{
				/* 4. accept */
				iSocketClient = accept(curfd, NULL, NULL);
				// 新得到的文件描述符添加到epoll模型中, 下一轮循环的时候就可以被检测了
				ev.events = EPOLLIN;	// 读缓冲区是否有数据
				ev.data.fd = iSocketClient;
				iRet = epoll_ctl(epfd, EPOLL_CTL_ADD, iSocketClient, &ev);
				if(iRet == -1)
				{
					perror("epoll_ctl-accept");
					exit(0);
				}
			}
			else
			{
				// 处理通信的文件描述符
				// 接收数据
				char buf[1024];
				memset(buf, 0, sizeof(buf));
				int len = recv(curfd, buf, sizeof(buf), 0);
				if(len == 0)
				{
					printf("客户端已经断开了连接
");
					// 将这个文件描述符从epoll模型中删除
					epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
					close(curfd);
				}
				else if(len > 0)
				{
					printf("客户端say: %s
", buf);
					send(curfd, buf, len, 0);
				}
				else
				{
					perror("recv");
					exit(0);
				} 
			}
		}
	}
	
	return 0;
}

客户端代码与之前一样

当在服务器端循环调用epoll_wait()的时候,会得到一个就绪列表,并通过该函数的第二个参数传出:

struct epoll_event evs[1024];
int num = epoll_wait(epfd, evs, size, -1);

每当 epoll_wait() 函数返回一次,在 evs 中最多可以存储 size 个已就绪的文件描述符信息,但是在这个数组中实际存储的有效元素个数为num个,如果在这个 epoll 实例的红黑树中已就绪的文件描述符很多,并且 evs 数组无法将这些信息全部传出,那么这些信息会在下一次epoll_wait() 函数返回的时候被传出。

通过 evs数组被传递出的每一个有效元素里边都包含了已就绪的文件描述符的相关信息,这些信息并不是凭空得来的,这取决于我们在往epoll 实例中添加节点的时候,往节点中初始化了哪些数据:

struct epoll_event ev;
ev.events = EPOLLIN;	// 检测 iSocketServer 的读缓冲区是否有数据
ev.data.fd = iSocketServer;
iRet = epoll_ctl(epfd, EPOLL_CTL_ADD, iSocketServer, &ev);

本文地址:https://www.vps345.com/12061.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 docker 容器 运维 java-rabbitmq java 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP ubuntu 无人机 机器人 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 人工智能 自动化 centos python linux 机器学习 服务器 深度学习 macos MacMini Mac 迷你主机 mini Apple javascript 前端 chrome edge ai nlp Deepseek Deepseek-R1 大模型 私有化部署 推理模型 数据库 oracle 关系型 安全 分布式 ddos android 网络安全 web安全 harmonyos 华为 网络工程师 网络管理 软考 2024 2024年上半年 下午真题 答案 游戏 云计算 ssh jvm 虚拟机 windows 计算机外设 bug redis FTP服务器 deepseek DeepSeek-R1 API接口 https c++ ssh漏洞 ssh9.9p2 CVE-2025-23419 程序 编程 内存 性能分析 AIGC 人工智能生成内容 Dify CH340 单片机 嵌入式硬件 串口驱动 CH341 uart 485 DNS 经验分享 网络 YOLO 目标检测 计算机视觉 stm32 开发语言 udp 语言模型 http vue3 excel 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 github git ssl django 华为云 华为od arm开发 架构 conda gnu vscode 学习 物联网 nginx 负载均衡 神经网络 卷积神经网络 ansible pytorch yolov5 镜像源 ROS 自动驾驶 远程桌面 tcp/ip 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 Ollama 模型联网 API CherryStudio kubernetes 学习方法 程序人生 数据库系统 云原生 iventoy VmWare OpenEuler flutter Google pay Apple pay llama deepseek-r1 大模型本地部署 vue.js spring boot 自然语言处理 进程 操作系统 进程控制 Ubuntu 笔记 C 环境变量 进程地址空间 Qwen2.5-coder 离线部署 opencv Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 c语言 pycharm ide mysql 远程工作 数据分析 音视频 php 网络协议 ip协议 dify 知识库 本地化部署 tomcat ui 游戏引擎 k8s dash json 正则表达式 springsecurity6 oauth2 授权服务器 前后端分离 jdk 后端 智能体开发 AI 嵌入式 unix RTSP xop RTP RTSPServer 推流 视频 gitea npm rsyslog golang go file server http server web server mcu kafka word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 oracle fusion oracle中间件 电脑 NVML nvidia-smi Cursor USB转串口 linux环境变量 adb UEFI Legacy MBR GPT U盘安装操作系统 本地环回 bind notepad 命令 AI编程 svn Linux 维护模式 ArkUI 鸿蒙系统 ArkTS WSL2 IP 地址 权限 node.js 策略模式 iftop 网络流量监控 gcc centos 7 prometheus grafana 7z react.js 前端面试题 持续部署 opengl devops qt arm vite 部署 回显服务器 UDP的API使用 audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 Flask FastAPI Waitress Gunicorn uWSGI Uvicorn 实时音视频 RAID RAID技术 磁盘 存储 rust腐蚀 IIS服务器 IIS性能 日志监控 kylin microsoft mcp mysql离线安装 ubuntu22.04 mysql8.0 typescript 计算机网络 ollama 私有化 本地部署 wsl 鸿蒙 网络结构图 算法 面试 vim 并查集 leetcode 英语 ubuntu20.04 Linux 开机黑屏 开发环境 jellyfin nas python2 ubuntu24.04 maxkb ARG 智能路由器 大数据 移动端开发 缓存 arkUI arkTs 1024程序员节 MobaXterm 远程连接 文件传输 elasticsearch 快捷键 旋转屏幕 自动操作 Ubuntu20.04 GLIBC 2.35 zip unzip selenium bash apache spring cloud perf pygame 指令 ffmpeg 视频编解码 pip vnc Python eureka 多线程 Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 Alexnet fstab compose centos-root /dev/mapper yum clean all df -h / du -sh JDK Java LInux Windows nvm 信息与通信 压力测试 大模型压力测试 EvalScope ACL 流量控制 基本ACL 规则配置 agi mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 dell服务器 gpu算力 ipython rpc YOLOv8 NPU Atlas800 A300I pro asi_bench filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 反向代理 adobe cursor 系统安全 chatgpt asm 串口服务器 jupyter uni-app 职场和发展 镜像下载 debian freebsd outlook 错误代码2603 无网络连接 2603 es6 qt6.3 g726 MCP 性能优化 交互 mac c# 多层架构 解耦 灵办AI Redis Desktop DeepSeek hadoop 高级IO epoll visual studio code vmware 卡死 list 数据结构 matplotlib fonts-noto-cjk 编辑器 intellij-idea 虚拟局域网 linux驱动开发 diskgenius 图像处理 nohup 异步执行 cuda visualstudio firewall 腾讯云大模型知识引擎 腾讯云 gitee virtualenv webdav 网络药理学 生信 生物信息学 gromacs 分子动力学模拟 MD 动力学模拟 openssl eclipse ubuntu 18.04 安装教程 虚拟现实 spring AI大模型 大模型技术 本地部署大模型 axure 富文本编辑器 ros ros1 Noetic 20.04 apt 安装 cron crontab日志 ip 阿里云 开源 milvus CPU 使用率 系统监控工具 linux 命令 qps 高并发 VMware k8s部署 MySQL8.0 高可用集群(1主2从) rdp 远程服务 gitlab unity rocketmq SRS 流媒体 直播 C语言 交换机 硬件 设备 GPU PCI-Express 科技 个人开发 雨云 NPS AutoDL react next.js 部署next.js 服务器配置 flask xrdp 银河麒麟 环境迁移 IMM fastapi web3.py 外网访问 内网穿透 端口映射 银河麒麟操作系统 国产化 html5 firefox grub 版本升级 扩容 websocket 媒体 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 vu大文件秒传跨域报错cors 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 rustdesk jmeter spark hive asp.net大文件上传 asp.net大文件上传下载 asp.net大文件上传源码 ASP.NET断点续传 华为OD 华为OD机试真题 可以组成网络的服务器 css css3 big data 云计算面试题 HarmonyOS Next 系统 黑苹果 软件需求 ios Linux PID 虚拟显示器 远程控制 jenkins iperf3 带宽测试 进程优先级 调度队列 进程切换 openvpn server openvpn配置教程 centos安装openvpn 运维开发 .net xcode QT 5.12.12 QT开发环境 Ubuntu18.04 shell脚本免交互 expect linux免交互 隐藏文件 程序员创富 kotlin android studio iphone VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 智能手机 驱动开发 教程 环境搭建 Maven mongodb burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 export env 变量 链表 chromium dpi pdf python3.11 冯诺依曼体系 测试工具 Ubuntu 22.04 MySql 算家云 算力租赁 Python教程 pycharm安装 maven Maxkb RAG技术 本地知识库 离线部署dify JAVA 课程设计 AI-native Docker Desktop onlyoffice 大模型应用 实时互动 glibc ue4 着色器 ue5 虚幻 监控k8s集群 集群内prometheus 目标跟踪 OpenVINO 推理应用 微信小程序 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 读写锁 llm transformer postgresql 框架搭建 flash-attention 报错 国产数据库 瀚高数据库 数据迁移 下载安装 tcp 计算生物学 生物信息 基因组 html DevOps 软件交付 数据驱动 应用场景 数据安全 工具分享 微信 微服务 网易邮箱大师 一切皆文件 防火墙 端口号 开放端口 访问列表 命名管道 客户端与服务端通信 RDP n8n 工作流 华为认证 mcp-proxy mcp-inspector fastapi-mcp agent sse FTP 服务器 升级 CVE-2024-7347 漏洞 webrtc 大模型微调 阻塞队列 生产者消费者模型 服务器崩坏原因 自定义客户端 SAS 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 ping++ oneapi live555 rtsp rtp 数据挖掘 ESP32 NAS Termux Samba 大语言模型 LLMs PVE EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 爬虫 WebRTC gpt 硬件工程 express Netty 即时通信 NIO SSH Unity Dedicated Server Host Client 无头主机 sysctl.conf vm.nr_hugepages 测试用例 功能测试 rust rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 IM即时通讯 QQ 企业微信 剪切板对通 HTML FORMAT 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 TCP WebServer windows 服务器安装 samba 浪潮信息 AI服务器 jar lighttpd安装 Ubuntu配置 Windows安装 性能测试 服务器优化 Chatbox postman HP Anyware Qualcomm WoS QNN AppBuilder 虚拟化 信号处理 tcpdump ftp服务 文件上传 p2p 昇腾 大模型训练/推理 推理问题 mindie 区块链 智能合约 哈希算法 统信 NFS chatbox Agent 前端框架 文件系统 路径解析 宠物 毕业设计 免费学习 宠物领养 宠物平台 webgl VMware安装Ubuntu Ubuntu安装k8s MacOS录屏软件 安卓 微信分享 Image wxopensdk cnn GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 VMware安装mocOS macOS系统安装 DevEco Studio java-ee rag ragflow 大模型部署 游戏程序 产品经理 protobuf 序列化和反序列化 安装 shell Mac内存不够用怎么办 软件工程 软件构建 信息可视化 echarts PX4 MAVROS 四旋翼无人机 yum docker-compose docker compose sublime text linux内核 智慧农业 开源鸿蒙 团队开发 安卓模拟器 mysql安装报错 windows拒绝安装 桌面环境 open webui iot nohup后台启动 HarmonyOS Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 frp sql 终端工具 远程工具 Ardupilot 系统架构 sublime text3 IP配置 netplan ollama api ollama外网访问 grep 进程信号 GPU训练 termux openjdk Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 创业创新 webpack photoshop vm js 图论 流程图 mermaid 深度求索 私域 docker run 数据卷挂载 交互模式 SSH 密钥生成 SSH 公钥 私钥 生成 C++ efficientVIT YOLOv8替换主干网络 TOLOv8 自动化运维 流水线 脚本式流水线 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 springboot etcd RBAC 实战案例 numpy 聚类 网络爬虫 ip命令 新增网卡 新增IP 启动网卡 设置代理 实用教程 集成学习 集成测试 powerpoint dubbo 思科 rabbitmq 单例模式 Ubuntu22.04 开发人员主页 burp suite 抓包 电子信息 通信工程 毕业 远程过程调用 Windows环境 宝塔面板无法访问 Linux Vim ufw 硬件架构 计算机科学与技术 remote-ssh vue ci/cd 进程程序替换 execl函数 execv函数 execvp函数 execvpe函数 putenv函数 显卡驱动持久化 GPU持久化 docker desktop 镜像 Claude Desktop Claude MCP Windows Cli MCP gru ranger MySQL8.0 京东云 DrissionPage matlab RAGflow dns 多线程服务器 Linux网络编程 stm32项目 MS Materials zotero WebDAV 同步失败 代理模式 pillow RustDesk自建服务器 rustdesk服务器 docker rustdesk 常用命令 文本命令 目录命令 aws IIS .net core Hosting Bundle .NET Framework vs2022 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 LDAP VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 MQTT 消息队列 cpu 实时 使用 odoo 服务器动作 Server action 知识图谱 LLM 大模型教程 opcua opcda KEPServer安装 ssrf 失效的访问控制 孤岛惊魂4 安全威胁分析 国产操作系统 虚拟机安装 宝塔面板 同步 备份 建站 监控 Hyper-V WinRM TrustedHosts vSphere vCenter 软件定义数据中心 sddc windows日志 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 Kali Linux 黑客 渗透测试 信息收集 磁盘监控 直播推流 大文件秒传跨域报错cors opensearch helm r语言 数据可视化 KingBase 项目部署到linux服务器 项目部署过程 Dell R750XS 群晖 文件分享 java-rocketmq 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 创意 社区 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 VMware创建虚拟机 .netcore 矩阵 GCC aarch64 编译安装 HPC db KylinV10 麒麟操作系统 Vmware C# MQTTS 双向认证 emqx 云桌面 微软 AD域控 证书服务器 互信 话题通信 服务通信 硅基流动 ChatBox ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口服务器 锁屏不生效 高效I/O 安全漏洞 信息安全 行情服务器 股票交易 速度慢 切换 股票量化接口 股票API接口 券商 股票交易接口api 类型 特点 zabbix mariadb ecmascript 支持向量机 localhost 教育电商 ECS服务器 Reactor 设计模式 医疗APP开发 app开发 僵尸进程 AI写作 远程 执行 sshpass 操作 linux安装配置 harmonyOS面试题 alias unalias 别名 chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 换源 国内源 Debian 中兴光猫 换光猫 网络桥接 自己换光猫 kali 共享文件夹 在线office bcompare Beyond Compare fpga开发 Hive环境搭建 hive3环境 Hive远程模式 c keepalived threejs 3D Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 嵌入式系统开发 conda配置 conda镜像源 safari CentOS Stream CentOS ESXi Ark-TS语言 重启 排查 系统重启 日志 原因 模拟实现 Ubuntu共享文件夹 共享目录 Linux共享文件夹 键盘 xml kernel webstorm c/c++ 串口 okhttp 匿名管道 top Linux top top命令详解 top命令重点 top常用参数 ArkTs uv 沙盒 WSL2 上安装 Ubuntu 软链接 硬链接 安装MySQL 磁盘清理 Reactor反应堆 IO curl wget 信号 内核 华为证书 HarmonyOS认证 华为证书考试 tar 工具 多产物 大屏端 elk lvm 磁盘挂载 磁盘分区 其他 iNode Macos langchain deep learning iTerm2 wpf 用户管理 yolov8 vmamba wps 高德地图 鸿蒙接入高德地图 HarmonyOS5.0 Office 空间 查错 搜索引擎 打不开xxx软件 无法检查其是否包含恶意软件 macbook Linux find grep SecureCRT Bug解决 Qt platform OpenCV mac设置host node 向量数据库 安装部署 milvus安装 CUPS 打印机 Qt5 图文教程 VMware虚拟机 macOS系统安装教程 macOS最新版 虚拟机安装macOS Sequoia systemctl composer tensorflow 管道 Github加速 Mac上Github加速 Chrome浏览器插件 需求分析 提示词 密码学 切换root 蓝耘科技 元生代平台工作流 ComfyUI 数据集 YOLOv12 Docker Compose fd 文件描述符 eNSP 网络规划 VLAN 企业网络 热榜 开发 环境配置 linux上传下载 中间件 可信计算技术 安全架构 网络攻击模型 5G 3GPP 卫星通信 基础环境 openstack Xen KVM minicom 串口调试工具 政务 分布式系统 监控运维 Prometheus Grafana Linux的权限 vr CrewAI ros2 moveit 机器人运动 低代码 springcloud log4j WebUI DeepSeek V3 openwrt 监控k8s 监控kubernetes WLAN selete rancher Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 redhat database nftables Mermaid 可视化图表 自动化生成 3d 数学建模 图形化界面 sdkman 免密 登录 公钥 私钥 trae linuxdeployqt 打包部署程序 appimagetool Kali 渗透 相机 新盘添加 partedUtil kubeless autoware rpa Alist rclone mount 挂载 网盘 UFW client-go k8s二次开发 计算机学习路线 编程语言选择 智能体 autogen openai coze linux子系统 忘记密码 Trae叒更新了? Docker Kubernetes 数据采集 Crawlee Playwright 桥接模式 windows虚拟机 虚拟机联网 UOS 开机自启动 桌面快捷方式 SSE 实验 oceanbase 传统数据库升级 银行 毕设 Trae IDE AI 原生集成开发环境 Trae AI MCP server C/S IPMI 王者荣耀 Cline Kylin-Server 服务器安装 嵌入式实习 Xterminal VSCode ruoyi Invalid Host allowedHosts 程序员 大模型入门 NAT转发 NAT Server vasp安装 openEuler 云电竞 云电脑 todesk kylin v10 麒麟 v10 intellij idea ocr 做raid 装系统 BMC rc.local 开机自启 systemd 麒麟 单一职责原则 nextjs reactjs 压测 ECS 源码剖析 rtsp实现步骤 流媒体开发 移动云 kamailio sip VoIP ukui 麒麟kylinos openeuler googlecloud 内网环境 iis 网络编程 聊天服务器 套接字 客户端 Socket DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 鲲鹏 火绒安全 cpp-httplib 统信UOS bonding 链路聚合 IPMITOOL 硬件管理 大数据平台 执法记录仪 智能安全帽 smarteye mybatis playbook 剧本 软件测试 温湿度数据上传到服务器 Arduino HTTP sqlserver sqlite3 camera Arduino 半虚拟化 硬件虚拟化 Hypervisor 视觉检测 llama.cpp CPU 主板 电源 网卡 TrueLicense 智能电视 安防软件 自动化测试 云耀服务器 服务器扩容没有扩容成功 clickhouse 代码规范 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题 CNNs 图像分类 蓝桥杯C++组 蓝桥杯 arcgis 内网渗透 靶机渗透 gunicorn c/s web3 区块链项目 pyside6 界面 web 海康 word 商用密码产品体系 技术共享 机架式服务器 1U工控机 RK3588 国产工控机 计算机系统 shard finebi web开发 源代码管理 notepad++ Claude wsl2 多端开发 智慧分发 应用生态 鸿蒙OS av1 电视盒子 机顶盒ROM 魔百盒刷机 小游戏 五子棋 VR手套 数据手套 动捕手套 动捕数据手套 neo4j 数据仓库 数据库开发 数据库架构 deekseek 图形渲染 chrome devtools chromedriver cudnn nvidia 小艺 Pura X 单元测试 crosstool-ng 鸿蒙开发 移动开发 命令行 基础入门 sonoma 自动更新 模拟器 RAGFLOW RAG 检索增强生成 文档解析 大模型垂直应用 飞牛NAS 飞牛OS MacBook Pro 输入法 ArcTS ArcUI GridItem Ubuntu Server Ubuntu 22.04.5 OpenHarmony kvm 邮件APP 免费软件 Linux24.04 deepin mamba Vmamba 混合开发 环境安装 源码 分析解读 Attention NLP 考研 Linux的基础指令 cmos SenseVoice 交叉编译 向日葵 AList fnOS 历史版本 下载 边缘计算 用户缓冲区 本地部署AI大模型 HarmonyOS NEXT 原生鸿蒙 稳定性 看门狗 WSL 版本 支付 微信支付 开放平台 etl 系统开发 binder 车载系统 framework 源码环境 强制清理 强制删除 mac废纸篓 can 线程池 查看显卡进程 fuser x64 SIGSEGV xmm0 su sudo CLion DIFY Linux权限 权限命令 特殊权限 CosyVoice 隐藏目录 管理器 通配符 fork wait waitpid exit ELF加载 ebpf uprobe 网络文件系统 双系统 cmake 跨平台 Mac软件 ROS2 WebVM perl MVS 海康威视相机 显示器 stable diffusion AI作画 宝塔 xfce 具身智能 强化学习 sudo原理 su切换 Isaac Sim 虚拟仿真 pyicu initramfs Linux内核 Grub 电脑桌面出现linux图标 电脑桌面linux图标删除不了 电脑桌面Liunx图标删不掉 linux图标删不掉 动静态库 gpt-3 推荐算法 zookeeper retry 重试机制 玩游戏 实时内核 大版本升 升级Ubuntu系统 llamafactory 微调 Qwen Xshell rime Echarts图表 折线图 柱状图 异步动态数据 可视化效果 设备树 进程间通信 resolv.conf 星河版 hdc mybase 裸机装机 linux磁盘分区 裸机安装linux 裸机安装ubuntu 裸机安装kali 裸机 Charles Obsidian Dataview 可执行程序 vsxsrv react native STL OpenCore 欧拉系统 mvc 顽固图标 启动台 nano appium risc-v 树莓派项目 多路转接 npu 技能大赛 ceph GoogLeNet TRAE 影刀 #影刀RPA# minio ollama下载加速 SSH 服务 SSH Server OpenSSH Server jina 跨域 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm workflow fast 实习 ShenTong AI代码编辑器 prompt 线程 金仓数据库 2025 征文 数据库平替用金仓 产测工具框架 IMX6ULL 管理框架 Open WebUI 办公自动化 pdf教程 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 apt pyautogui cd 目录切换 ssh远程登录 AnythingLLM AnythingLLM安装 xpath定位元素 IPv4 子网掩码 公网IP 私有IP Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 bot docker命令大全 大模型面经 大模型学习 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 代理 DocFlow 拓扑图 firewalld seatunnel 网络用户购物行为分析可视化平台 大数据毕业设计 P2P HDLC HiCar CarLife+ CarPlay QT k8s集群资源管理 云原生开发 yum源切换 更换国内yum源 DenseNet anaconda docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos Docker Hub docker pull daemon.json ragflow 源码启动 自动化编程 dity make Cookie 金融 telnet 远程登录 Xinference RAGFlow lio-sam SLAM Node-Red 编程工具 流编程 飞牛nas fnos docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 mq 主从复制 OpenManus wireshark 显示过滤器 ICMP Wireshark安装 Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer 容器技术 钉钉 cfssl 文心一言 自学笔记 小米 澎湃OS Android 端口聚合 win11 windows11 System V共享内存 进程通信 DBeaver edge浏览器 copilot import save load 迁移镜像 K8S k8s管理系统 scikit-learn GameFramework HybridCLR Unity编辑器扩展 自动化工具 ubuntu安装 linux入门小白 make命令 makefile文件 软件卸载 系统清理 ShapeFile GeoJSON Nginx 底层实现 WireGuard 异地组网 分子对接 autodock mgltools PDB PubChem LVM lvresize 磁盘扩容 pvcreate 全文检索 图搜索算法 内存管理 EVE-NG 考试 GRE IPv4/IPv6双栈 双栈技术 网路规划设计 ensp综合实验 IPv4过渡IPv6 IPv4与IPv6 postgres Dify重启后重新初始化 rtc pthread Qwen3 qwen3 32b vllm Masshunter 质谱采集分析软件 使用教程 科研软件 卸载 软件 列表 小智 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? deepseek-v3 ktransformers hosts hosts文件管理工具 开源软件 AI提示词优化 RagFlow 超级终端 多任务操作 提高工作效率 fabric LLaMA-Factory 集成 企业风控系统 互联网反欺诈 DDoS攻击 SQL注入攻击 恶意软件和病毒攻击 python高级编程 Ansible elk stack HistoryServer Spark YARN jobhistory 网卡的名称修改 eth0 ens33 EMQX 通信协议 华为机试 AD域 H3C v10 物联网开发 kind ftp 云服务 vscode1.86 1.86版本 ssh远程连接 个人博客 armbian u-boot 无法解析服务器的名称或地址 LLM Web APP Streamlit junit unity3d 大模型推理 HCIE 数通 豆瓣 追剧助手 迅雷 云服务器 服务器繁忙 统信操作系统 string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap Spring Security MNN vscode 1.86 api VPS SEO 社交电子 iDRAC R720xd 直流充电桩 充电桩 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 token sas Nuxt.js nuxt3 服务器部署ai模型 X11 Xming ux llama3 Chatglm 开源大模型 远程看看 远程协助 模拟退火算法 弹性计算 裸金属服务器 弹性裸金属服务器 FunASR ASR mosquitto DOIT 四博智联 业界资讯 思科模拟器 Cisco asp.net上传文件夹 asp.net上传大文件 .net core断点续传 能力提升 面试宝典 技术 IT信息化 算力 AISphereButler 游戏服务器 Minecraft netty SWAT 配置文件 服务管理 网络共享 加解密 Yakit yaklang 状态管理的 UDP 服务器 Arduino RTOS 博客 数据管理 数据治理 数据编织 数据虚拟化 代码调试 ipdb XCC Lenovo idm 网站搭建 serv00 树莓派 VNC .net mvc断点续传 OD机试真题 服务器能耗统计 田俊楠 工业4.0 域名服务 DHCP 符号链接 配置 智能音箱 智能家居 音乐库 飞牛 dba iBMC UltraISO 迁移指南 Windsurf gradle less visual studio Jellyfin saltstack MQTT协议 消息服务器 代码 代码托管服务 浏览器自动化 React Next.js 开源框架 视频平台 录像 视频转发 视频流 机柜 1U 2U Web服务器 多线程下载工具 PYTHON powerbi WINCC Carla 智能驾驶 Web应用服务器 服务器部署 本地拉取打包 数码 联机 僵尸毁灭工程 游戏联机 开服 gaussdb 语音识别 zerotier 服务器ssl异常解决 弹性服务器 联网 easyconnect 笔灵AI AI工具 Pyppeteer 视频监控 网站 nacos 配置原理 glm4 负载测试 deepseak 豆包 KIMI 腾讯元宝 VPN wireguard 充电桩平台 充电桩开源平台 solidworks安装 umeditor粘贴word ueditor粘贴word ueditor复制word ueditor上传word图片 ueditor导入word ueditor导入pdf ueditor导入ppt 论文阅读 电路仿真 multisim 硬件工程师 硬件工程师学习 电路图 电路分析 仪器仪表 laravel 软件开发 信任链 日志分析 系统取证 wordpress java-zookeeper 飞腾处理器 文件存储服务器组件 Webserver 异步 RK3568 paddle rsync Putty 花生壳 OpenSSH bigdata fpga vue在线预览excel和编辑 vue2打开解析xls电子表格 浏览器新开页签或弹框内加载预览 文件url地址或接口二进制文档 解决网页打不开白屏报错问题 issue linq 分布式数据库 集中式数据库 业务需求 选型误 GPU状态 语法 移动魔百盒 服务网格 istio 序列化反序列化 捆绑 链接 谷歌浏览器 youtube google gmail sequoiaDB navicat deepseek r1 玩机技巧 软件分享 软件图标 EMUI 回退 降级 embedding 嵌入式Linux IPC ruby xshell termius iterm2 prometheus数据采集 prometheus数据模型 prometheus特点 mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 代理服务器 Typore trea idea cocoapods 我的世界服务器搭建 minecraft MacOS vpn MAC IO模型 csrutil mac恢复模式进入方法 SIP 恢复模式 终端 g++ g++13 MDK 嵌入式开发工具 论文笔记 beautifulsoup 子系统 lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 NVM Node Yarn PM2 brew comfyui comfyui教程 物理地址 页表 虚拟地址 材料工程 对比 meld DiffMerge csrf Linux的基础开发工具 cpolar harmonyosnext swift miniconda SPI nac 802.1 portal 端口开放 lvs RockyLinux GKI KMI 互联网实用编程指南 AppLinking 应用间跳转 ubuntu24 vivado24 yum换源 系统内核 Linux版本 function address 函数 地址 蓝牙 Bluetooth 配对 IMX317 MIPI H265 VCU 企业网络规划 华为eNSP 本地知识库部署 DeepSeek R1 模型 ubuntu24.04.1 yaml Ultralytics 可视化 健康医疗 互联网医院 Python基础 Python技巧 kerberos TCP协议 USB网络共享 运维监控 浏览器开发 AI浏览器 rnn PPI String Cytoscape CytoHubba linux 命令 sed 命令 自动化任务管理 多进程 计算机 springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 显卡驱动 虚幻引擎 searxng dock 加速 bat 软负载 seleium 端口 查看 ss GRUB引导 Linux技巧 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 免费域名 域名解析 问题解决 性能调优 安全代理 DeepSeek r1 image Sealos NLP模型 lb 协议 图片增强 增强数据 scapy Helm k8s集群 生活 Docker快速入门 netlink libnl3 Apache Beam 批流统一 案例展示 数据分区 容错机制 esp32 GeneCards OMIM TTD VGG网络 卷积层 池化层 SystemV fiddler ajax 音乐服务器 Navidrome 音流 脚本 post.io 企业邮箱 搭建邮箱 静态IP 命令模式 软考设计师 中级设计师 SQL 软件设计师 源代码 可用性测试 watchtower 微信公众平台 线程同步 线程互斥 条件变量 d3d12 android-studio MinIO 轮播图 springboot容器部署 springboot容器化部署 微服务容器化负载均衡配置 微服务容器多节点部署 微服务多节点部署配置负载均衡 isaacgym 基本指令 模板 scrapy 热键 dns是什么 如何设置电脑dns dns应该如何设置 libreoffice 转换 pppoe radius Kylin OS SVN Server tortoise svn UDP autodl EasyConnect SSL证书 code-server RTMP 应用层 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 流式接口 网页设计 DeepSeek行业应用 Heroku 网站部署 CORS 服务器时间 多个客户端访问 IO多路复用 TCP相关API 规格说明书 hibernate 游戏机 pyqt TrinityCore 魔兽世界 我的世界 我的世界联机 恒源云 Wi-Fi bootstrap 银河麒麟服务器操作系统 系统激活 深度优先 并集查找 换根法 树上倍增 XFS xfs文件系统损坏 I_O error SSL 域名 黑客技术 AI agent ISO镜像作为本地源 ldap jetty undertow 相差8小时 UTC 时间 网工 国标28181 监控接入 语音广播 流程 SDP TCP服务器 qt项目 qt项目实战 qt教程 佛山戴尔服务器维修 佛山三水服务器维修 三级等保 服务器审计日志备份 thingsboard 聊天室 联想开天P90Z装win10 崖山数据库 YashanDB 显示管理器 lightdm gdm 雨云服务器 URL 宕机切换 服务器宕机 nfs Java Applet URL操作 服务器建立 Socket编程 网络文件读取 Erlang OTP gen_server 热代码交换 事务语义 tailscale derp derper 中转 备选 调用 示例 uniapp hugo 致远OA OA服务器 服务器磁盘扩容 网络穿透 流量运营 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 micropython mqtt wsgiref Web 服务器网关接口 SysBench 基准测试 服务器主板 AI芯片 W5500 OLED u8g2 skynet Dell HPE 联想 浪潮 CDN LORA 搭建个人相关服务器 毕昇JDK 微信开放平台 微信公众号配置 VS Code 欧标 OCPP AD 域管理 小智AI服务端 xiaozhi TTS 服务器管理 配置教程 网站管理 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 超融合 端口测试 UOS1070e Qwen2.5-VL 服务器正确解析请求体 nosql banner 授时服务 北斗授时 NFC 近场通讯 智能门锁 动态规划 跨域请求 网络原理 免费 servlet PyQt PySide6 程序化交易 量化交易 高频交易 金仓数据库概述 金仓数据库的产品优化提案 分布式账本 共识算法 massa sui aptos sei AimRT 7-zip element-ui 上传视频并预览视频 vue上传本地视频及进度条功能 vue2选择视频上传到服务器 upload上传视频组件插件 批量上传视频 限制单个上传视频 #STC8 #STM32 Python 视频爬取教程 Python 视频爬取 Python 视频教程 框架 qwen2vl 百度云 矩池云 数据下载 数据传输 匿名FTP 邮件传输代理 SSL支持 chroot监狱技术 PTrade QMT 量化股票 gitee go OpenGL SoC 概率论 rtcp CPU架构 服务器cpu 腾讯云服务器 轻量应用服务器 linux系统入门 linux命令 风扇散热策略 曙光 海光 宁畅 中科可控 静态NAT 粘包问题 元服务 应用上架 regedit 开机启动 风扇控制软件 合成模型 扩散模型 图像生成 Linux环境 ArtTS whistle macOS 极限编程 搜狗输入法 中文输入法 archlinux kde plasma 鸿蒙NEXT 华为鸿蒙系统 ArkTS语言 Component 生命周期 条件渲染 Image图片组件 NVIDIA win向maOS迁移数据 数据库管理 GRANT REVOKE 接口返回 qtcreator 导航栏 Bandizip Mac解压 Mac压缩 压缩菜单 PostgreSQL15数据库 软件商店 信创 livecd systemtools devmem NTP服务器 wifi驱动 jdk11安装 jdk安装 openjdk11 openjdk11安装 IP地址 IPv6 计算机基础 苹果电脑装windows系统 mac安装windows系统 mac装双系统 macbook安装win10双 mac安装win10双系统 苹果电脑上安装双系统 mac air安装win 影视app easyTier 组网 进程池实现 文件共享 broadcom MySQL direct12 figma Logstash 日志采集 docker部署Python docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 抗锯齿 烟花代码 烟花 元旦 增强现实 沉浸式体验 技术实现 案例分析 AR AI Agent 字节智能运维 项目部署 easyui qemu libvirt deployment daemonset statefulset cronjob 抓包工具 达梦 DM8 解决方案 接口优化 virtualbox 集群管理 电视剧收视率分析与可视化平台 网页服务器 web服务器 AzureDataStudio qt5 客户端开发 Zoertier 内网组网 机械臂 IPv6测试 IPv6测速 IPv6检测 IPv6查询 webview solr VM虚拟机 docker search anythingllm open-webui docker国内镜像 finalsheel 智能问答 Spring AI Milvus 漏洞报告生成 mapreduce 定义 核心特点 优缺点 适用场景 动态库 GCC编译器 -fPIC -shared 医院门诊管理系统 MLLMs VLM gpt-4v Python学习 Python编程 GPUGEEK 通用环境搭建 5分钟快速学 docker入门 高可用 代理配置 企业级DevOps Radius Ubuntu 24.04.1 轻量级服务器 GIS 遥感 WebGIS ecm bpm 僵尸世界大战 游戏服务器搭建 HAProxy 根服务器 银河麒麟桌面操作系统 内网服务器 内网代理 内网通信 es open Euler dde h.264 飞书 无桌面 线性代数 电商平台 muduo MI300x 银河麒麟高级服务器 外接硬盘 Kylin HTTP 服务器控制 ESP32 DeepSeek triton 模型分析 sqlite 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 swoole ai工具 xss Unity插件 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 游戏开发 sentinel 干货分享 黑客工具 密码爆破 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 分布式训练 midjourney 架构与原理 chfs ubuntu 16.04 vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 IDEA tidb 北亚数据恢复 oracle数据恢复 ABAP hexo 繁忙 解决办法 替代网站 汇总推荐 AI推理 pgpool lua 状态模式 西门子PLC 通讯 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 阿里云ECS 信创终端 中科方德 win服务器架设 windows server 数字证书 签署证书 AI员工 mcp服务器 client close yashandb 三次握手 设计规范 logstash EMC存储 NetApp存储 Qt QModbus vCenter服务器 ESXi主机 监控与管理 故障排除 日志记录 AWS lrzsz caddy 火山引擎 authing cocos2d 3dcoat aac pxe mcp协议 go-zero OpenManage pyscenic 生信教程 工厂方法模式 accept 自定义登录信息展示 motd 美化登录 实时云渲染 云渲染 3D推流 openvino 环境 非root 能源 动态域名 LVS 惠普服务器 惠普ML310e Gen8 惠普ML310e Gen8V2 h.265 站群 多IP 流量 socket 排序算法 选择排序 RNG 状态 可复现性 随机数生成 大厂程序员 硅基计算 碳基计算 认知计算 生物计算 AGI 系统架构设计 软件哲学 程序员实现财富自由 站群服务器 gitlab服务器 物理服务器 服务器租用 云服务器租用 物理机租用 网络接口 时间间隔 所有接口 多网口 事件分析 边缘服务器 利旧 AI识别 5090 显卡 AI性能 Metastore Catalog bert mujoco OS Ubuntu 24.04 搜狗输入法闪屏 Ubuntu中文输入法 实时日志 logs 能效分析 nginx默认共享目录 ubantu 青少年编程 ANDROID_HOME zshrc 自定义shell当中管道的实现 匿名和命名管道 MateBook latex 更换镜像源 红黑树 tar.gz tar.xz linux压缩 截图 录屏 gif 开发工具 基础指令 李心怡 proxy模式 输入系统 CAD瓦片化 栅格瓦片 矢量瓦片 Web可视化 DWG解析 金字塔模型 知行EDI 电子数据交换 知行之桥 EDI homeassistant 红黑树封装map和set web环境 Multi-Agent 蜂窝网络 频率复用 射频单元 无线协议接口RAN 主同步信号PSS 手机 Modbus TCP 学习路线 LSTM 烟雾检测 yolo检测 消防检测 Web3 Telegram nmcli 网络配置 Linux的进程概念 Apache Flume 配置优化 高级功能 大数据工具集成 containerd 访问公司内网 openresty C/C++ 恒玄BES 仓库 Headless Linux 查询数据库服务IP地址 SQL Server 大大通 第三代半导体 碳化硅 计算虚拟化 弹性裸金属 带外管理 Anolis nginx安装 linux插件下载 flink ardunio BLE 存储维护 pythonai PlaywrightMCP 网络库 泛型编程 Async注解 事件驱动 access blocked 破解 vmware tools 制造 anonymous janus 证书 签名 jQuery 光电器件 LED rtmp http状态码 请求协议 机器人仿真 模拟仿真 电子信息工程 EF Core 客户端与服务器评估 查询优化 数据传输对象 查询对象模式 多媒体 java毕业设计 微信小程序医院预约挂号 医院预约 医院预约挂号 小程序挂号 orbslam2 pipe函数 管道的大小 匿名管道的四种情况 word转pdf 零售 数字比特流 模拟信号 将二进制数据映射到模拟波形上 频谱资源 振幅频率相位 载波高频正弦波 容器化 Serverless 4 - 分布式通信、分布式张量 代码复审 BCLinux dockerfile 华为昇腾910b3 sse_starlette Starlette Server-Sent Eve 服务器推送事件 地平线5 asp.net DELL R730XD维修 全国服务器故障维修 ps命令 共享 设置 软路由 Linux指令 影刀证书 分享 服务注册与发现 MCP 服务器 JADX-AI 插件 客户端-服务器架构 点对点网络 服务协议 网络虚拟化 网络安全防御 科勘海洋 数据采集浮标 浮标数据采集模块 HTTP状态码 客户端错误 服务器端错误 API设计 迁移 手动分区 charles CodeBuddy首席试玩官 Svelte 全栈 SFTP 本地不受DeepSeek MobileNetV3 A2A 零日漏洞 CVE OpenAI 机架式 IDC