1.多路IO复用
监测多个事件,一旦某个事件发生了,就进行相应的处理
select、poll、epoll就是实现了这个机制,但是实现一些区别
2.select、poll函数
这2者的差别不太,都有一些性能上的弊端,select函数是监测的文件描述符有限制(用数组保存需要监测的文件描述符),poll函数没有(用链表保存)
刚刚说到性能上的弊端
- 监测的文件描述符要在内核和用户空间之间拷贝,每一次事件处理之后需要重新设置关心事件,拷贝数据到内核
- 当事件发生时,返回的却是所有的文件描述符而不是就绪的描述符集合,增大了拷贝数据的量,以及遍历集合找到正确描述符的开销
3.epoll函数集
针对select、poll函数的缺点,实现性能更好的epoll函数集
主要在内核层增加一些监测文件描述符的数据和管理
改进:
- 没有频繁的数据拷贝,只需要设置一次,下一次就不需要重新设置,可以在已经设置好的基础上增减就行
- 返回的是就绪的描述符集合,减少了数据拷贝的量和遍历开销
4.使用场景
经过上面的比较,epoll函数集的实现完全是碾压select、poll,当前内核内存上消耗要大一点
epoll函数的改进是为了更好应对活跃的上万个连接请求的场景,请注意活跃2个字
你能想象一下,同时上万个活跃的链接,每一次的select都需要把上万个连接描述符进行内核和用户空间的拷贝是很大的开销
5.epoll使用实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1000
#define EPOLLEVENTS 100
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 int socket_bind(const char* ip, int port)
{
int listen_fd = 0;
struct sockaddr_in servaddr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listen_fd)
{
perror("socket error:");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (-1 == bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)))
{
perror("bind error:");
exit(1);
}
return listen_fd;
}
static void do_read(int epoll_fd, int fd, char* buf)
{
int nread = 0;
nread = read(fd, buf, MAXSIZE);
if (-1 == nread)
{
perror("read error");
close(fd);
delete_event(epoll_fd, fd, EPOLLIN);
}
else if (nread == 0)
{
fprintf(stderr, "client close\n");
close(fd);
delete_event(epoll_fd, fd, EPOLLIN);
}
else
{
printf("read message is : %s", buf);
modify_event(epoll_fd, fd, EPOLLOUT);
}
}
static void do_write(int epoll_fd, int fd, char* buf)
{
int nwrite = 0;
nwrite = write(fd, buf, strlen(buf));
if (-1 == nwrite)
{
perror("write error:");
close(fd);
delete_event(epoll_fd, fd, EPOLLOUT);
}
else
{
modify_event(epoll_fd, fd, EPOLLIN);
}
memset(buf, 0, MAXSIZE);
}
static void handle_accept(int epoll_fd, int listen_fd)
{
int client_fd = 0;
struct sockaddr_in client_addr;
socklen_t client_addr_len = 0;
client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (-1 == client_fd)
{
perror("accept error:");
}
else
{
printf("accept a new client: %s:%d\n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
add_event(epoll_fd, client_fd, EPOLLIN);
}
}
static void handle_events(int epoll_fd, struct epoll_event *events, int num, int listen_fd, char* buf)
{
int i = 0;
int fd = 0;
for (i = 0; i < num; i++)
{
fd = events[i].data.fd;
if ((fd == listen_fd) && (events[i].events & EPOLLIN))
{
handle_accept(epoll_fd, listen_fd);
}
else if (events[i].events & EPOLLIN)
{
do_read(epoll_fd, fd, buf);
}
else if (events[i].events & EPOLLOUT)
{
do_write(epoll_fd, fd, buf);
}
}
}
static void do_epoll(int listen_fd)
{
int ret = 0;
int epoll_fd = 0;
char buf[MAXSIZE];
struct epoll_event events[EPOLLEVENTS];
memset(buf, 0, MAXSIZE);
epoll_fd = epoll_create(FDSIZE);
add_event(epoll_fd, listen_fd, EPOLLIN);
while(1)
{
ret = epoll_wait(epoll_fd, events, EPOLLEVENTS, -1);
handle_events(epoll_fd, events, ret, listen_fd, buf);
}
close(epoll_fd);
}
int main(int argc, char** argv)
{
int listen_fd = 0;
listen_fd = socket_bind(IPADDRESS, PORT);
listen(listen_fd, LISTENQ);
do_epoll(listen_fd);
return 0;
}