【socket】基于Linux使用select上报温度--服务端
select函数
select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是readfds(文件描述符有数据到来可读)、 writefds(文件描述符可写)、和exceptfds(文件描述符异常)。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、 或者有错误异常),或者超时( timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到 究竟是哪些文件描述符就绪。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:
nfds:指待测试的fd的总个数,它的值是待测试的最大文件描述符加1
中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为NULL;
timeout:设置select的超时时间,如果设置为NULL则永不超时;
select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-1;
struct timeval
{
long tv_sec; //秒
long tv_usec; //微秒
};
void FD_ZERO(fd_set *set);//清空集合
void FD_CLR(int fd, fd_set *set);//将给定的描述符从文件中删除
int FD_ISSET(int fd, fd_set *set);//判断指定描述符是否在集合中
void FD_SET(int fd, fd_set *set);//将给定的描述符加入集合
select的缺点:
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select支持的文件描述符数量太小了,默认是1024
可以从内核和select的关系来看:
(1)传向select的参数告诉内核:
①我们所关心的描述符。
②对于每个描述符我们所关心的条件。
③希望等待多长时间
(2)从select返回时,内核告诉我们:
①已准备好的描述符的数量。
②哪一个描述符已准备好读、写或异常条件
select流程图
服务端代码实现
- 通过命令行指定监听的端口;
- 程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
- 程序能够捕捉kill信号正常退出;
- 服务器要支持多个客户端并发访问,可以选择多路复用、多进程或多线程任意一种实现;
- 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如192.168.0.26/2022-01-21 15:40:30/20.0C”;
#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/select.h>
#include <time.h>
#include "sqlite3.h"
#define BACKLOG 13
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
void print_usage(char* progname);
void sig_stop(int signum);
void sqlite_tem(char *buf);
int socket_listen(char *listen_ip, int listen_serv_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 maxfd = 0;
int fds_array[1024];
fd_set rdset;
int found;
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);
for (i = 0;i < ARRAY_SIZE(fds_array);i++)
{
fds_array[i] = -1;
}
fds_array[0] = ser_fd;
while (!g_stop)
{
FD_ZERO(&rdset);
for (i = 0;i < ARRAY_SIZE(fds_array);i++)
{
if (fds_array[i] < 0)
continue;
maxfd = fds_array[i] > maxfd ? fds_array[i] : maxfd;
FD_SET(fds_array[i], &rdset);
}
rv = select(maxfd + 1, &rdset, NULL, NULL, NULL);
if (rv < 0)
{
printf("select failure :%s\n", strerror(errno));
break;
}
else
{
if (rv == 0)
{
printf("select gettime out\n");
continue;
}
}
if (FD_ISSET(ser_fd, &rdset))
{
if ((cli_fd = accept(ser_fd, (struct sockaddr*) & cli_addr, &cliaddr_len)) < 0)
{
printf("accept new client failure:%s\n", strerror(errno));
continue;
}
found = 0;
for (i = 0;i < ARRAY_SIZE(fds_array);i++)
{
if (fds_array[i] < 0)
{
printf("accrpt new client[%d] and add it into array\n", cli_fd);
fds_array[i] = cli_fd;
found = 1;
break;
}
}
if (!found)
{
printf("accept new client[%d] but full, so refuse it\n", cli_fd);
close(cli_fd);
}
}
else
{
for (i = 0;i < ARRAY_SIZE(fds_array);i++)
{
if (fds_array[i] < 0 || !FD_ISSET(fds_array[i], &rdset))
continue;
else
{
memset(buf, 0, sizeof(buf));
rv = read(cli_fd, buf, sizeof(buf));
if (rv < 0)
{
printf("Read information from client failure:%s\n", strerror(errno));
close(cli_fd);
exit(0);
}
else if (rv == 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;
}
优点:
基于select的I/O复用模型的是单进程执行可以为多个客户端服务,这样可以减少创建线程或进程所需要的CPU时间片或内存资源的开销;此外几乎所有的平台上都支持select(),其良好跨平台支持.
当然它也有两个主要的缺点:
每次调用 select()都需要把fd集合从用户态拷贝到内核态,之后内核需要遍历所有传递进来的fd,这时如果客户端fd很多时会导致系统开销很大;
单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过setrlimit()、修改宏定义甚至重新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;