网络编程 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 环形缓冲区的基本原理&…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
