Epoll编程
- 解决select的数量限制
- 使用与高并发 ,例如c10k
epoll用作处理大量文字描述符,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
epoll结构体
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 */
};
#include<sys/epoll.h>
epoll接口
- int epoll_create(int size)
这个函数创建一个 epoll 的句柄,
size 用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好 epoll 句柄后,
它会一直占用一个文件描述符,直到调用 close() 关闭才会释放
注册函数
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
这个函数是 epoll 的事件注册函数,相当于告诉 epoll 你要监测什么样的事件。
- 第一个 参数是 epoll_create() 函数创建的句柄
- 第二个 参数表示动作,用三个宏来表示:
- EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
- EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;
- EPOLL_CTL_DEL:从 epfd 中删除一个fd;
- 第三个 参数是需要监听的 fd;
- 第四个 参数是告诉内核需要监听什么事, struct epoll_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 */
};```
events 可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读(包括对端正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将 EPOLL 设为边缘触发 (Edge Triggered) 模式,这是相对于水平触发 (Level Triggered) 来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
## 收集在epoll监控的事件中已经发送的事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
* 参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
> 等待事件的产生,类似于 select() 调用。参数 events 用来从内核得到事件的集合,
> maxevents 告诉内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的 size ,
> 参数 timeout 是超时时间(单位:毫秒, 0 会立即返回, -1 会一直阻塞)。该函数返回需要处理的事件数目,如返回 0 表示已超时。
## 原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销
## demo
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>
int main(int argc, char** argv)
{
int ep = epoll_create(1024);
if (ep < 0) {
printf(“epoll_create failed\n”);
return 1;
}
epoll_event event;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
//添加读事件
int status = epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if (status < 0) {
printf("epoll_ctl failed\n");
return 1;
}
epoll_event events[1024];
for (;;) {
int nsd = epoll_wait(ep, events, 1024, -1);
if (nsd > 0) {
for (int i = 0; i < nsd; i++) {
if (events[i].data.fd == STDIN_FILENO) {
char buf[100] = {0};
read(events[i].data.fd, buf, 100);
printf("STDIN: %s\n", buf);
}
}
}
}
return 0;
}
* demo server
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);
if(opts < 0) {
perror("fcntl(sock, GETFL)");
exit(1);
}
opts = opts | O_NONBLOCK;
if(fcntl(sock, F_SETFL, opts) < 0) {
perror("fcntl(sock, SETFL, opts)");
exit(1);
}
}
int main(int argc, char *argv[])
{
printf(“epoll socket begins.\n”);
int i, maxi, listenfd, connfd, sockfd, epfd, nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
struct epoll_event ev, events[20];
epfd = epoll_create(256);
struct sockaddr_in client addr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd);
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = "127.0.0.1";
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for(; ;) {
nfds = epoll_wait(epfd, events, 20, 500);
for(i = 0; i < nfds; ++i) {
if(events[i].data.fd == listenfd) {
printf("accept connection, fd is %d\n", listenfd);
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
if(connfd < 0) {
perror("connfd < 0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
printf("connect from %s\n", str);
ev.data.fd = connfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else if(events[i].events & EPOLLIN) {
if((sockfd = events[i].data.fd) < 0) continue;
if((n = read(sockfd, line, MAXLINE)) < 0) {
if(errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else {
printf("readline error");
}
} else if(n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
printf("received data: %s\n", line);
ev.data.fd = sockfd;
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
else if(events[i].events & EPOLLOUT) {
sockfd = events[i].data.fd;
write(sockfd, line, n);
printf("written data: %s\n", line);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
}
client
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUF 1024
/*********************************************************************
- filename: select-client.c
- 演示网络异步通讯,这是客户端程序
*********************************************************************/
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -1;
if (argc != 3)
{
printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]);
exit(0);
}
// 创建一个 socket 用于 tcp 通信
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
exit(errno);
}
// 初始化服务器端(对方)的地址和端口信息
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0)
{
perror(argv[1]);
exit(errno);
}
// 连接服务器
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(errno);
}
printf("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
while (1)
{
// 把集合清空
FD_ZERO(&rfds);
// 把标准输入句柄0加入到集合中
FD_SET(0, &rfds);
maxfd = 0;
// 把当前连接句柄sockfd加入到集合中
FD_SET(sockfd, &rfds);
if (sockfd > maxfd)
maxfd = sockfd;
// 设置最大等待时间
tv.tv_sec = 3;
tv.tv_usec = 0;
// 开始等待
retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
printf("将退出,select出错! %s", strerror(errno));
break;
}
else if (retval == 0)
{
/* printf("没有任何消息到来,用户也没有按键,继续等待……\n"); */
continue;
}
else
{
if (FD_ISSET(sockfd, &rfds)) // 连接的socket上有消息到来则接收对方发过来的消息并显示
{
bzero(buffer, MAXBUF + 1);
// 接收对方发过来的消息,最多接收 MAXBUF 个字节
len = recv(sockfd, buffer, MAXBUF, 0);
if (len > 0)
{
printf("接收消息成功:'%s',共%d个字节的数据\n", buffer, len);
}
else
{
if (len < 0)
{
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
}
else
{
printf("对方退出了,聊天终止!\n");
}
break;
}
}
if (FD_ISSET(0, &rfds)) // 用户按键了,则读取用户输入的内容发送出去
{
bzero(buffer, MAXBUF + 1);
fgets(buffer, MAXBUF, stdin);
if (!strncasecmp(buffer, "quit", 4))
{
printf("自己请求终止聊天!\n");
break;
}
// 发消息给服务器
len = send(sockfd, buffer, strlen(buffer) - 1, 0);
if (len < 0)
{
printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buffer, errno, strerror(errno));
break;
}
else
{
printf("消息:%s\t发送成功,共发送了%d个字节!\n", buffer, len);
}
}
}
}
// 关闭连接
close(sockfd);
return 0;
}
---------
会有个别错字或字母