【socket】基于poll和epoll通信温度上报
poll函数
poll是Linux中的字符设备驱动中的一个函数,poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
pollfd:指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。
nfds 指定数组中监听的元素个数.
timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()
一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
下表列出指定 events 标志以 及测试 revents 标志的一些常值:
timeout:该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0; 失败时,poll()返回-1,并设置errno为下列值之一:
EBADF 一个或多个结构体中指定的文件描述符无效。
EFAULTfds 指针指向的地址超出进程的地址空间。
EINTR 请求的事件之前产生一个信号,调用可以重新起。
EINVALnfds 参数超出PLIMIT_NOFILE值。
ENOMEM 可用内存不足,无法完成请求。
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的增删改查
参数:
epfd是epoll_create返回值
op是用来指定需要执行的操作
fd:指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符
ev:的指针结构体
op指定操作的值:添加,删除,修改对fd的监听
EPOLL_CTL_ADD | 添加fd到epoll |
---|---|
EPOLL_CTL_MOD | 修改已经注册fd的事件 |
EPOLL_CTL_DE | epfd删除一个fd |
结构体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; //用户数据
};
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
功能:
等待就绪事件
参数:
epfd是epoll_create()的返回值
evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;
maxevents指定所evlist数组里包含的元素个数;
timeout用来确定epoll_wait()的阻塞行为,有如下几种:
如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生或者直到捕获到一个信号为止。
如果timeout等于0,执行一次非阻塞式地检查,看兴趣列表中的描述符上产生了哪个事件。
如果timeout大于0,调用将阻塞至多timeout毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止
events中的值如下所示:
常量 | 说明 | 作为 epoll_ctl()的输入 | 作为epoll_wait()的返回 |
---|---|---|---|
EPOLLIN | 可读取非高优先级数据 | 能 | 能 |
EPOLLPRI | 可读取高优先级数据 | 能 | 能 |
EPOLLRDHUP | socket对端关闭 | 能 | 能 |
EPOLLOUT | 普通数据可写 | 能 | 能 |
EPOLLET | 采用边沿触发事件通知 | 能 | |
EPOLLONESHOT | 在完成事件通知之后禁用检查 | 能 | |
EPOLLERR | 有错误发生 | 能 | |
POLLHUP | 出现挂断 | 能 |
epoll的优点
- 支持一个进程打开大数目的socket描述符(fd)
- IO效率不随fd数目增加而线性下降
- 使用mmap加速内核与用户空间的消息传递
- 内核微调
poll代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/select.h>
#include <ctype.h>
#include <libgen.h>
#include <poll.h>
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))//求数组元素的个数
int socket_Server_init(char *listen_ip,int listen_port);
void sqlite_tem(char *buf);
void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-p(--port): sepcify server listen port.\n");
printf("-h(--Help): print this help information.\n");
printf("-d(--daemon):set program running on background\n");
return ;
}
int main (int argc, char **argv)
{
int listenfd = -1;
int clifd;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
socklen_t len;
int serv_port = 0;
int ch;
int rv ;
int on = 1;
char buf[1024];
int i,j;
int found;
int max;
int daemon_run = 0;
char *progname = NULL;
struct pollfd fds_arry[1024];
struct option opts[] =
{
{"daemon",no_argument,NULL,'b'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
progname = basename(argv[0]);
while( (ch=getopt_long(argc, argv, "bp:h", opts, NULL)) != -1 )
{
switch(ch)
{
case 'p':
serv_port=atoi(optarg);
break;
case 'b':
daemon_run=1;
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if( !serv_port )
{
print_usage(argv[0]);
return 0;
}
if( (listenfd = socket_Server_init(NULL, serv_port)) <0)
{
printf("ERROR %s server listen on port %d failure\n",argv[0],serv_port);
return -1;
}
printf("%s server start to listen on port %d\n",argv[0],serv_port);
if(daemon_run)//设置进程后台运行
{
daemon(0,0);
}
for(i=0; i<ARRAY_SIZE(fds_arry); i++)//遍历数组
{
fds_arry[i].fd=-1;//将整个数组初始化为-1,为什么是-1;因为这个数组存放的是文件描述符,系统会生成三个文件描述符0,1,2
}
fds_arry[0].fd = listenfd;//将第一个数组赋值为listenfd
//一个tcp的网络链接中包含一个四元组:源ip,目的ip,源端口,目的端口
fds_arry[0].events = POLLIN;
max = 0;
for( ; ; )
{
rv = poll(fds_arry, max+1, -1);
if(rv < 0)
{
printf("POLL failure:%s\n",strerror(errno));
break;
}
else if( rv ==0 )
{
printf("poll get timeout\n");
continue;
}
if( fds_arry[0].revents & POLLIN )//判断指定描述符是否在集合中
{
if( (clifd=accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0)
{
printf("accept new client failure:%s\n",strerror(errno));
continue;
}
found = 0;
for(i=0; i<ARRAY_SIZE(fds_arry);i++)
{
if( fds_arry[i].fd< 0 )
{
printf("accept new client [%d] and add it into array\n",clifd);
fds_arry[i].fd = clifd;
fds_arry[i].events = POLLIN;
found = 1;
break;
}
}
if(!found)
{
printf("accept new client [%d] but full, so refuse it\n",clifd);
close(clifd);
continue;
}
max = i>max ? i:max;
if( rv <=0 )
continue;
}
else
{
for ( i=1; i<ARRAY_SIZE(fds_arry);i++)
{
if(fds_arry[i].fd < 0)
continue;
if( (rv=read(fds_arry[i].fd,buf,sizeof(buf))) <=0)
{
printf("socket [%d] read failure or get disconnected\n",fds_arry[i].fd);
close(fds_arry[i].fd);
fds_arry[i].fd=-1;
}
else
{
printf("%s\n",buf);
sqlite_tem(buf);
printf("Database inserted successfully!\n");
}
}
}
}
cleanup:
close(listenfd);
return 0;
}
int socket_Server_init(char *listen_ip, int listen_port)
{
struct sockaddr_in servaddr;
int rv = 0;
int on = 1;
int listenfd;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("use socket()to create a TCP socket failure:%s\n",strerror(errno));
return -1;
}
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port = htons(listen_port);
if( !listen_ip )
{
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
if(inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) <=0)
{
printf("inet_pton set listen IP address failure\n");
rv = -2;
goto cleanup;
}
}
if( bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
{
printf("socket[%d] bind to port failure:%s\n",listenfd,strerror(errno));
rv = -3;
goto cleanup;
}
if( listen(listenfd,13) < 0)
{
printf("use bind to bind tcp socket failure:%s\n",strerror(errno));
rv = -4;
goto cleanup;
}
cleanup:
if(rv < 0)
close(listenfd);
else
rv = listenfd;
return rv;
}
void sqlite_tem(char *buf)
{
int nrow=0;
int ncolumn = 0;
char **azResult=NULL;
int rv;
sqlite3 *db=NULL;
char *zErrMsg = 0;
char sql1[100];
char *ipaddr=NULL;
char *datetime=NULL;
char *temper=NULL;
char *sql = "create table if not exists temperature(ipaddr char(30), datetime char(50), temper char(30))";
ipaddr = strtok(buf,"/");
datetime = strtok(NULL, "/");
temper = strtok(NULL, "/");
rv = sqlite3_open("tempreture.db", &db);
if(rv)
{
printf("Can't open database:%s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return;
}
printf("opened a sqlite3 database named tempreture.db successfully!\n");
int ret = sqlite3_exec(db,sql, NULL, NULL, &zErrMsg);
if(ret != SQLITE_OK)
{
printf("create table fail: %s\n",zErrMsg);
}
if(snprintf(sql1,sizeof(sql1), "insert into temper values('%s','%s','%s')", ipaddr, datetime, temper) < 0)
{
printf("Failed to write data\n");
}
//printf("Write data successfully!\n");
sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}
epoll代码实现
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "sqlite3.h"
#define BACKLOG 13
#define MAX_EVENTS 512
void print_usage(char* progname);
void sig_stop(int signum);
void sqlite_tem(char *buf);
int socket_listen(char *listen_ip, int port);
static int g_stop = 0;
int main(int argc, char* argv[])
{
int rv;
int ret;
int opt;
int idx;
int port;
int log_fd;
int ch = 1;
int daemon_run = 0;
int ser_fd = -1;
int cli_fd = -1;
struct sockaddr_in cli_addr;
socklen_t cliaddr_len = 20;
int epollfd;
struct epoll_event event;
struct epoll_event event_array[MAX_EVENTS];
int events;
int found;
int a;
int i;
char *zErrMsg;
sqlite3 *db;
char buf[1024];
struct option opts[] = {
{"daemon", no_argument, NULL, 'd'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, "dp:h", opts, &idx)) != -1)
{
switch (opt)
{
case 'd':
daemon_run = 1;
break;
case 'p':
port = atoi(optarg);
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if (!port)
{
print_usage(argv[0]);
return 0;
}
/*创建日志*/
if (daemon_run)
{
printf("Program %s is running at the background now\n", argv[0]);
log_fd = open("receive_temper.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);
if ((daemon(1, 1)) < 0)
{
printf("Deamon failure : %s\n", strerror(errno));
return 0;
}
}
/*安装信号*/
signal(SIGUSR1, sig_stop);
/*调用socket*/
if( (ser_fd = socket_listen(NULL, port)) < 0 )
{
printf("ERROR: %s server listen on serv_port %d failure\n", argv[0], port);
return -2;
}
printf("server start to listen on serv_port %d\n", port);
/*创建opll*/
if ((epollfd = epoll_create(MAX_EVENTS)) < 0)
{
printf("epoll_create failure:%s\n", strerror(errno));
return 0;
}
event.events = EPOLLIN;
event.data.fd = ser_fd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ser_fd, &event) < 0)
{
printf("epoll add ser_fd failure:%s\n", strerror(errno));
return 0;
}
while (!g_stop)
{
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 (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 == ser_fd)
{
cli_fd = accept(ser_fd, (struct sockaddr*) & cli_addr, &cliaddr_len);
if (cli_fd < 0)
{
printf("Accept the request from client failure:%s\n", strerror(errno));
continue;
}
event.data.fd = cli_fd;
event.events = EPOLLIN;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, cli_fd, &event) < 0)
{
printf("epoll add client socket failure:%s\n", strerror(errno));
close(cli_fd);
continue;
}
}
else
{
memset(buf, 0, sizeof(buf));
a = read(cli_fd, buf, sizeof(buf));
if (a < 0)
{
printf("Read information from client failure:%s\n", strerror(errno));
close(cli_fd);
exit(0);
}
else if (a == 0)
{
printf("The connection with client has broken!\n");
close(cli_fd);
exit(0);
}
else
{
printf("%s\n",buf);
sqlite_tem(buf);
printf("Database inserted successfully!\n");
}
}
}
}
close(ser_fd);
return 0;
}
/*帮助信息*/
void print_usage(char* progname)
{
printf("-d(--daemon):let program run in the background.\n");
printf("-p(--port):enter server port.\n");
printf("-h(--help):print this help information.\n");
return;
}
/*信号函数*/
void sig_stop(int signum)
{
if (SIGUSR1 == signum)
{
g_stop = 1;
}
return;
}
/*socket函数*/
int socket_listen(char *listen_ip, int port)
{
int rv = 0;
int on = 1;
int ser_fd;
struct sockaddr_in servaddr;
if ( (ser_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
{
printf("Use socket() to create a TCP socket failure: %s\n", strerror(errno));
return -1;
}
setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if( !listen_ip )
{
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
if( inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) <= 0 )
{
printf("Inet_pton() set listen IP address failure\n");
rv = -2;
goto cleanup;
}
}
if( bind(ser_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 )
{
printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));
rv = -3;
goto cleanup;
}
if( listen(ser_fd, 64) < 0 )
{
printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));
rv = -4;
goto cleanup;
}
cleanup:
if( rv < 0 )
close(ser_fd);
else
rv = ser_fd;
return rv;
}
/*数据库*/
void sqlite_tem(char *buf)
{
int nrow=0;
int ncolumn = 0;
char **azResult=NULL;
int rv;
sqlite3 *db=NULL;
char *zErrMsg = 0;
char sql1[100];
char *ipaddr=NULL;
char *datetime=NULL;
char *temper=NULL;
char *sql = "create table if not exists temperature(ipaddr char(30), datetime char(50), temper char(30))";
ipaddr = strtok(buf,"/");
datetime = strtok(NULL, "/");
temper = strtok(NULL, "/");
rv = sqlite3_open("tempreture.db", &db);
if(rv)
{
printf("Can't open database:%s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return;
}
printf("opened a sqlite3 database named tempreture.db successfully!\n");
int ret = sqlite3_exec(db,sql, NULL, NULL, &zErrMsg);
if(ret != SQLITE_OK)
{
printf("create table fail: %s\n",zErrMsg);
}
if(snprintf(sql1,sizeof(sql1), "insert into temper values('%s','%s','%s')", ipaddr, datetime, temper) < 0)
{
printf("Failed to write data\n");
}
//printf("Write data successfully!\n");
sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
sqlite3_free(zErrMsg);
sqlite3_close(db);
return;
}