epoll函数
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
水平触发LT模式:
水平触发的主要特点是,如果用户在监听epoll事件,当内核有事件的时候,会拷贝
给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次
epoll_wait再次返回该事件。
这样如果用户永远不处理这个事件,就导致每次都会有该事件从内核到用户的拷
贝,耗费性能,但是水平触发相对安全,最起码事件不会丢掉,除非用户处理完
毕
边沿触发ET模式:
边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用
户处理还是不处理,以后将不会再通知。这样减少了拷贝过程,增加了性能,但是
相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。
边沿触发仅触发一次,水平触发会一直触发。
int epoll_create(int size);
功能:创建epoll
参数:size忽略不用
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
epoll的事件注册函数,epoll_ctl向 epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回–1,此时需要根据errno错误码判断错误类型。
它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
epoll_wait方法返回的事件必然是通过 epoll_ctl添加到 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; //32位整数
uint64_t u64; //64位整数
} epoll_data_t;
struct epoll_event
{
uint32_t events; //设置什么属性
epoll_data_t data; //用户数据
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。如果返回–1,则表示出现错误,需要检查 errno错误码判断错误类型。
第1个参数 epfd是 epoll的描述符。
第2个参数 events则是分配好的 epoll_event结构体数组,epoll将会把发生的事件复制到 events数组中(events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存。内核这种做法效率很高)。
第3个参数 maxevents表示本次可以返回的最大事件数目,通常 maxevents参数与预分配的events数组的大小是相等的。
第4个参数 timeout表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout为0,则表示 epoll_wait在 rdllist链表中为空,立刻返回,不会等待。
epoll的优点
1.支持一个进程打开大数目的socket描述符(fd)
2.IO效率不随fd数目增加而线性下降
3.使用mmap加速内核与用户空间的消息传递
4.内核微调
epoll代码
/*********************************************************************************
* Copyright: (C) 2023 Yangpeng
* All rights reserved.
*
* Filename: main.c
* Description: This file
*
* Version: 1.0.0(2023年01月10日)
* Author: Yangpeng <1023769078@qq.com>
* ChangeLog: 1, Release initial version on "2023年01月10日 19时51分51秒"
*
********************************************************************************/
#include <stdio.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sqlite3.h>
#include <syslog.h>
#include <libgen.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include <pthread.h>
#include "main.h"
#include "database.h"
#include "socket_server.h"
#include "sqlite3.h"
#define database_name "temperature.db"
#define table_name "temperature"
#define MAX_EVENTS 512
int g_sigstop = 0;
int main (int argc, char **argv)
{
int daemon_run=0;
int opt;
char *progname = NULL;
int log_fd=1;
struct sigaction sigact,sigign;
int listenfd,connfd;
int serv_port;
int serv_ip;
int epollfd;
struct epoll_event event;
struct epoll_event event_array[MAX_EVENTS];
int events;
char buf[512];
pack_info_t pack_info;
char *ptr=NULL;
struct option long_options[] =
{
{"daemon", no_argument, NULL, 'd'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
progname = basename(argv[0]);
/* Parser the command line parameters */
while ((opt = getopt_long(argc, argv, "dp:h", long_options, NULL)) != -1)
{
switch (opt)
{
case 'd':
daemon_run=1;
break;
case 'p':
serv_port = atoi(optarg);
break;
case 'h': /* Get help information */
print_usage(progname);
return EXIT_SUCCESS;
default:
break;
}
}
if( !serv_port )
{
print_usage(progname);
return -1;
}
//建立日志系统
log_fd=open("temperature.log", O_CREAT|O_RDWR, 0666);
if(log_fd < 0)
{
printf("Open the logfile failure: %s\n", strerror(errno));
return 0;
}
//标准输出、标准出错重定向
// dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
//安装信号
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigact.sa_handler = signal_stop;
sigemptyset(&sigign.sa_mask);
sigign.sa_flags = 0;
sigign.sa_handler = SIG_IGN;
sigaction(SIGINT, &sigact, 0);
sigaction(SIGPIPE, &sigign, 0);
sigaction(SIGTERM, &sigact, 0);
//检查数据库是否存在,如果存在返回0,不存在返回-1
printf("Check database existence\n");
if (access(database_name, W_OK) < 0)
{
if(create_database_table() < 0)//创建数据库和表
{
printf("Create database and table failure: %s\n", strerror(errno));
return -1;
}
}
printf("Create database and table successfully\n");
set_socket_rlimit(); /* set max open socket count */
listenfd=socket_server_init(NULL,serv_port);
if(listenfd<0)
{
printf("ERROR: %s server listen on port %d failure\n", argv[0],serv_port);
return -2;
}
printf("%s server start to listen on port %d\n", argv[0],serv_port);
//判断是否打开守护进程函数
if(daemon_run)
{
if(daemon(1, 1)<0)
{
printf("Running daemon failure:%s\n", strerror(errno));
return 0;
}
printf("Running daemon successfully!\n");
}
//创建epoll实例
epollfd=epoll_create(MAX_EVENTS);
if(epollfd<0)
{
printf("epoll_create() failure: %s\n", strerror(errno));
return -3;
}
event.events=EPOLLIN;
event.data.fd=listenfd;
//把listenfd加入epoll兴趣列表
if(epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&event)<0)
{
printf("epoll add listen socket failure: %s\n", strerror(errno));
return -4;
}
while(!g_sigstop)
{
//程序在这阻塞
printf(" program will blocked here\n");
events=epoll_wait(epollfd,event_array,MAX_EVENTS,-1);
if(events<0)
{
printf("epoll failure :%s\n",strerror(errno));
break;
}
else if(events==0)
{
printf("epoll get timeout\n");
continue;
}
//处理发生的事件
for(int i=0;i<events;i++)
{
if ( (event_array[i].events & EPOLLERR) || (event_array[i].events & EPOLLHUP) )
{
printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));
epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
}
if(event_array[i].data.fd==listenfd)
{
if((connfd=accept(listenfd,NULL,NULL))<0)/*不保存客户端信息(结构体指针为NULL)*/
{
printf("accept new client failure: %s\n", strerror(errno));
continue;
}
event.data.fd=connfd;
event.events=EPOLLIN;
if( epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) < 0 )
{
printf("epoll add client socket failure: %s\n", strerror(errno));
close(event_array[i].data.fd);
continue;
}
printf("epoll add new client socket[%d] ok.\n", connfd);
}
else
{
memset(buf,0,sizeof(buf));
if(read(event_array[i].data.fd,buf,sizeof(buf))<=0)
{
printf("socket[%d] read data failure or disconnect and will be remove.\n", event_array[i].data.fd);
epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
continue;
}
//分割字符串
ptr=strtok(buf,"/");
while(NULL!=ptr)
{
strncpy(pack_info.sn, ptr, sizeof(pack_info.sn));
ptr=strtok(NULL, "/");
strncpy(pack_info.datime, ptr, sizeof(pack_info.datime));
ptr=strtok(NULL, "/");
pack_info.temper=atof(ptr);
ptr=strtok(NULL, "/");
}
//保存到表中
data_insert(pack_info, table_name);
}
}
}
close(listenfd);
return 0;
}
void print_usage(char *progname)
{
printf("Usage: %s [OPTION]...\n", progname);
printf(" %s is a socket server program, which used to verify client and echo back string from it\n",progname);
printf("\nMandatory arguments to long options are mandatory for short options too:\n");
printf(" -d[daemon ] set program running on background\n");
printf(" -p[port ] Socket server port address\n");
printf(" -h[help ] Display this help information\n");
printf("\nExample: %s -d -p 8900\n", progname);
return ;
}
void signal_stop(int signum)
{
if(SIGTERM == signum)
{
printf("SIGTERM signal detected\n");
g_sigstop = 1;
}
}
/* Set open file description count to max */
void set_socket_rlimit(void)
{
struct rlimit limit = {0};
getrlimit(RLIMIT_NOFILE, &limit );
limit.rlim_cur = limit.rlim_max;
setrlimit(RLIMIT_NOFILE, &limit );
printf("set socket open fd max count to %ld\n", limit.rlim_max);
}
结果展示
想要更详细的代码可以看看 项目代码地址