网络编程 lesson5 IO多路复用
select
当需要在一个或多个文件描述符上等待事件发生时,可以使用select函数。
select函数是一个阻塞调用,它会一直等待,直到指定的文件描述符上有事件发生或超时。
select函数详解
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);功能:select用于监测是哪个或哪些文件描述符产生事件;参数:nfds: 监测的最大文件描述个数(这里是个数,使用的时候注意,与文件中最后一次打开的文件描述符所对应的值的关系是什么?)readfds: 读事件集合; //读(用的多)writefds: 写事件集合; //NULL表示不关心exceptfds:异常事件集合; timeout:超时检测 1如果不做超时检测:传 NULL select返回值: <0 出错>0 表示有事件产生;如果设置了超时检测时间:&tvselect返回值:<0 出错>0 表示有事件产生;==0 表示超时时间已到;struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};void FD_CLR(int fd, fd_set *set);//将fd从表中清除int FD_ISSET(int fd, fd_set *set);//判断fd是否在表中void FD_SET(int fd, fd_set *set);//将fd添加到表中void FD_ZERO(fd_set *set);//清空表1
使用步骤
1.准备文件描述符集合:
创建一个文件描述符集合,用于指定你感兴趣的文件描述符。可以使用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏来操作文件描述符集合。
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds); // 将文件描述符fd1添加到集合中
FD_SET(fd2, &readfds); // 将文件描述符fd2添加到集合中
2.设置超时时间:
准备一个timeval结构体,指定select函数的超时时间,或设置为NULL表示没有超时限制。
struct timeval timeout;
timeout.tv_sec = 5; // 设置超时时间为5秒
timeout.tv_usec = 0;
3.调用select函数:
将文件描述符集合和超时时间作为参数传递给select函数,等待事件发生。
int numReady = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
if (numReady == -1) {// 处理错误
} else if (numReady == 0) {// 超时处理
} else {// 有事件发生// 遍历文件描述符集合,检查哪些文件描述符上有事件发生for (int fd = 0; fd <= maxfd; ++fd) {if (FD_ISSET(fd, &readfds)) {// 该文件描述符上有事件发生// 处理事件}}
}
select实现io多路复用的特点:
- 一个进程只能监听1024个文件描述符
- select每次唤醒都会轮询驱动下的poll函数,效率低,消耗资源
- select每次都会清空表,清空后需要将用户空间的表重新拷贝到内核空间,浪费时间(0-3g是用户态,3-4g是内核态)
练习1:检测终端输入事件(键盘 0),鼠标输入事件
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 20
int main(int argc, char const *argv[])
{int pd;pd = open("/dev/input/mouse0", O_RDONLY);if (pd < 0){perror("open mouse0 err.");return -1;}//1.create fd_set tablefd_set readfds, tempfds;FD_ZERO(&readfds); //清空//2.add care file descriptorFD_SET(0, &readfds);FD_SET(pd, &readfds);//注意参数//3.maxfdint maxfd = pd;char buf[N] = "";//4.Calling select functionswhile (1){//5.add tempfdstempfds = readfds;if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0){perror("select err.");return -1;}if (FD_ISSET(0, &tempfds)){fgets(buf, N, stdin);printf("key:%s", buf);}if (FD_ISSET(pd, &tempfds)){int ret = read(pd, buf, N);buf[ret] = '\0';printf("mouse:%s\n", buf);}}close(pd);return 0;
}
练习2:使用select可以实现tcp链接多个服务器
//使用IO多路实现tcp绑定多个服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>/* superset of previous */#define Port 1025
#define N 128int main(int argc, char const *argv[])
{char buf[N] = "";int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err.");return -1;}struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(Port);saddr.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(caddr);if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err.");return -1;}if (listen(sockfd, 10) < 0){perror("listen");return -1;}fd_set readfds, tempfds;FD_ZERO(&readfds);FD_SET(0, &readfds);FD_SET(sockfd, &readfds);int maxfd = sockfd;while (1) //循环位置注意下{tempfds = readfds;if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0){perror("select err.");return -1;}if (FD_ISSET(0, &tempfds)){fgets(buf, N, stdin);printf("%s", buf);}if (FD_ISSET(sockfd, &tempfds)){int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}else if (acceptfd == 0){perror("client exit");}printf("port:%d\n", ntohs(caddr.sin_port));printf("ip address:%s\n", inet_ntoa(caddr.sin_addr));}}close(sockfd);return 0;
}
poll
poll函数是一种多路复用的机制,用于同时监视多个文件描述符的状态(通过控制静态数组)
poll函数详解
int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数:struct pollfd *fds关心的文件描述符数组struct pollfd fds[N];nfds:个数timeout: 超时检测毫秒级的:如果填1000,1秒如果-1,阻塞struct pollfd {int fd; /* 检测的文件描述符 */short events; /* 检测事件 */short revents; /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */};事件: POLLIN :读事件POLLOUT : 写事件POLLERR:异常事件
使用步骤
1.创建并初始化pollfd结构数组:
poll函数使用一个名为struct pollfd的结构体数组来表示要监视的文件描述符以及监视的事件。每个结构体包含了一个文件描述符的信息和要监视的事件类型。可以通过创建并初始化一个pollfd结构体数组来准备监视的文件描述符。
2.设置要监视的文件描述符和事件:
对于每个要监视的文件描述符,设置其对应的文件描述符(fd字段)以及要监视的事件类型(events字段),如读事件(POLLIN)、写事件(POLLOUT)等。
3.调用poll函数:
使用创建好的pollfd结构体数组作为参数,调用poll函数来进行多路复用的操作。poll函数的原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-
- fds:指向pollfd结构体数组的指针。
- nfds:数组中要监视的文件描述符的数量。
- timeout:设置超时时间,以毫秒为单位。指定为-1表示无限等待,指定为0表示立即返回,指定为正整数表示等待的毫秒数。
4.检查poll函数的返回值:
poll函数返回时,会修改pollfd结构体数组中的revents字段,指示发生了哪些事件。可以通过检查revents字段来确定哪些文件描述符发生了事件。
5.处理文件描述符的事件:
根据revents字段的值,处理相应文件描述符发生的事件,如读事件或写事件。可以使用条件语句或循环结构来处理多个文件描述符的事件。
6.重复步骤2-5:
如果需要继续监视文件描述符的事件,可以重复执行步骤2-5,以实现多次的多路复用。
poll实现io多路复用的特点:
- 优化文件描述数个数限制,个数由程序员自己进行决定
- poll被唤醒后需要轮询一遍驱动下的poll函数,效率低,浪费cpu资源(在代码中能看出来遍历数组)
- 只需将用户空间的表拷贝一次到内核空间即可,不会清空文件描述符表
练习1:TCP实现多个服务器和客户端连接(基于poll实现)
//服务器端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{int socket_fd;socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket_fd < 0){perror("socket");return -1;}struct sockaddr_in ip;ip.sin_family = AF_INET;ip.sin_port = ntohs(1025);ip.sin_addr.s_addr = inet_addr("0.0.0.0");if(connect(socket_fd, (struct sockaddr *)&ip, sizeof(ip))<0){perror("connect");return -1;}char arr[128];int send_val;while (1){scanf("%s", arr);send_val = send(socket_fd, arr, sizeof(arr), 0);if (send_val < 0){perror("send");return -1;}else if (send_val == 0){printf("server is exit");break;}}close(socket_fd);return 0;
}//poll函数实现tcp处理服务器
//客户端#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <string.h>#define Port 1025
#define N 128
#define PollN 100
int main(int argc, char const *argv[])
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("perror socket");return -1;}struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(Port);saddr.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(caddr);if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err.");return -1;}if (listen(sockfd, 5) < 0){perror("listen err.");return -1;}//1.创建文件描述符表struct pollfd fds[PollN];memset(fds, 0, (sizeof(struct pollfd) * PollN));//2.将关心得文件描述符添加到表中fds[0].fd = 0;fds[0].events = POLLIN;fds[1].fd = sockfd;fds[1].events = POLLIN;int last = 1; //标记最大元素下标志char buf[N];while (1){ //3.调用poll函数if (poll(fds, last + 1, -1) < 0) //-1表示无限阻塞{perror("poll err");return -1;}//4.遍历结构体数组for (int i = 0; i <= last; i++){if (fds[i].revents == POLLIN) //fd是0得情况 //第二个会赋值第三个{if (fds[i].fd == 0){fgets(buf, N, stdin);printf("%s", buf);}else if (fds[i].fd == sockfd)//fd是sockfd情况{int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err.");return -1;}printf("port:%d\n", ntohs(caddr.sin_port));printf("Ipaddr:%s\n", inet_ntoa(caddr.sin_addr));/***********************************/last++;fds[last].fd = acceptfd;fds[last].events = POLLIN;}else//fd是acceptfd情况{size_t ret = recv(fds[i].fd, buf, N, 0);if (ret < 0){perror("recv err.");}else if (ret == 0){//若是对面服务器退出得处理perror("client exit.");close(fds[i].fd);/***************************************/fds[i] = fds[last];//把最后一个值直接拿过来替换就可,这里得数组不注重存储顺序i--;last--;break;}else //链接成功{printf("%s\n", buf);}}}}}close(sockfd);return 0;
}
epoll
epoll介绍
epoll 是一种事件驱动的 I/O 复用机制,用于高效地处理大量的文件描述符(sockets、文件等)的并发 I/O 操作。它在 Linux 操作系统中提供,用于替代旧的 select和 poll 系统调用。
注意:epoll是 Linux 特有的系统调用,无法在其他操作系统上直接使用。其他操作系统通常使用不同的机制,如 kqueue(BSD 系统)和 IOCP(Windows)来实现类似的功能。
epoll底层原理(了解)
认识红黑树

epoll底层原理和红黑树有关,先了解下红黑树(新手不必深究)。
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在每个节点上增加了一个额外的属性表示节点的颜色,可以是红色或黑色。红黑树满足以下性质:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 每个叶节点(NIL节点,空节点)都是黑色。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。
- 对于每个节点,从该节点到其子孙节点的所有路径上包含相同数量的黑色节点。
这些性质确保了红黑树的平衡性和高效性。由于红黑树是自平衡的,它的插入、删除和查找操作的时间复杂度都是对数时间 O(log n),其中 n 是树中节点的数量。
红黑树在很多编程语言的标准库中被广泛使用,特别适用于需要高效的插入和删除操作,并且需要保持有序性的场景。它常被用作实现映射(Map)和集合(Set)等数据结构的基础。
红黑树的算法相对复杂,包括了节点的插入、删除和旋转等操作。在实际应用中,通常使用现有的红黑树实现,而不需要手动实现它。许多编程语言和算法库都提供了红黑树的实现,可以直接使用这些库来获得红黑树的功能。
关键机制和数据结构
epoll 的底层原理涉及到 Linux 内核中的几个关键组件和数据结构。
- 事件表(Event table):epoll 使用一个事件表来存储待处理的事件和相关的文件描述符。事件表是一个红黑树(Red-Black Tree),用于快速查找和插入事件。每个事件项包含了文件描述符、事件类型以及用户定义的数据。
- 等待队列(Wait queue):epoll 通过等待队列来管理等待事件的进程或线程。等待队列是一个链表,其中的每个节点代表一个等待事件的进程或线程。当没有事件发生时,进程或线程会被加入到等待队列中,以便在事件就绪时唤醒。
- 文件描述符表(File descriptor table):内核维护着一个文件描述符表,用于跟踪和管理所有打开的文件描述符。每个文件描述符表项包含了文件描述符的状态、操作函数指针等信息。
- 内核事件结构体(Kernel event structure):内核使用一种特殊的数据结构来表示事件。这个结构体包含了事件的类型、文件描述符等信息。当一个事件发生时,内核会创建这个结构体,并将其插入到事件表中。
底层原理
- 创建 epoll 实例:通过调用 epoll_create 系统调用,内核会分配和初始化一个 epoll 实例,并返回一个文件描述符。
- 注册事件:使用 epoll_ctl 系统调用将感兴趣的文件描述符添加到 epoll 实例的事件表中。内核会将文件描述符相关的信息创建为一个内核事件结构体,并插入到事件表中。
- 等待事件:使用 epoll_wait 系统调用等待事件发生。当没有事件发生时,进程或线程会被放入等待队列中。当有事件发生时,内核会将相应的事件结构体标记为就绪,并唤醒等待队列中的进程或线程。
- 处理事件:进程或线程被唤醒后,可以通过 epoll_wait 返回的就绪事件列表,获取每个事件的文件描述符和事件类型。通过事件的回调函数处理相应的操作,如读取、写入等。
- 反复等待:重复执行步骤 3 和步骤 4,以实现事件的持续处理。
函数接口
epoll_create:创建红黑树
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建红黑树根节点参数:size:不作为实际意义值 >0 即可
返回值:成功时返回epoll文件描述符,失败时返回-1。
epoll_ctl:控制epoll函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll属性epfd:epoll_create函数的返回句柄。op:表示动作类型。有三个宏 来表示:EPOLL_CTL_ADD:注册新的fd到epfd中EPOLL_CTL_MOD:修改已注册fd的监听事件EPOLL_CTL_DEL:从epfd中删除一个fdFd:需要监听的fd。event:告诉内核需要监听什么事件EPOLLIN:表示对应文件描述符可读EPOLLOUT:可写EPOLLPRI:有紧急数据可读;EPOLLERR:错误;EPOLLHUP:被挂断;EPOLLET:触发方式,边缘触发;(默认使用边缘触发)ET模式:表示状态的变化;
返回值:成功时返回0,失败时返回-1
epoll_ctl涉及到的共用体和结构体
typedef union epoll_data {
void* ptr;(无效)
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;struct epoll_event {
uint32_t events; / * Epoll事件* /
epoll_data_t data; / *用户数据变量* /
};
epoll_wait:等待事件产生
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select的用法epfd:句柄;events:用来保存从内核得到事件的集合;maxevents:表示每次能处理事件最大个数;timeout:超时时间,毫秒,0立即返回,-1阻塞
成功时返回发生事件的文件描述个数,失败时返回-1
epoll实现io多路复用的特点:
- 监听的文件描述符个数无限制(取决于自己系统)
- 异步I/O,不需要轮询,使用callback(回调函数)直接拿到唤醒的文件描述符。
- epoll不需要重构文件描述表,只需将用户空间表拷贝到内核空间一次即可。
练习:使用epoll实现多个客户端和服务器进行连接
//使用epoll得方式完成多个客户端进行通信
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>int main(int argc, char const *argv[])
{char buf[128] = "";int socked = socket(AF_INET, SOCK_STREAM, 0);if (socked < 0){perror("socket err.");exit(-1);}struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(25535);saddr.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(caddr);if (bind(socked, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err.");exit(-1);}if (listen(socked, 10) < 0){perror("listen err.");exit(-1);}printf("listen is ok\n");//上面得代码都一样。下面开始引入epoll//1.创建一个表struct epoll_event event;struct epoll_event events[10];//epoll引入和红黑树得概念//>>1创建一颗树int epfd = epoll_create(1);//>>2添加关心得文件描述符到树中event.data.fd = 0; //添加标准输入event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);event.data.fd = socked;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, socked, &event);while (1){//>>3掉用epoll_wait等待事情发生int ret = epoll_wait(epfd, events, 10, -1); //-1表示不进行超时检测//epoll_wait返回事件得文件描述符个数if (ret < 0){perror("epoll_wait err.");exit(-1);}for (int i = 0; i < ret; i++){if (events[i].data.fd == 0){fgets(buf, 128, stdin);printf("stdin said:%s\n", buf);}else if (events[i].data.fd == socked){int accepted = accept(socked, (struct sockaddr *)&caddr, &len);if (accepted < 0){perror("accept err.");exit(-1);}printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), htons(caddr.sin_port));//链接成功后还得添加到树上event.data.fd = accepted;event.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, accepted, &event);}else //用户发送数据{int recvbyte = recv(events[i].data.fd, buf, sizeof(buf),0);if (recvbyte < 0){perror("recv err.");exit(-1);}else if (recvbyte == 0){printf("%d client exit\n", events[i].data.fd);close(events[i].data.fd);//下树操作epoll_ctl(epfd, EPOLL_CTL_DEL,events[i].data.fd, NULL);}else{printf("%d %s", events[i].data.fd, buf);}}}}close(socked);return 0;
}
//client
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void show(void);
void list_client(int sockfd, char *buf, int size);
void put_client(int sockfd, char *buf, int size);
void get_client(int sockfd, char *buf, int size);
int main(int argc, char const *argv[])
{//1.创建套接子int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err.");return -1;}//填充结构体struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(25535);saddr.sin_addr.s_addr = INADDR_ANY;if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("connect err.");return -1;}//收发消息pid_t pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0){char buf[128];while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n'){buf[strlen(buf) - 1] == '\0';}send(sockfd, buf, sizeof(buf), 0);}}else{char buf[128];int recvtype;while (1){recvtype = recv(sockfd, buf, sizeof(buf), 0);if (recvtype < 0){perror("recv err");return -1;}printf("%s\n", buf);}}close(sockfd);return 0;
}
select,poll,epoll伪代码
select伪代码
//函数原型select(nfds,&readfds,&writefds,&exportfds,&timeout) 1.fd_set readfds,tempfds;FD_ZERO(&readfds);2.FD_SET(0,&readfds)FD_SET(sockfd,&readfds);int maxfd=sockfd;3.while(1){tempfds=readfds;int ret=select(maxfd+1,&tempfds,NULL,NULL,NULL);4.if(FD_ISSET(0,&tempfds)){fgets()}if(FD_ISSET(sockfd,&tempfds)){acceptfd=accept();FD_SET(acceptfd,&readfds);if(maxfd < acceptfd)maxfd=acceptfd;}for(int i=4;i<=maxfd;i++){if(FD_ISSET(i,&tempfds)){recv();if(退出){close(i);FD_CLR(i,&readfds);if(i==maxfd)maxfd--;}}} }
poll伪代码
//函数原型int poll(struct pollfd *fds, nfds_t nfds, int timeout);1.struct pollfd fds[200]={};/*struct pollfd {int fd;short events;//检测事件 POLLIN POLLOUT short revents;//poll函数返回用于判断,无0,有revents=events}*/
2.fds[0].fd=0;fds[0].events=POLLIN;fds[1].fd=sockfd;fds[1].events=POLLIN;int last=1;3.while(1){int ret=poll(fds,last+1,-1);for(int i=0;i<=last;i++){if(fds[i].revents == fds[i].events){ if(fds[i].fd==0){}else if(fds[i].fd==sockfd){acceptfd=accept();last++;fds[last].fd=acceptfd;fds[last].events=POLLIN;}else{recv();if(退出){close(fds[i].fd);fds[i]=fds[last];last--;i--;}}}}}
epoll伪代码
int epfd=epoll_create(1)epoll_ctl(epfd,op,fd,&event)op:EPOLL_CTL_ADD EPOLL_CTL_DEL struct epoll_event{int events;xxx data.fd;}1.int epfd=epoll_create(1)2. struct epoll_event event;event.data.fd=0;event.evnets=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)event.data.fd=sockfd;event.evnets=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)3.while(1)struct epoll_event events[20];int ret=epoll_wait(epfd,events,20,-1);for(int i=0;i<ret;i++){//直接处理if(events[i].data.fd==0){}else if(events[i].data.fd==sockfd){acceptfd=accept();event.data.fd=acceptfd;event.evnets=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,acceptfd,&event)}else{recv();if(退出){close(events[i].data.fd);epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);}}}
相关文章:
网络编程 lesson5 IO多路复用
select 当需要在一个或多个文件描述符上等待事件发生时,可以使用select函数。 select函数是一个阻塞调用,它会一直等待,直到指定的文件描述符上有事件发生或超时。 select函数详解 int select(int nfds, fd_set *readfds, fd_set *writefd…...
码出高效_第一章 | 有意思的二进制表示及运算
目录 0与1的世界1.如何理解32位机器能够同时处理处理32位电路信号?2.如何理解负数的加减法运算3.溢出在运算中如何理解4.计算机种常用的存储单位及转换5.位移运算规则6.有趣的 && 和 & 浮点数1.定点小数(为什么会出现浮点数表示?…...
测试类型(单元、集成、系统或手动测试)
测试类型(单元、集成、系统或手动测试) 单元测试 单元是系统的单个组件,例如类或单个方法。孤立地测试单元称为单元测试。 优点:速度快/易控/易写 缺点:缺乏现实性/无法捕获所有错误(例如与其他组件或服务的交互) 单元…...
【笔试强训编程题】Day3.(字符串中找出连续最长的数字串 69385)和(数组中出现次数超过一半的数字 23271)
作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训编程题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!! 文章目录…...
学懂缓存雪崩,缓存击穿,缓存穿透仅需一篇,基于Redis讲解
在了解缓存雪崩、击穿、穿透这三个问题前,我们需要知道为什么我们需要缓存。在了解这三个问题后,我们也必须知道使用Redis时,如何解决这些问题。 所以我将按照"为什么我们需要缓存"、"什么是缓存雪崩、击穿、穿透"、&qu…...
Android 12.0SystemUI 状态栏下拉和通知栏始终居中
1.概述 在12.0的产品定制化开发中,在系统原生的SystemUI 状态栏下拉和通知栏,默认是根据手势的x 坐标的位置居中显示,但是如果太靠两边感觉不太好,下拉太靠边不太好看所以产品提出不管手势在哪里下滑 都要去下拉和通知栏居中显示 会比较好看些 下面就来实现这个需求 2.Sy…...
面向过程编程和面向对象编程的区别
目录 一、面向过程编程 举个栗子: 二、面向对象编程 继续举个栗子: 三、区别 面向过程编程和面向对象编程是两种不同的编程范式,它们在代码的组织和结构上有所不同。 一、面向过程编程 面向过程编程(Procedural Programmin…...
2023年数学与人工智能国际会议——火热征稿中~
会议简介 Brief Introduction 2023年数学与人工智能国际会议(CFMAI 2023) 会议时间:2023年9月22 -24日 召开地点:中国杭州 大会官网:www.cfmai.org 2023年数学与人工智能国际会议(CFMAI 2023)由中山大学主办,CoreShare科享学术交流…...
格式化数字的实用命令:numfmt
在 Linux 系统中,numfmt 是一个用于格式化数字的实用工具。它可以将数字转换为不同的表示方式,如十进制、二进制、字节单位等。本文将详细介绍 numfmt 命令的使用方法,并提供一些适合初学者的示例。 Numfmt 命令语法 numfmt 命令的基本语法如…...
传统的交叉熵函数如何通过平滑处理可以适用于多标签分类任务
传统的交叉熵损失函数通常用于多分类问题,而在多标签分类问题中,每个样本可能属于多个标签,因此需要使用一些新的技术来优化交叉熵损失函数。 一种常用的技术是标签平滑(Label Smoothing),它可以优化传统的…...
关于Netty的一些问题
1.Netty 是什么? Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于nio的,它封装了jdk的nio,让我们使用起来更加方法灵活。 2.Netty 的特点是什么? 高并发&…...
Java - ThreadLocal数据存储和传递方式的演变之路
Java - ThreadLocal数据存储和传递方式的演变之路 前言一. InheritableThreadLocal - 父子线程数据传递1.1 父子线程知识预热和 InheritableThreadLocal 实现原理1.2 InheritableThreadLocal 的诟病 二. TransmittableThreadLocal (TTL) 横空出世2.1 跨线程变量传递测试案例2.2…...
vuex三问
文章目录 一、什么是vuex?二、为什么使用vuex?三、如何使用vuex?1.首先安装vuex2.注册vue中3.实例化vuex的store4. 挂载在vue实例上5.在组件中就可以通过this.$store对vuex进行操作。 总结 一、什么是vuex? Vuex 是一个专为 Vue.…...
Selenium自动化测试(基于Java)
目录 一. 了解Selenium ✅1.1 概念 ✅1.2 作用 ✅1.3 特点 ✅1.4 工作原理 二. Selenium Java 环境搭建 ✅2.1 下载 Chrome 浏览器 ✅2.2 查看浏览器的版本 ✅2.3 下载浏览器驱动 ✅2.4 验证环境是否搭建成功 三. Selenium 常用 API ✅3.1 定位元素 ✅3.2 操作对象…...
【网页布局形式----浮动】
网页布局形式----浮动 css浮动:一、常见的三种网页布局形式:1.1 网页布局两大准则: 二 、浮动:2.1 浮动语法:2.2 浮动特性(重难点):浮动元素通常与标准流的父级元素搭配使用…...
人力资源管理的本质
文章目录 写在前面简述用人方面 写在前面 还没写完呢 这是个人理解,本人理工科出身,喜欢直来直去,理论化的知识,苦于市面上的人力书籍资料都不说人话,遂有此文刚入门,甚至没有系统的学习管理知识…...
[NOIP2015 提高组] 运输计划
题目链接 给定一棵树以及树上的 m m m 条通路,我们可以在树上选取一条边,将其权重置为 0 0 0,目标是 min 将某条边权重置 0 max 通路权重 . \min_{将某条边权重置 0}\max 通路权重. 将某条边权重置0minmax通路权重. 20pts(m1) 当…...
【GreendDao 】RxQuery根据指定条件查询,完成后处理UI逻辑
GreenDao 和 RxJava 结合使用可以更方便地处理数据查询和 UI 逻辑的交互。RxQuery 使得一次查询结果可以直接转化成 Observable,而通过 RxJava 的操作符,可以方便地完成异步查询和 UI 逻辑的交互。以下是一个根据指定条件查询数据,查询完成后…...
【C++】unordered_set 和 unordered_map 使用 | 封装
文章目录 1. 使用1. unordered_set的使用2. unordered_map的使用 2. 封装修改结构定义针对insert参数 data的两种情况复用 哈希桶的insertKeyOfT模板参数的作用 迭代器operator()beginendunordered_set对于 begin和end的复用unordered_map对于 begin和end的复用unordered_map中…...
C++环形缓冲区设计与实现:从原理到应用的全方位解析
C环形缓冲区设计与实现:从原理到应用的全方位解析 一、环形缓冲区基础理论解析(Basic Theory of Circular Buffer)1.1 环形缓冲区的定义与作用(Definition and Function of Circular Buffer)1.2 环形缓冲区的基本原理&…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...
