【socket】基于socket使用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,出错返回-1struct 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()、修改宏定义甚至重新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值