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

【IO模型与并发服务器】

阻塞IO与非阻塞IO

阻塞IO

非阻塞IO

IO多路复用

select函数

//头文件:
#include <sys/socket.h> 
#include <sys/types.h>
//函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval  *timeout);

poll函数

//头文件:
#include <sys/socket.h> #include <sys/types.h>
//函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数详解

参数名类型作用说明
fdsstruct pollfd*指向 pollfd 结构体数组的指针,存储待监控的 FD 及关注的事件(输入 / 输出结合)。
nfdsnfds_t需监控的 pollfd 结构体数量(即数组有效长度),nfds_t 是无符号整数类型(通常为 unsigned int)。
timeoutint

超时时间(单位:毫秒),控制 poll() 的阻塞行为:

timeout > 0:阻塞 timeout 毫秒后返回(超时返回 0);

timeout = 0:不阻塞,立即返回(轮询模式);

timeout = -1:无限阻塞,直到至少一个 FD 发生事件或被信号中断。

返回值:成功返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0,失败返回-1(并重置错误码)

pollfd 结构体

struct pollfd {int    fd;       // 待监控的文件描述符(FD)short  events;   // 用户关注的事件类型(输入参数,由用户设置)short  revents;  // 内核返回的实际发生的事件(输出参数,由内核填充)
};

fd:待监控的文件描述符

  • 作用:指定需要 poll() 监控的文件描述符(如 socket、管道、标准输入等)。
  • 特殊值
    • 若 fd = -1poll() 会忽略该结构体(不监控任何事件),且对应的 events 和 revents 会被忽略。
    • 用途:动态管理监控列表时(如移除某个 FD),无需修改数组长度,只需将 fd 设为 -1 即可。

2. events:用户关注的事件(输入参数)

用户通过设置 events 的二进制位,指定需要监控的事件类型。常见事件常量定义在 <poll.h> 中,可通过按位或(|) 组合多个事件。

事件常量取值(十六进制)含义说明
POLLIN0x001普通数据 / 优先数据可读(如 socket 收到客户端数据、标准输入有输入)。
POLLPRI0x002紧急数据可读(如 socket 的带外数据 OOB)。
POLLOUT0x004普通数据 / 优先数据可写(如 socket 可发送数据,无阻塞)。
POLLERR0x008对应 FD 发生错误(无需用户设置,内核自动返回至 revents)。
POLLHUP0x010对应 FD 发生挂起(如 peer 关闭连接,管道写端关闭)(内核自动返回)。
POLLNVAL0x020对应 fd 无效(如 FD 未打开、已关闭)(内核自动返回)。
POLLRDNORM0x040普通数据可读(与 POLLIN 功能重叠,部分系统兼容用)。
POLLWRNORM0x080普通数据可写(与 POLLOUT 功能重叠,部分系统兼容用)。
POLLRDBAND0x100优先带数据可读(如 TCP 带外数据,与 POLLPRI 关联)。
POLLWRBAND0x200优先带数据可写。

3. revents:内核返回的实际事件(输出参数)

  • 作用poll() 调用成功后,内核会根据 FD 的实际状态,在 revents 中填充发生的事件(二进制位)。
  • 关键特性
    • 只读性:用户无需设置 revents,仅需在 poll() 返回后读取其值。
    • 自动包含错误事件:即使用户未在 events 中设置 POLLERRPOLLHUPPOLLNVAL,内核若检测到这些事件,也会自动将其写入 revents
    • 与 events 的关联revents 中的事件是 events 中用户关注事件的子集或超集(超集源于错误事件的自动添加)。

多线程并发服务器

服务器模型

多线程并发服务器原理

服务端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>#define PORT 8888
#define SERVER_IP "192.168.136.130"
#define BUFFER_SIZE 128//自定义结构体 -> 封装客户端连接信息,用于在线程间传递
typedef struct MSG{int acceptfd;//客户端套接字描述符struct sockaddr_in clientaddr;//客户端地址信息
}msg_t;//创建线程处理函数
void *deal_read_write(void *arg){msg_t msg = *(msg_t*)arg;char buf[BUFFER_SIZE] = {0}; int nbytes = 0;printf("客户端【%s:%d】连接到了服务器\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port));//客户端数据接收与发送while(1){nbytes = recv(msg.acceptfd, buf, BUFFER_SIZE, 0);if(nbytes == -1){perror("读取客户端消息失败:");}else if(nbytes == 0){printf("客户端【%s:%d】断开连接\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port));break;}printf("客户端【%s:%d】发来数据:%s\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port),buf);//回显数据给客户端strcat(buf, "--服务器");if(send(msg.acceptfd, buf, BUFFER_SIZE, 0) == -1){perror("回显数据失败:");}}close(msg.acceptfd);pthread_exit(NULL);
}int main(){//创建套接字-配置服务器地址结构体int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1){perror("创建套接字失败:");return -1;}struct sockaddr_in server_addr;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 = inet_addr(SERVER_IP);socklen_t serveraddr_len = sizeof(server_addr);//绑定、监听、处理客户端的连接if(bind(sockfd, (struct sockaddr *)&server_addr, serveraddr_len) == -1){perror("绑定失败:");close(sockfd);return -1;}if(listen(sockfd, 5) == -1){perror("监听失败:");close(sockfd);return -1;}struct sockaddr_in client_addr;socklen_t clientaddr_len = sizeof(client_addr);int acceptfd = 0;pthread_t pthread_id = 0;msg_t msg;int ret = 0;//主线程:接受连接并创建子线程while(1){if((acceptfd = accept(sockfd, (struct sockaddr *)&client_addr, &clientaddr_len)) == -1){perror("接受连接失败:");return -1;}msg.acceptfd = acceptfd;msg.clientaddr = client_addr;//创建子线程ret = pthread_create(&pthread_id, NULL, deal_read_write, &msg);if(ret == -1){printf("错误码:%d,错误信息:%s\n",ret,strerror(ret));close(sockfd);return -1;}ret = pthread_detach(pthread_id);if(ret == -1){printf("错误码:%d,错误信息:%s\n",ret,strerror(ret));close(sockfd);return -1;}}close(sockfd);return 0;
}

多进程并发服务器

#include <stdio.h>          // 标准输入输出库(打印日志、错误信息)
#include <sys/types.h>      // 系统数据类型定义(pid_t、socket相关类型等)
#include <sys/socket.h>     // socket核心库(创建、绑定、监听、收发等接口)
#include <netinet/ip.h>     // IPv4协议相关定义(补充sockaddr_in结构体支持)
#include <netinet/in.h>     // 网络地址结构体定义(sockaddr_in、字节序转换函数)
#include <string.h>         // 字符串处理库(memset、strcat等)
#include <stdlib.h>         // 标准库(exit函数等)
#include <arpa/inet.h>      // IP地址转换库(inet_ntoa:网络字节序IP转字符串)
#include <unistd.h>         // 系统调用库(close、fork、kill、getpid等)
#include <signal.h>         // 信号处理库(signal、sig_func信号回调)
#include <sys/wait.h>       // 进程等待库(wait函数:回收子进程资源)// 服务器监听端口号(自定义,需确保未被占用,范围1024-65535)
#define PORT 8888
// 服务器绑定的IP地址(根据实际网卡配置修改,需与客户端在同一网段)
#define SERVER_IP "192.168.26.128"
// 数据收发缓冲区大小(单次最大传输128字节,避免缓冲区溢出)
#define BUFFER_SIZE 128/*** @brief 信号处理函数:专门回收子进程资源,避免僵尸进程* @param signum 接收到的信号编号(此处固定为SIGUSR1)* 核心逻辑:wait(NULL) 会阻塞等待任意子进程终止,回收其PCB资源* 避免子进程退出后成为僵尸进程(占用系统进程表资源)*/
void sig_func(int signum){// wait(NULL):不关心子进程退出状态,仅回收资源;若没有子进程可回收,会立即返回wait(NULL);
}int main(){// 服务器监听套接字描述符(用于接受客户端连接请求,不直接收发数据)int sockfd = 0;// 客户端连接套接字描述符(每个客户端对应一个,用于与该客户端收发数据)int acceptfd = 0;// 进程ID(fork返回值:父进程返回子进程PID,子进程返回0,失败返回-1)pid_t pid = 0;// 函数返回值临时变量(用于判断系统调用是否成功)int ret = 0;/*** 1. 创建TCP套接字(监听用)* 参数说明:* AF_INET:使用IPv4协议族* SOCK_STREAM:流式套接字(对应TCP协议,可靠、面向连接)* 0:默认协议(由系统自动匹配TCP协议,无需手动指定)* 返回值:成功返回非负套接字描述符,失败返回-1*/sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1){perror("套接字创建失败:");  // 打印系统错误原因(如权限不足、资源耗尽)return -1;                  // 套接字创建失败,直接退出程序}// 服务器地址结构体:存储服务器的IP、端口、协议族等信息struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体(避免随机垃圾值影响)server_addr.sin_family = AF_INET;              // 协议族:IPv4server_addr.sin_port = htons(PORT);             // 端口号:htons将主机字节序转为网络字节序(大端)server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  // IP地址:inet_addr将字符串IP转为网络字节序socklen_t serveraddr_len = sizeof(server_addr);  // 地址结构体长度(传给bind函数)/*** 2. 绑定套接字:将监听套接字与服务器IP+端口绑定(确定服务地址)* 若绑定失败(如IP错误、端口已被占用),需关闭套接字避免资源泄漏*/if(bind(sockfd, (struct sockaddr*)&server_addr, serveraddr_len) == -1){perror("绑定失败:");close(sockfd);  // 释放已创建的套接字资源return -1;      // 绑定失败,退出程序}/*** 3. 监听连接:将套接字转为监听状态,等待客户端发起连接* 参数说明:* 第二个参数5:监听队列大小(最大同时等待连接的客户端数,超过则拒绝)*/if(listen(sockfd, 5) == -1){perror("监听失败:");close(sockfd);  // 释放套接字资源return -1;      // 监听失败,退出程序}// 客户端地址结构体:存储连接成功的客户端IP、端口信息struct sockaddr_in client_addr;socklen_t clientaddr_len = sizeof(client_addr);  // 客户端地址结构体长度/*** 4. 注册信号处理函数:绑定SIGUSR1信号与sig_func回调* 作用:子进程退出前发送SIGUSR1信号,触发sig_func回收子进程资源*/signal(SIGUSR1, sig_func);printf("服务器启动成功!监听IP:%s,端口:%d\n", SERVER_IP, PORT);/*** 主循环:持续接受客户端连接(服务器核心逻辑)* 循环特性:accept是阻塞函数,无客户端连接时会一直阻塞等待*/while(1){/*** 5. 接受客户端连接:* 参数说明:* sockfd:监听套接字* &client_addr:输出参数,存储连接客户端的IP和端口* &clientaddr_len:输入输出参数,传入结构体长度,返回实际使用长度* 返回值:成功返回客户端套接字描述符(acceptfd),失败返回-1*/acceptfd = accept(sockfd, (struct sockaddr*)&client_addr, &clientaddr_len);if(acceptfd == -1){perror("接受连接失败:");close(sockfd);  // 接受连接失败,关闭监听套接字break;          // 退出主循环,程序终止}/*** 6. 创建子进程:用fork创建子进程处理当前客户端通信* 原因:父进程继续监听新连接,子进程专注于单个客户端的数据收发(并发处理)*/pid = fork();if(pid == -1){  // fork失败(如系统进程数达到上限)perror("创建子进程失败:");close(acceptfd);  // 关闭当前客户端套接字(避免资源泄漏)break;            // 退出主循环,程序终止}else if(pid == 0){  // 子进程逻辑(pid=0表示当前是子进程)// 子进程专属:与客户端进行数据收发char buf[BUFFER_SIZE] = {0};  // 数据收发缓冲区(初始化清空,避免脏数据)int nbytes = 0;               // 实际接收的字节数// 打印客户端连接信息:inet_ntoa转网络字节序IP为字符串,ntohs转网络字节序端口为主机字节序printf("客户端【%s:%d】连接到服务器\n", inet_ntoa(client_addr.sin_addr),  // 客户端IPntohs(client_addr.sin_port));     // 客户端端口/*** 7. 接收客户端数据:* 参数说明:* acceptfd:客户端套接字* buf:接收缓冲区* BUFFER_SIZE:缓冲区最大长度* 0:默认阻塞模式(无数据时子进程阻塞等待)* 返回值:nbytes>0表示实际接收字节数;=0表示客户端断开;=-1表示接收失败*/nbytes = recv(acceptfd, buf, BUFFER_SIZE, 0);if(nbytes == -1){  // 接收数据失败(如网络异常)perror("接收失败:");}else if(nbytes == 0){  // 客户端主动断开连接(调用close)printf("客户端【%s:%d】断开连接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));break;  // 退出子进程内部逻辑,准备关闭资源}// 打印客户端发送的数据(nbytes确保只打印实际接收的内容)printf("客户端【%s:%d】发来数据:%s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);// 拼接回显标记:在客户端数据后添加"--来自服务端",告知客户端数据已处理strcat(buf, "--来自服务端");/*** 8. 回显数据给客户端:将处理后的数据发送回客户端*/if(send(acceptfd, buf, BUFFER_SIZE, 0) == -1){perror("回显失败:");}close(acceptfd);  // 子进程通信完成,关闭客户端套接字(释放资源)kill(getpid(), SIGUSR1);  // 向自身发送SIGUSR1信号,触发父进程回收资源exit(0);  // 子进程正常退出(必须调用,否则子进程会继续执行主循环)}else if(pid > 0){  // 父进程逻辑(pid>0表示当前是父进程,pid为子进程ID)// 父进程无需与客户端通信,关闭客户端套接字(避免文件描述符泄漏)// 注:子进程会复制acceptfd,父进程关闭不影响子进程使用close(acceptfd);}}close(sockfd);  // 程序退出前,关闭服务器监听套接字(释放资源)return 0;
}

IO多路复用并发服务器

服务端

#include <stdio.h>          // 标准输入输出库(打印日志、错误信息)
#include <stdlib.h>         // 标准库(无直接使用,保留兼容)
#include <sys/types.h>      // 系统数据类型定义(socket、fd相关类型)
#include <string.h>         // 字符串处理库(memset清空缓冲区/结构体)
#include <sys/socket.h>     // socket核心库(创建、绑定、监听、收发、select等接口)
#include <arpa/inet.h>      // IP地址转换库(inet_ntoa:网络字节序IP转字符串)
#include <netinet/ip.h>     // IPv4协议补充定义(支持sockaddr_in结构体)
#include <netinet/in.h>     // 网络地址结构体(sockaddr_in)+ 字节序转换函数(htons/ntohs)
#include <unistd.h>         // 系统调用库(close关闭套接字)
#include <sys/time.h>       // select机制依赖(fd_set、select函数声明)// 服务器监听端口号(1024-65535区间,需确保未被占用)
#define PORT 8888
// 服务器绑定IP(根据实际网卡配置修改,与客户端需同一网段;0.0.0.0可监听所有网卡)
#define SERVER_IP "192.168.26.128"
// 数据收发缓冲区大小(单次最大传输128字节,避免缓冲区溢出)
#define BUFFER_SIZE 128int main(){// 监听套接字描述符:专门用于接受客户端连接请求,不直接收发数据int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if(listen_fd == -1){  // socket创建失败(如权限不足、系统资源耗尽)perror("创建套接字失败:");  // 打印系统级错误原因return -1;                  // 初始化失败,直接退出程序}// 服务器地址结构体:存储服务器的IP、端口、协议族等核心信息struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体(避免随机垃圾值干扰)server_addr.sin_family = AF_INET;              // 协议族:IPv4server_addr.sin_port = htons(PORT);             // 端口号:htons将主机字节序转为网络字节序(大端)server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  // IP地址:inet_addr将字符串IP转为网络字节序socklen_t serveraddr_len = sizeof(server_addr);  // 地址结构体长度(传给bind函数的必需参数)// 绑定套接字:将监听套接字与服务器IP+端口绑定(确定服务的网络地址)int ret = bind(listen_fd, (struct sockaddr*)&server_addr, serveraddr_len);if(ret == -1){  // 绑定失败(如IP错误、端口已被占用、无绑定权限)perror("绑定失败:");close(listen_fd);  // 释放已创建的监听套接字资源(避免泄漏)return -1;          // 绑定失败,退出程序}// 监听连接:将套接字转为"监听状态",等待客户端发起TCP连接请求// 第二个参数10:监听队列大小(最大同时等待连接的客户端数,超过则拒绝新连接)if(listen(listen_fd, 10) == -1){perror("监听失败:");close(listen_fd);  // 释放套接字资源return -1;          // 监听失败,退出程序}/*** select机制核心:文件描述符集合(fd_set)管理* 为什么需要两个集合?* - readfd_save_set:保存所有需要监控的fd(监听fd + 所有客户端连接fd),避免每次select后丢失* - readfd_modify_set:每次select调用的临时集合(select会修改该集合,只保留有事件的fd)*/fd_set readfd_save_set, readfd_modify_set;FD_ZERO(&readfd_save_set);  // 初始化集合:清空所有fd位(默认所有fd都未被监控)FD_SET(listen_fd, &readfd_save_set);  // 将监听套接字加入监控集合(监控"新连接请求"事件)int max_fd = listen_fd;  // 记录当前最大的文件描述符(select的第一个参数必需,需动态更新)char buf[BUFFER_SIZE] = {0};  // 数据收发缓冲区(初始化清空,避免脏数据残留)printf("开启小型的select并发服务器\n");printf("服务器监听成功!IP:%s,端口:%d\n", SERVER_IP, PORT);// 主循环:持续监控并处理事件(select并发的核心循环)while(1){// 关键:每次select前,将临时集合重置为保存集合(因为select会修改临时集合)readfd_modify_set = readfd_save_set;/*** select函数:多路复用核心接口,监控多个fd的"读事件"* 参数说明:* 1. max_fd + 1:监控的fd范围(fd从0开始,需包含最大fd,故+1)* 2. &readfd_modify_set:监控"读事件"的fd集合(有数据可读/新连接请求)* 3. NULL:不监控"写事件"(此处仅需收发,无需主动监控写就绪)* 4. NULL:不监控"异常事件"(简化逻辑,默认忽略)* 5. NULL:阻塞模式(无任何事件时,select会一直阻塞,不占用CPU资源)* 返回值:>0表示有事件的fd数量;-1表示失败;0表示超时(此处无超时)*/int fds = select(max_fd + 1, &readfd_modify_set, NULL, NULL, NULL);if(fds == -1){  // select调用失败(如被信号中断)perror("select失败:");continue;  // 不退出循环,继续监控(容错处理)}// 客户端地址结构体:存储"新连接客户端"的IP和端口信息(仅在accept时使用)struct sockaddr_in client_addr;socklen_t clientaddr_len = sizeof(client_addr);/*** 遍历所有可能的fd,判断哪个fd有事件发生* 为什么从3开始?0=标准输入、1=标准输出、2=标准错误,均不监控* 遍历范围:3 ~ max_fd(覆盖所有已加入监控的fd)*/int event_fd = 3;for(; event_fd < max_fd + 1; event_fd++){// FD_ISSET:判断当前fd是否在"有事件的集合"中(核心事件判断宏)if(FD_ISSET(event_fd, &readfd_modify_set)){// 情况1:监听套接字有事件 → 有新客户端连接请求if(event_fd == listen_fd){// 接受新连接:生成"客户端连接套接字"(acceptfd),专门与该客户端收发数据int acceptfd = accept(event_fd, (struct sockaddr*)&client_addr, &clientaddr_len);if(acceptfd == -1){  // 接受连接失败(如系统资源不足)perror("accept失败:");continue;  // 忽略该失败连接,继续监控其他fd}// 将新客户端的连接fd加入"保存集合"(后续监控其读事件:是否发数据)FD_SET(acceptfd, &readfd_save_set);// 更新max_fd:确保select能监控到最新的fd(必须维护,否则新fd无法被监控)max_fd = (max_fd > acceptfd) ? max_fd : acceptfd;// 打印客户端连接信息(inet_ntoa转IP,ntohs转端口字节序)printf("客户端【%s:%d】连接服务器\n",inet_ntoa(client_addr.sin_addr),  // 客户端IP(网络字节序→字符串)ntohs(client_addr.sin_port));     // 客户端端口(网络字节序→主机字节序)}// 情况2:客户端连接fd有事件 → 客户端发送数据/断开连接else{memset(buf, 0, sizeof(buf));  // 清空缓冲区(避免上次数据残留)// 接收客户端数据:从客户端连接fd中读取数据int nbytes = recv(event_fd, buf, BUFFER_SIZE, 0);if(nbytes == -1){  // 接收数据失败(如网络异常、客户端强制断开)perror("获取客户端信息失败:");continue;  // 忽略该错误,继续监控其他客户端}// nbytes == 0:客户端主动调用close断开连接(TCP四次挥手完成)else if(nbytes == 0){printf("客户端【%s:%d】断开连接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));FD_CLR(event_fd, &readfd_save_set);  // 从监控集合中移除该fd(不再监控)close(event_fd);                      // 关闭连接套接字(释放资源,避免泄漏)continue;  // 跳过后续逻辑,处理下一个fd}// 正常接收:打印客户端发送的数据printf("客户端【%s:%d】发来数据:%s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);// 数据回显:将接收的数据原封不动发送回客户端(验证通信正常)nbytes = send(event_fd, buf, BUFFER_SIZE, 0);if(nbytes == -1){  // 回显失败(如客户端已断开)perror("回显失败:");continue;}}}}}// 理论上不会执行到这里(主循环是死循环),仅作资源释放冗余处理close(listen_fd);return 0;
}
http://www.dtcms.com/a/613856.html

相关文章:

  • QT QML Item基类浅谈
  • Go语言学习笔记(二)
  • 前端CSS预处理器对比,Sass与Less
  • Ubuntu NAT模式设置静态 IP 地址
  • 建英文网站广州排名seo公司
  • Qt 对 JSON和XML文件的操作详解
  • flash网站开发广州门户网站制作公司
  • VideoPipe中集成多模态大模型做视频(图片)分析
  • 网站推广优化排名seo建设网站论文
  • Halcon HDevelop 核心语法与实战笔记
  • 网站开发需要的资料高级网站开发工程师工资
  • (6)框架搭建:Qt实战项目之主窗体工具条
  • 购物网站建设和使用东莞公司展厅设计公司
  • Android协程 vs. 传统线程/线程池:现代化异步编程的演进
  • 【编程】脚本编写入门:从零到一的自动化之旅
  • 自动化测试用例编写详解
  • ASC学习笔记0017:返回此能力系统组件的所有属性列表
  • Python可迭代对象讲解
  • 开源项目分享:Gitee热榜项目 2025年11月第三周 周榜
  • 哪里可做网站优化推广网站seo
  • Java Web 项目中Maven 常用库
  • 私人做网站图片网络科技有限公司怎么挣钱
  • 人力资源网站怎么做网络维护是什么职业
  • 体育直播/赛事直播/电竞直播/游戏直播/录播转播/原生APP/赛程比分系统
  • 学Java第四十五天——斗地主小游戏创作
  • 怎样创建一个国际网站wordpress外观菜单
  • 【总结】计网 IPv6
  • 【动态高斯重建】论文集合:从4DGT到OMG4、4DSioMo
  • 【ASR论文】Zipformer:更快、更强的语音识别编码器 | 小米公司
  • 从零开始学二叉树(上):树的初识 —— 从文件系统到树的基本概念