(二)多路io复用技术
1.select
2.poll
3.epoll
(1)定义
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
目前epoll是linux大规模并发网络程序中的热门首选模型。
epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
可以使用cat命令查看一个进程可以打开的socket描述符上限。
cat /proc/sys/fs/file-max
如有需要,可以通过修改配置文件的方式修改该上限值。
sudo vi /etc/security/limits.conf
在文件尾部写入以下配置,soft软限制,hard硬限制。如下所示。
* soft nofile 65536
* hard nofile 100000
(2)基础API
- 创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。
#include <sys/epoll.h>
int epoll_create(int size) size:监听数目
- 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
event: 告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
- 等待所监控文件描述符上有事件的产生,类似于select()调用。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
events: 用来存内核得到事件的集合,
maxevents: 告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout: 是超时时间
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
(3)epoll代码实现
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<sys/epoll.h>
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("too few argument...\n");
exit(1);
}
int port = atoi(argv[1]);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket");
exit(1);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sock_addr)&serv_addr, sizeof(serv_addr));
listen(lfd, 128);
printf("start to accrpt...\n");
struct sockaddr_in clit_addr;
socklen_t cli_len = sizeof(clit_addr);
int epfd = epoll_create(2000);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event all [2000];
while (1)
{
int ret = epoll_wait(epfd, all, sizeof(all)/all[0], -1);
for (int i = 0; i < ret; i++)
{
int fd = all[i].data.fd;
if (fd == lfd)
{
int cfd = accept(lfd, (struct sock_addr)&clit_addr; &cli_len);
if (cfd == -1)
{
perror("accept");
exit(1);
}
struct epoll_event temp;
temp.events = EPOLLIN;
temp.data.fd = cfd;
epoll_ctl(epfd; EPOLL_CTL_ADD, cfd, &temp);
char ip[16];
printf("new client IP: %S port: %d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, ip, 16)
ntohs(clit_addr.sin_port));
}
else
{
if (!(all[i] & EPOLLIN))
{
continue;
}
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
if (len = -1)
{
perror("read");
exit(1);
}
else if (len == 0)
{
printf("client disconnected...\n");
if (epoll_ctl(epfd, EPOLL_DEL, fd, NULL) == -1)
{
perror("epoll_ctl-del_err");
exit(1);
}
close(fd);
}
else
{
printf("recv buf:%s\n", buf);
write(fd, buf, len);
}
}
}
}
close(lfd);
return 0;
}