1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > IO多路复用机制——select poll epoll

IO多路复用机制——select poll epoll

时间:2024-03-09 04:10:50

相关推荐

IO多路复用机制——select poll epoll

IO多路复用

IO复用方法select方法接口介绍检测键盘是否有数据并发处理多个TCP客户端 poll方法接口介绍实现TCP服务端的并发 epoll方法接口介绍实现TCP服务端的并发 LT和ET模式两种模式的区别ET模式编程流程

IO复用方法

I/O复用使得程序能同时监听多个文件描述符,这对于提高程序的性能至关重要;

网络程序在下列情况下需要使用I/O复用技术:

TCP服务器同时要处理监听套接字和连接套接字服务器要同时处理TCP请求和UDP请求程序要同时处理多个套接字客户端程序要同时处理用户输入和网络连接服务器要同时监听多个端口

以往我们服务器端需要并发处理多个客户端,会使用多进程或多线程的方式完成

当客户端与服务端连接完成accept(),随后服务端将套接字交给多线程处理,当有多个客户端就需要多个线程进行连接,当客户端数量巨大的时候,通过多线程的方式就不够好

IO复用的方法

selectpollepoll(Linux特有)

IO复用属于系统调用,举个例子:

假如我们有十个人等待去领取东西,如果东西一直没发下来,这十个人就一直在那里等待,而IO方法处理,就是只用一个人在这里等,当发现谁的东西来了,就告诉他,他在过来,这样只用一个人等待

假如我们有一个sockfd监听套接字,我们创建一个集合,将sockf`加入到集合中注册好读事件(连接),然后通过select、poll、epoll 来检测该集合中的描述符有没有事件产生,select执行后会阻塞住,直到有事件产生或者超时,select返回有几个描述符有事件产生

当有客户端去连接sockfd后,执行 accept() 产生一个新的描述符,这样集合就有了两个描述符,这时候如果select再次发现sockfd是有事件产生,select结束阻塞,再一次调用accept(),又产生一个新的描述符,原本每个线程在等待事件产生都会发生阻塞,而使用了select只需要阻塞select一个,也就只需要一个线程来处理大量的服务端与客户端

select方法

接口介绍

int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select系统调用;参数:描述符最大值+1,读事件集合结构体,写事件集合结构体,异常事件集合结构体,超时时间;

对于读事件,假如套接字描述符接收缓冲区有数据,读事件就绪

对于写事件,发生缓冲区有空间,写事件就绪

fd_set

下面就是fd_set的结构体,我们来分析一下

#define __FD_SETSIZE 1024typedef long int __fd_mask;#define __NFDBITS (8 * (int) sizeof (__fd_mask))typedef struct{#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set) ((set)->fds_bits)#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];# define __FDS_BITS(set) ((set)->__fds_bits)#endif} fd_set;

首先是一个长整型的数组,数组长度是[__FD_SETSIZE / __NFDBITS],前面是1024,长整型的字节长*8,也就是长整型有多少个位,也就是说结构体需要1024个位,在这里是通过一个位去存放一个描述符

按照偏移量来存放以及查找描述符,仅通过描述符来操作是很复杂的,所以系统提供了一些方法

FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fdFD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fdint FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置

对于上面提到过的第一个参数是描述符最大值+1,因为这是我们描述符的总数,也是我们需要关注的总数目,像上图所示,我们最大的描述符是7,所以只需要关注集合前8个位,后面的位我们并没有使用到

超时时间的结构体如下

struct timeval{long tv_sec; //秒数long tv_usec; // 微秒数};

当我们有是个套接字放在select,当select返回,我们如何知道哪些是有数据的,还是看上面的图,select()返回值为2,我们知道集合中有两个套接字就绪,具体是哪两个呢,当select返回后,我们只会保留有数据的套接字,其他都会被清零

int FD_ISSET(int fd, fd_set *fdset);测试 fdset 的位 fd 是否被设置

使用上面提到的FD_ISSET方法来判断哪些位有数据

检测键盘是否有数据

我们编写下面的代码,让select检测标准输入描述符

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<assert.h>#include<string.h>#include<sys/select.h>#include<sys/time.h>#define STDIN 0 //标准输入文件描述符int main(){int fd = STDIN;fd_set fdset;//集合while(1){FD_ZERO(&fdset);//清空集合FD_SET(fd,&fdset);//添加描述符struct timeval tv = {5,0};//五秒钟//创建select//参数://最大描述符+1//读事件集合//写事件集合//异常事件集合//超时时间int n = select(fd+1,&fdset,NULL,NULL,&tv);if( n == -1){printf("select error\n");continue;}else if( n == 0 ){printf("time out\n");continue;}else{if(FD_ISSET(fd,&fdset))//是否被设置{char buff[128] = {0};read(fd,buff,127);printf("read:%s\n",buff);}}}}

编译运行代码

当我们没有任何输入的时候,每隔五秒timeval时间结束就打印一个timeout

而我们输入数据后,会被读出来,然后代码阻塞在select的部分

并发处理多个TCP客户端

按照原本我们的方法,处理多个客户端需要使用多线程进行分配,这样开销较大,我们使用select只需要使用一个线程就可以与多个客户端进行通信

当有客户端与服务端连接时,我们就会通过accept()得到一个连接套接字,再有新客户端连接,就又会得到新的连接套接字

当我们将c,添加到select添加到集合,这时候,如果sockfd上有事件方式,我们就进行accept操作,如果是c上有事件就执行recv操作

左边是我们收集的描述符,当select返回了代表有事件产生,就用通过FD_ISSET()来检测具体是哪一个描述符上的事件

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<assert.h>#include<string.h>#include<sys/socket.h>#include<arpa/inet.h>#include<fcntl.h>#include<netinet/in.h>#include<sys/select.h>#include<sys/time.h>#define MAXFD 100void fds_init(int fds[]){int i = 0;for(;i<MAXFD;i++){fds[i] = -1;}}void fds_add(int fds[],int fd){int i =0;for(;i<MAXFD;i++){if(fds[i] == -1){fds[i] = fd;break;}}}void fds_del(int fds[],int fd){int i =0;for(;i<MAXFD;i++){if(fds[i] == fd){fds[i] = -1;break;}}}int create_socket();int main(){int sockfd = create_socket();assert(sockfd != -1);int fds[MAXFD];//创建数组存放套接字fds_init(fds);//初始化数组fds_add(fds,sockfd);//想数组添加描述符fd_set fdset;//创建集合while(1){FD_ZERO(&fdset);//清空集合int maxfd = -1;//找到描述符最大值int i = 0;for(;i<MAXFD;i++){if(fds[i]!=-1){FD_SET(fds[i],&fdset);//将描述符添加进集合if(maxfd < fds[i]){maxfd = fds[i];//最大的描述符}}}struct timeval tv = {5,0};int n = select(maxfd+1,&fdset,NULL,NULL,&tv);if(n == -1){printf("select error");continue;}else if( n == 0 ){printf("time out");continue;}else{int i = 0;for(;i<MAXFD;i++)//遍历套接字数组,辨别那个套接字产生事件{if(fds[i] == -1){continue;}if( FD_ISSET(fds[i],&fdset) )//是否被设置{if(fds[i] == sockfd )//判断是否监听套接字{struct sockaddr_in caddr;int len = sizeof(caddr);int c = accept(fds[i],(struct sockaddr*)&caddr,&len);if(c == -1){continue;}printf("accept c =%d\n",c);fds_add(fds,c);//得到新的描述符}else{char buff[128] = {0};int res = recv(fds[i],buff,127,0);if ( res <= 0)//对方关闭连接{close(fds[i]);fds_del(fds,fds[i]);//移除数组printf("one client over\n");}else{printf("buff(%d) = %s\n",fds[i],buff);send(fds[i],"ok",2,0);}}}}}}}int create_socket(){int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){return -1;}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res == -1){return -1;}res = listen(sockfd,5);if(res == -1){return -1;}return sockfd;}

我们写好客户端代码来演示一下

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>int main(){int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd!=-1);assert(sockfd != -1);struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);while(1){printf("input:\n");char buff[128] = {0};fgets(buff,128,stdin);if(strncmp(buff,"end", 3) == 0){break;}send(sockfd,buff,strlen(buff),0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff = %s\n",buff);}close(sockfd);}

运行查看

对于服务器,如果我们数据一次没有接受完会发生什么,我们修改代码来查看你一下,修改接收数据大小

当select返回后,我们对数据只有一次recv,如果没有读完,下次再次调用select依旧会报告有事件,即使客户端没有发送数据,因为我们的接收缓冲区数据没有收完

所以即使我们服务端没有一次收完客户端发来的数据,下一次执行select依旧可以报告事件,并且继续接收

poll方法

接口介绍

poll系统调用和select类似,也是在指定事件内轮询一定数量的文件描述符,以测试其中是否有就绪者

poll的原型如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:结构体指针,数组元素个数,超时时间(以毫秒为单位)超时返回0,失败返回-1,成功返回就绪文件描述符的总数;fds 参数是一个 struct pollfd 结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件,pollfd 结构体定义如下:

struct pollfd{int fd; // 文件描述符short events; // 注册的关注事件类型short revents; // 实际发生的事件类型,由内核填充};

poll与select相比,事件类型更多,能够支持的文件描述符数目更多

当我们有一个连接一开始的时候,接收缓冲区是空的,读事件没有就绪,而发送缓冲区也是空的,写事件一开始就是就绪的,等待对方发送数据后,读时间才会产生

实现TCP服务端的并发

通过帮助手册查看一下

poll的服务端代码与select服务端代码很是相似,不过select在使用数组存放文件描述符是自己定义的,而poll中存放描述符的数组是方法需要的结构体数组

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<assert.h>#include<string.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<poll.h>#define MAXFD 10void fds_init(struct pollfd fds[]){int i = 0;for(;i<MAXFD;i++){fds[i].fd = -1;//描述符fds[i].events = 0;//注册关注事件fds[i].revents = 0;//内核返回实际发生事件}}void fds_add(struct pollfd fds[],int fd){int i = 0;for(;i<MAXFD;i++){if(fds[i].fd == -1){fds[i].fd = fd;fds[i].events = POLLIN;//读事件fds[i].revents = 0;break;}}}void fds_del(struct pollfd fds[],int fd){int i = 0;for(;i<MAXFD;i++){if(fds[i].fd == fd){fds[i].fd = -1;fds[i].events = 0;fds[i].revents = 0;//内核没有返回事件 自然会返回0}}}int create_socket();int main(){int sockfd = create_socket();assert(sockfd != -1);struct pollfd fds[MAXFD];//存放描述符的数组fds_init(fds);//初始化fds_add(fds,sockfd);//添加描述符while(1){//创建poll//参数//结构体数组//描述符大小,1个有效,其余9个无效//超时间 5000毫秒int n = poll(fds,MAXFD,5000);//会阻塞住,直到某个描述符有事件产生或超时if(n == -1)//创建失败{continue;}else if(n == 0){printf("time out\n");}else{int i = 0;for(;i<MAXFD;i++){if( fds[i].fd == -1){continue;}if(fds[i].revents & POLLIN){if(fds[i].fd == sockfd)//判断是不是监听套接字{struct sockaddr_in caddr;int len = sizeof(caddr);int c = accept(fds[i].fd,(struct sockaddr*)&caddr,&len);if(c<0){continue;}printf("accept c = %d\n",c);fds_add(fds,c);//将新描述符添加进数组}else{char buff[128] = {0};int num = recv(fds[i].fd,buff,127,0);if(num <= 0)//连接关闭{close(fds[i].fd);fds_del(fds,fds[i].fd);//从数组删除述描述符printf("client close\n");continue;}printf("read(%d):%s\n",fds[i].fd,buff);send(fds[i].fd,"ok",2,0);}}//if(fds[i].revents & POLLOUT)//{}}}}}int create_socket(){int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){return -1;}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res == -1){return -1;}res = listen(sockfd,5);if(res == -1){return -1;}return sockfd;}

使用以前的客户端代码

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>int main(){int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd!=-1);assert(sockfd != -1);struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);while(1){printf("input:\n");char buff[128] = {0};fgets(buff,128,stdin);if(strncmp(buff,"end", 3) == 0){break;}send(sockfd,buff,strlen(buff),0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff = %s\n",buff);}close(sockfd);}

编译运行查看

实现了服务器并发处理多个客户端

epoll方法

接口介绍

epoll是Linux特有的I/O复用函数,它在实现和使用上与select、poll有很大差异,首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件描述符或者事件集,但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表

epoll相关函数如下:

epoll_create()用于创建内核事件表epoll_ctl()用于操作内核事件表epoll_wait()用于在一段超过时间内等待一组文件描述符上的事件

其各自的原型如下:

int epoll_create(int size);

创建内核事件表;参数:指定内核事件表的大小,返回内核事件表的文件描述符,失败返回-1,内核事件表的实现是红黑树,我们给的size并没有使用,只是做有效性的检测

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

控制内核时间表,添加、移除;参数:epoll_create的返回值,操作类型,需要操作的文件描述符,文件描述符上指定的事件

event参数指定事件,它是epoll_event结构指针类型,epoll_event的定义如下:

struct epoll_event{_uint32_t events; // epoll 事件epoll_data_t data; // 用户数据};

其中, events 成员描述事件类型, epoll 支持的事件类型与 poll 基本相同,表示epoll 事件的宏是在 poll 对应的宏前加上‘E’ ,比如 epoll 的数据可读事件是EPOLLIN。但是 epoll 有两个额外的事件类型–EPOLLET 和 EPOLLONESHOT。data 成员用于存储用户数据,是一个联合体,其定义如下:

typedef union epoll_data{void *ptr;int fd;uint32_t u32;uint64_t u64;}epoll_data_t;

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

select与poll使用的都是轮询的方法来检测事件,也就是通过挨个去检查的方式,而epoll是在每个描述符上注册回调函数,效率更高,是适合处理大量描述符的方法

实现TCP服务端的并发

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<assert.h>#include<string.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/epoll.h>#define MAXFD 10void epoll_add(int epfd,int fd){struct epoll_event ev;//事件类型ev.events = EPOLLIN;//读事件ev.data.fd = fd;//描述符//添加描述符//参数://内核事件表文件描述符//操作类型//添加的文件描述符//指定事件if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1){perror("epoll ctl add err\n");}}void epoll_del(int epfd, int fd){if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1){perror("epoll ctl del err\n");}}int create_socket();int main(){int sockfd = create_socket();assert(sockfd != -1);//创建内核事件表int epfd = epoll_create(MAXFD);//仅需要一个参数,事件可以存放更多数据assert(epfd != -1);//向内核事件表添加文件描述符epoll_add(epfd,sockfd);//内核检测获取就绪描述符,内核将就绪描述符给到我们提供的空间中struct epoll_event evs[MAXFD];//一次最多输10个,但是可以循环输入while(1)//不断检测{//获取就绪描述符//参数://内核事件表//给定的空间//最多返回的数量//超时时间 5000毫秒int n = epoll_wait(epfd,evs,MAXFD,5000);if( n == -1){perror("epoll wait err\n");continue;}else if( n == 0 ){printf("time out\n");continue;}else{//select 或 poll 就要遍历处理int i = 0;for(;i<n;i++)//只需要处理前n个{int fd = evs[i].data.fd;if(evs[i].events & EPOLLIN)//判断是否与读时间{if(fd == sockfd){struct sockaddr_in caddr;int len = sizeof(caddr);int c = accept(fd,(struct sockaddr*)&caddr,&len);if(c < 0){continue;}printf("accept c = %d\n",c);epoll_add(epfd,c);//添加进内核事件表}else{char buff[128] = {0};int res = recv(fd,buff,127,0);if(res <= 0){epoll_del(epfd,fd);//从内核事件表删除close(fd);printf("client close\n");continue;}printf("read(%d): %s",fd,buff);send(fd,"ok",2,0);}}//if(evs[i].event & EPOLLOUT)//{}}}}}int create_socket(){int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){return -1;}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res == -1){return -1;}res = listen(sockfd,5);if(res == -1){return -1;}return sockfd;}

客户端代码依旧使用的之前的

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>int main(){int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd!=-1);assert(sockfd != -1);struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res != -1);while(1){printf("input:\n");char buff[128] = {0};fgets(buff,128,stdin);if(strncmp(buff,"end", 3) == 0){break;}send(sockfd,buff,strlen(buff),0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff = %s\n",buff);}close(sockfd);}

LT和ET模式

epoll 对文件描述符有两种操作模式: LT(Level Trigger,电平触发)模式和 ET(EdgeTrigger,边沿触发)模式。 LT 模式是默认的工作模式。当往 epoll 内核事件表中注册一个文件描述符上的 EPOLLET 事件时, epoll 将以高效的 ET 模式来操作该文件描述符。

LT模式:当某个描述符上有事件就绪,如果我们没有将这个事件处理完,或没有处理,那么下一轮I/O函数时会继续提醒我们,直到事件处理完成ET模式:当某个描述符上有事件就绪,会通知应用程序有事件产生,不管用户处理或没处理都不会再次通知,除非此事件第二次产生才会继续通知

根据上面的代码可以看出,我们的epoll默认就处于LT模式,我们修改原本的代码来验证一下

这就是我们说的LT模式

现在我们修改代码,开启ET模式

我们目前只改动这一出编译运行查看

两种模式的区别

我们只读了一次数据,并且第二轮函数它并没有提醒我们没有接收完成,当我们发送第二次数据,会将缓冲区的下一个字节读取出来,而不是我们第二次发送的数据,所以ET模式会在描述符事件就绪后,只提醒一次,要求我们用户需要一次将数据处理完成

当收到数据,LT模式会在数据发送到达持续到缓冲区的数据处理完成,在其中的任何一刻检测都会检测到读事件发生,代表数据没有被处理完成

ET模式我们称之为高效模式或者边缘触发,当数据发送达到后,会提醒我们一次读事件产生,直到等到下一次数据发送到达才会再次提醒事件产生

ET模式编程流程

我们原本的策略是,IO函数去检测,当发现描述符上有事件就绪,我们去处理,然后再去检测再去处理,我们不能绕过IO函数去处理事件

若我们想要一次在ET模式通过循环一次将数据读完,会有两个问题(读事件为例):

我们不知道什么时候可以把数据读完如果我们将数据读完,我们再recv会被阻塞

所以这个时候,我们需要将描述符设置为非阻塞模式,若描述符上事件未就绪,我们进行操作会返回失败,而非阻塞

我们根据之前的epoll代码进行修改

首先将描述符修改为非阻塞模式,

查看帮助手册,fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性

添加设置非阻塞模式代码

在添加描述符方法中调用

第二步设置循环读取

我们引用错误码全局变量#include <errno.h>进行比较,来确定recv错误是因为描述符接收缓冲区未空

我们修改了此处的代码

编译执行查看

完整代码

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<assert.h>#include<string.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/epoll.h>#include<fcntl.h>#include<errno.h>#define MAXFD 10void setnonblock(int fd)//设置非阻塞{int oldfl = fcntl(fd,F_GETFL);//获取原本属性int newfl = oldfl | O_NONBLOCK;//添加非阻塞属性if( fcntl(fd,F_SETFL,newfl) == -1 ){perror("fcntl error\n");}}void epoll_add(int epfd,int fd){struct epoll_event ev;//事件类型ev.events = EPOLLIN | EPOLLET;//读事件ev.data.fd = fd;//描述符setnonblock(fd);//添加描述符//参数://内核事件表文件描述符//操作类型//添加的文件描述符//指定事件if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1){perror("epoll ctl add err\n");}}void epoll_del(int epfd, int fd){if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1){perror("epoll ctl del err\n");}}int create_socket();int main(){int sockfd = create_socket();assert(sockfd != -1);//创建内核事件表int epfd = epoll_create(MAXFD);//仅需要一个参数,事件可以存放更多数据assert(epfd != -1);//向内核事件表添加文件描述符epoll_add(epfd,sockfd);//内核检测获取就绪描述符,内核将就绪描述符给到我们提供的空间中struct epoll_event evs[MAXFD];//一次最多输10个,但是可以循环输入while(1)//不断检测{//获取就绪描述符//参数://内核事件表//给定的空间//最多返回的数量//超时时间 5000毫秒int n = epoll_wait(epfd,evs,MAXFD,5000);if( n == -1){perror("epoll wait err\n");continue;}else if( n == 0 ){printf("time out\n");continue;}else{//select 或 poll 就要遍历处理int i = 0;for(;i<n;i++)//只需要处理前n个{int fd = evs[i].data.fd;if(evs[i].events & EPOLLIN)//判断是否与读时间{if(fd == sockfd){struct sockaddr_in caddr;int len = sizeof(caddr);int c = accept(fd,(struct sockaddr*)&caddr,&len);if(c < 0){continue;}printf("accept c = %d\n",c);epoll_add(epfd,c);//添加进内核事件表}else{//recv 若失败原因为描述符接受缓冲区为空而失败while(1){char buff[128] = {0};int res = recv(fd,buff,1,0);//一次只读一个字节if( res == -1 ){printf("errno:%d",errno);//判断失败码 是否代表缓冲区为空if(errno != EAGAIN && errno != EWOULDBLOCK){perror("recv err\n");}else{send(fd,"ok",2,0);}break;}else if(res == 0){epoll_del(epfd,fd);close(fd);printf("client close\n");break;}else{printf("recv(%d):%s\n",fd,buff);}}}}//if(evs[i].event & EPOLLOUT)//{}}}}}int create_socket(){int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){return -1;}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res == -1){return -1;}res = listen(sockfd,5);if(res == -1){return -1;}return sockfd;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。