
【1】epoll相关的系统调用
epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大差异。
首先,epoll使用一组函数来完成任务,而不是单个函数。
其次,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传人文件描述符集或事件集。
但是epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
epoll_create()
这个文件描述符使用如下epoll create函数来创建:
#include<sys/epoll.h> int epoll_create(int size)
其中size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。
返回值
该函数返回一个文件描述符,用作其他所有epoll系统调用的第一个参数,用来指定要访问的内核事件表。
epoll_ctl()
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数epfd是通过epoll_create创建的epoll实例
参数fd是将要被监视的文件描述符
参数op是epoll操作符
其中参数op有如下三个有效值如下
- EPOLL_CTL_ADD ,向epfd注册fd的上的event
- EPOLL_CTL_MOD,修改fd已注册的event
- EPOLL_CTL_DEL,从epfd上删除fd的event
参数event指定事件,他是一个epoll_event结构指针类型如下:
struct epoll_event { __uint32_t events; // epoll事件 epoll_data_t data; // 用户数据 };
其中events成员描述事件类型,epoll支持的事件类型和poll基本相同,唯一的区别就是在poll的事件类型前加上E代表epoll,如下:
事件 | 描述 |
---|---|
EPOLLIN | 有数据可读 |
EPOLLRDNORM | 有普通数据可读 |
EPOLLRDBAND | 有优先数据可读 |
EPOLLPRI | 有紧迫数据可读 |
EPOLLOUT | 写数据不会导致阻塞 |
EPOLLWRNORM | 写普通数据不会导致阻塞 |
EPOLLWRBAND | 写优先数据不会导致阻塞 |
EPOLLMSGSIGPOLL | 消息可用 |
data成员用于存储用户数据,其类型为epoll_data_t定义如下:
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;
epoll_data_t是一个联合体.
fd是我们最常用的成员,他指定事件所从属的目标文件描述符
ptr可指定与fd相关的用户数据。
返回值
epoll_ctl()执行成功时返回0,失败时返回-1,并置errno
epoll_wait()
//返回就绪事件描述符的个数 #include<sys/epoll.h> int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
epoll_wait()用于等待事件的就绪,类似于select()的调用。
参数events用来从内核得到事件的集合。
参数maxevents告诉内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)
返回值
该函数调用成功时返回需要处理的事件个数,如返回0表示已超时。
【2】代码示例
使用epoll实现多客户端服务器的交互
server.c
#include <stdio.h> #include <string.h> #include <assert.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAXFD 10 //向内核事件表epfd 中添加 新事件的文件描述符fd void epoll_add(int epfd, int fd) { // 设置epoll_event的结构成员 struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; //EPOLL_CTL_ADD添加新事件及描述符到内核事件表 if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) { perror("epoll_ctl add error\n"); } } void epoll_del(int epfd, int fd) { //从内核事件表中移除fd if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) { perror("epoll_ctl del error\n"); } } int create_sockfd(); int main() { int sockfd = create_sockfd(); assert(sockfd != -1); //创建内核事件表 int epfd = epoll_create(MAXFD); assert(epfd != -1); // 设置epoll_event的结构成员 struct epoll_event ev; ev.data.fd = sockfd; ev.events = EPOLLIN; epoll_add(epfd, sockfd); // 定义events数组存放就绪描述符 struct epoll_event events[MAXFD]; while (1) { /* epoll_wait返回的是前n个已经全就绪的文件描述符, 那么我们不用全部遍历,只遍历前n个就可以 超时时间设置为5秒 */ int n = epoll_wait(epfd, events, MAXFD, 5000); // epoll_wait() 调用失败 if (n == -1) { perror("epoll wait error!\n"); } else if (n == 0) { printf("time out!\n"); } // 只遍历前n个,因为内核已告诉我们前n个有就绪事件 else { int i = 0; for (; i < n; ++i) { int fd = events[i].data.fd; if (fd == -1) { continue; } // events 为内核为我们返回的就绪事件 if (events[i].events & EPOLLIN) { if (fd == sockfd) { struct sockaddr_in caddr; int len = sizeof(caddr); // 接收一个套接字已建立的连接,得到连接套接字connfd int connfd = accept(sockfd, (struct sockaddr*) & caddr, (socklen_t *)&len); if (connfd < 0) { continue; } printf("accept c=%d\n", connfd); // 将连接套接字connfd,添加到内核事件表中 epoll_add(epfd, connfd); } else { char buff[128] = { 0 }; // recv用来接收客端数据 int res = recv(fd, buff, 127, 0); if (res <= 0) { //不能先close,应该先调用epoll_del() //因为先调用close关闭了文件描述符后,再调用epoll_del() //内核就找不到所要从内核事件表中移除的文件描述符 epoll_del(epfd, fd); close(fd); printf("one client over\n"); continue; } printf("buff %d = %s\n", fd, buff); send(fd, "OK", 2, 0); } } } } } } int create_sockfd() { //创建监听套接字(socket描述符),指定协议族ipv4,字节流服务传输 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { return -1; } // socket专用地址信息设置 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"); //命名套接字,将socket专用地址绑定到socket描述符上 int res = bind(sockfd, (struct sockaddr*) & saddr, sizeof(saddr)); if (res == -1) { return -1; } // 创建监听队列 listen(sockfd, 5); return sockfd; }
client.c
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); 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) { char buff[128] = {0}; printf("Please Input:"); 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("RecvBuff:%s\n",buff); printf("\n"); } close(sockfd); }

