【socket】基于Linux使用select上报温度–服务端

select使用

*
select函数
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流程图

【socket】基于Linux使用select上报温度--服务端

; 服务端代码实现

  • 通过命令行指定监听的端口;
  • 程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
  • 程序能够捕捉kill信号正常退出;
  • 服务器要支持多个客户端并发访问,可以选择多路复用、多进程或多线程任意一种实现;
  • 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如192.168.0.26/2022-01-21 15:40:30/20.0C”;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

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

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

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

    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()、修改宏定义甚至重新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;

Original: https://www.cnblogs.com/Ye-Wei/p/16728602.html
Author: 西故黄鹤楼
Title: 【socket】基于Linux使用select上报温度–服务端

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/606342/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux下使用ssh测试端口是否开启

    当服务器上不允许使用telnet时,可以使用ssh测试远程服务器端口是否开启 具体命令如下 -v 显示连接debug信息 -p port 指定端口 ssh -v -p 80 roo…

    Linux 2023年6月7日
    0105
  • Redis的slot迁移工具

    工具下载: https://github.com/eyjian/redis-tools/blob/master/move_redis_slot.sh 支持迁移已有的keys。 #!…

    Linux 2023年5月28日
    095
  • EKS助力小白实践云原生——通过k8s部署wordpress应用

    目前云原生在大厂已经有了充分的实践,也逐渐向小厂以及非互联网公司推广。适逢12月20日,腾讯云原生【燎原社】精心打造了云原生在线技术工坊,让零基础的同学也能快速入门和实践 Dock…

    Linux 2023年6月13日
    078
  • Tomcat 介绍及使用教程

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 1. Tomcat 介绍 Apache Tomcat 是由 Apache Software Foundation(ASF)开发的…

    Linux 2023年5月27日
    076
  • Linux文件属性详述

    一、文件属性信息概述 文件属性信息组成如下: 文件索引属性信息——inode编号; 文件类型权限信息; 文件链接属性信息——硬链接数; 文件属主信息——文件所有者; 文件属组属性信…

    Linux 2023年5月27日
    095
  • 使用MyBatis Generator代码生成器的简单模式

    在动态web项目的lib目录下放入mybatis-3.2.2jar、mysql-connector-java-5.1.25-bin.jar、log4j-1.2.17.jar还有生成…

    Linux 2023年6月8日
    0112
  • 同城双活-流量分流

    引言 现阶段,在同城带宽时延问题没有经过大规模的生产实践、验证的情况下,我们只导入”白名单或1%”的小比例请求流量,进入双活环境,确保环境有效的(活的),同…

    Linux 2023年6月14日
    095
  • shell脚本echo打印错位

    问题描述 在脚本中使用curl命令请求Jenkins的API获取job的编号,随后将编号和其他字符串拼接后,使用echo命令打印出来,但打印后字符串错位了。 脚本大致如下: num…

    Linux 2023年6月8日
    0111
  • WPF 多线程下跨线程处理 ObservableCollection 数据

    本文告诉大家几个不同的方法在 WPF 里,使用多线程修改或创建 ObservableCollection 列表的数据 需要明确的是 WPF 框架下,非 UI 线程直接或间接访问 U…

    Linux 2023年6月6日
    093
  • 消息中间件MQ的学习境界和路线

    在《深入理解Java类加载机制,再也不用死记硬背了》里我提到了对于一门语言的”会”的三个层次。本篇将以知识地图的形式展现学习消息中间件MQ各个层次要掌握的内…

    Linux 2023年6月14日
    0107
  • Linux解压命令

    .tar解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(注:tar是打包,不是压缩!)———————————————….

    Linux 2023年6月13日
    072
  • 请求方式

    题目如下 题目描述为请求方式,HTTP的请求方式一共有八种,读者自行去查 打开靶场如下 题目的意思需要以CTF**B为请求方式,由于平台名为CTFHUB,于是试了一下 接着抓包,推…

    Linux 2023年6月7日
    099
  • 深入理解Java类加载机制,再也不用死记硬背了

    谈谈”会”的三个层次 在《说透分布式事务》中,我举例里说明了会与会的差别。对一门语言的学习,这里谈谈我理解的”会”的三个层次: 第一…

    Linux 2023年6月14日
    098
  • Android(Linux)控制GPIO方法二

    前文《Android(Linux)控制GPIO的方法及实时性分析》主要使用Linux shell命令控制GPIO,该方法可在调试过程中快速确定GPIO硬件是否有问题,即对应的GPI…

    Linux 2023年6月7日
    075
  • node-java的使用及源码分析

    上篇文章简单提了下node调用java的方法但也只属于基本提了下怎么输出helloworld的层度,这次将提供一些案例和源码分析让我们更好地了解如何使用node-java库。 前置…

    Linux 2023年6月14日
    097
  • Redis的快照持久化-RDB与AOF

    Redis为了内部数据的安全考虑,会把本身的数据以文件形式保存到硬盘中一份,在服务器重启之后会自动把硬盘的数据恢复到内存(redis)的里边。 数据保存到硬盘的过程就称为&#822…

    Linux 2023年5月28日
    096
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球