【网络socket】基于epoll获取温度上报服务器(服务器)

文章介绍了Linux的epoll机制,包括两种工作模式:LT(水平触发)和ET(边缘触发),以及epoll_create、epoll_ctl和epoll_wait等关键函数的用法。epoll提供了一种高效处理大量文件描述符的方法,优化了IO效率,尤其适合高并发的网络编程场景。代码示例展示了如何创建epoll实例,注册和管理事件,以及等待和处理事件。
摘要由CSDN通过智能技术生成

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);
}

结果展示

在这里插入图片描述
想要更详细的代码可以看看 项目代码地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值