海南住房和城乡建设厅网站首页,网页标准化对网站开发维护所有者的好处,论述电子商务网站建设的流程,wordpress调用产品图片select
当需要在一个或多个文件描述符上等待事件发生时#xff0c;可以使用select函数。
select函数是一个阻塞调用#xff0c;它会一直等待#xff0c;直到指定的文件描述符上有事件发生或超时。
select函数详解 int select(int nfds, fd_set *readfds, fd_set *writefd…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 超时检测毫秒级的如果填10001秒如果-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资源在代码中能看出来遍历数组只需将用户空间的表拷贝一次到内核空间即可不会清空文件描述符表
练习1TCP实现多个服务器和客户端连接基于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 特有的系统调用无法在其他操作系统上直接使用。其他操作系统通常使用不同的机制如 kqueueBSD 系统和 IOCPWindows来实现类似的功能。
epoll底层原理了解
认识红黑树 epoll底层原理和红黑树有关先了解下红黑树新手不必深究。
红黑树Red-Black Tree是一种自平衡的二叉搜索树它在每个节点上增加了一个额外的属性表示节点的颜色可以是红色或黑色。红黑树满足以下性质
每个节点要么是红色要么是黑色。根节点是黑色。每个叶节点NIL节点空节点都是黑色。如果一个节点是红色的则它的两个子节点都是黑色的。对于每个节点从该节点到其子孙节点的所有路径上包含相同数量的黑色节点。
这些性质确保了红黑树的平衡性和高效性。由于红黑树是自平衡的它的插入、删除和查找操作的时间复杂度都是对数时间 O(log n)其中 n 是树中节点的数量。
红黑树在很多编程语言的标准库中被广泛使用特别适用于需要高效的插入和删除操作并且需要保持有序性的场景。它常被用作实现映射Map和集合Set等数据结构的基础。
红黑树的算法相对复杂包括了节点的插入、删除和旋转等操作。在实际应用中通常使用现有的红黑树实现而不需要手动实现它。许多编程语言和算法库都提供了红黑树的实现可以直接使用这些库来获得红黑树的功能。
关键机制和数据结构
epoll 的底层原理涉及到 Linux 内核中的几个关键组件和数据结构。
事件表Event tableepoll 使用一个事件表来存储待处理的事件和相关的文件描述符。事件表是一个红黑树Red-Black Tree用于快速查找和插入事件。每个事件项包含了文件描述符、事件类型以及用户定义的数据。等待队列Wait queueepoll 通过等待队列来管理等待事件的进程或线程。等待队列是一个链表其中的每个节点代表一个等待事件的进程或线程。当没有事件发生时进程或线程会被加入到等待队列中以便在事件就绪时唤醒。文件描述符表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失败时返回-1epoll_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阻塞
成功时返回发生事件的文件描述个数失败时返回-1epoll实现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.hint 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(clientip%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.hvoid 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;
}selectpollepoll伪代码
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 maxfdsockfd;3.while(1){tempfdsreadfds;int retselect(maxfd1,tempfds,NULL,NULL,NULL);4.if(FD_ISSET(0,tempfds)){fgets()}if(FD_ISSET(sockfd,tempfds)){acceptfdaccept();FD_SET(acceptfd,readfds);if(maxfd acceptfd)maxfdacceptfd;}for(int i4;imaxfd;i){if(FD_ISSET(i,tempfds)){recv();if(退出){close(i);FD_CLR(i,readfds);if(imaxfd)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有reventsevents}*/
2.fds[0].fd0;fds[0].eventsPOLLIN;fds[1].fdsockfd;fds[1].eventsPOLLIN;int last1;3.while(1){int retpoll(fds,last1,-1);for(int i0;ilast;i){if(fds[i].revents fds[i].events){ if(fds[i].fd0){}else if(fds[i].fdsockfd){acceptfdaccept();last;fds[last].fdacceptfd;fds[last].eventsPOLLIN;}else{recv();if(退出){close(fds[i].fd);fds[i]fds[last];last--;i--;}}}}}epoll伪代码
int epfdepoll_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 epfdepoll_create(1)2. struct epoll_event event;event.data.fd0;event.evnetsEPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,0,event)event.data.fdsockfd;event.evnetsEPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,event)3.while(1)struct epoll_event events[20];int retepoll_wait(epfd,events,20,-1);for(int i0;iret;i){//直接处理if(events[i].data.fd0){}else if(events[i].data.fdsockfd){acceptfdaccept();event.data.fdacceptfd;event.evnetsEPOLLIN|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);}}}