Linux I/O复用——epoll()
编程TWO 编程小兔崽 今天
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
1、epoll()
epoll()是Linux特有的I/O复用函数,它的实现与使用上和select()、poll()、有很大差异。
epoll()用一组函数来完成任务,而不是单个函数;其次,epoll()把文件描述放到内核事件表中,只需一个额外的文件描述符,来标识内核中唯一的这个事件表。
需要使用的API:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
需要使用的结构体信息:
typedef union epoll_data
{
void *ptr;
int fd; //一般情况下,都用的是这个文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
2、epoll_wait()
关键:对epoll_wait()函数的核心理解
(1)、返回值:事件表中就绪客户端的个数;
(2)、参数events:将事件表中的就绪客户端的信息放到了events数组中。
3、epoll()的核心思想
就是创建一个内核事件表,存放所监听客户端的套接字和当前的事件,在利用epoll_wait()函数查找就绪的套接字,最后经过增加、删除、修改利用epoll_ctl()函数进行;当然了,这其中还有一批搭配使用的宏;
3、代码实现
(1)、utili.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8787
#define LISTEN_QUEUE 5
#define SIZE 10
#define BUFFER_SIZE 256#include
#define OPEN_MAX 1000
#include
#define FDSIZE 1000
#define EPOLLEVENTS 100
(2)、ser.c
#include"../utili.h"
static int socket_bind(const char *ip, int port)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
//addrSer.sin_addr.s_addr = inet_addr(ip);
inet_pton(AF_INET, ip, &addrSer.sin_addr);
addrSer.sin_port = htons(port);
bind(listenfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
return listenfd;
}
static void add_event(int epollfd, int fd, int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}
static void delete_event(int epollfd, int fd, int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}
static void modify_event(int epollfd, int fd, int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}
static void handle_accept(int epollfd, int listenfd)
{
int clifd;
struct sockaddr_in addrCli;
socklen_t len = sizeof(struct sockaddr);
clifd = accept(listenfd, (struct sockaddr*)&addrCli, &len);
if(clifd != -1)
{
add_event(epollfd, clifd, EPOLLIN);
}
}
static void do_read(int epollfd, int fd, char *buf){
int nread = read(fd, buf, BUFFER_SIZE);
if(nread == -1)
{
close(fd);
delete_event(epollfd, fd, EPOLLIN);
}
else
{
printf("read msg:>%s\n",buf);
modify_event(epollfd, fd, EPOLLOUT);
}
}
static void do_write(int epollfd, int fd, char *buf){
int nwrite = write(fd, buf, strlen(buf)+1);
if(nwrite == -1)
{
close(fd);
delete_event(epollfd, fd, EPOLLOUT);
}
else
{
modify_event(epollfd, fd , EPOLLIN);
}
memset(buf, 0, BUFFER_SIZE);
}
static void handle_events(int epollfd, struct epoll_event *events, int num,
int listenfd, char *buf)
{
int i;
int fd;
for(i=0; i
{
fd = events[i].data.fd;
if((fd==listenfd) && (events[i].events&EPOLLIN)) //根据其结果分别进入三种状态
handle_accept(epollfd, listenfd); //申请与服务器连接
else if(events[i].events & EPOLLIN)
do_read(epollfd, fd, buf); //只读
else if(events[i].events & EPOLLOUT)
do_write(epollfd, fd, buf); //只写
}
}
static void do_epoll(int listenfd)
{
int ret;
char buffer[BUFFER_SIZE];
struct epoll_event events[EPOLLEVENTS];
int epollfd = epoll_create(FDSIZE);
add_event(epollfd, listenfd, EPOLLIN);
for(;;)
{
//select poll
ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
handle_events(epollfd, events, ret, listenfd, buffer);
}
close(epollfd);}int main(void)
{
int listenfd;
listenfd = socket_bind(SERVER_IP, SERVER_PORT);
listen(listenfd, LISTEN_QUEUE);
do_epoll(listenfd);
return 0;
}
(3)、cli.c
#include"../utili.h"
static void add_event(int epollfd, int fd, int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}
static void delete_event(int epollfd, int fd, int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);}static void modify_event(int epollfd, int fd, int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);}static void do_read(int epollfd, int fd, int sockfd, char *buf){
int nread = read(fd, buf, BUFFER_SIZE);
if(nread == -1)
{
close(fd);
delete_event(epollfd, fd, EPOLLIN);
}
else
{
if(fd == STDIN_FILENO)
add_event(epollfd, fd, EPOLLIN);
else
{
delete_event(epollfd, fd, EPOLLIN);
add_event(epollfd, STDOUT_FILENO, EPOLLOUT);
}
}
printf("Ser :>%s", buf);}static void do_write(int epollfd, int fd, int sockfd, char *buf){
int nwrite = write(fd, buf, strlen(buf)+1);
if(nwrite == -1)
{
perror("write");
close(fd);
}
else
{
if(fd == STDOUT_FILENO)
{
delete_event(epollfd, fd, EPOLLOUT);
}
else
{
modify_event(epollfd, fd, EPOLLIN);
}
}
memset(buf, 0, BUFFER_SIZE);}static void handle_events(int epollfd, struct epoll_event *events, int num,
int sockfd, char *buf)
{
int i;
int fd;
for(i=0; i
{
fd = events[i].data.fd;
if(events[i].events & EPOLLIN)
do_read(epollfd, fd, sockfd, buf);
else if(events[i].events, fd, sockfd, buf)
do_write(epollfd, fd, sockfd, buf);
}
}
static void handle_connection(int sockfd)
{
struct epoll_event events[EPOLLEVENTS];
int epollfd = epoll_create(FDSIZE);
add_event(epollfd, STDIN_FILENO, EPOLLIN);
int ret;
char buffer[BUFFER_SIZE];
for(;;)
{
ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
handle_events(epollfd, events, ret, sockfd, buffer);
}
close(epollfd);}int main(void)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
connect(sockfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
handle_connection(sockfd);
close(sockfd);
return 0;
}
运行结果
服务器端就是等待客户端的使用;
客户端1
客户端2
利用epoll()函数,不用轮询每个套接字,效率更高效一些;
Linux编程重点的知识也跟大家讲了一部分,接下来会跟大家分享进程之间的通信、线程之间的通信,这些也是平时面试和开发经常用到的知识,还有一个跟大家说一下,最近工作比较忙,项目比较紧,可能更新文章没有那么频繁,不过基本上每周都会更新至少3篇,谢谢大家的支持,加油每天进步一点点!
推荐阅读:
欢本文的朋友们,欢迎长按下图关注订阅号编程小兔崽,收看更多精彩内容
每天进步一点点,如果有用给小编点个赞