温度实时监控上报(多线程实现客户端并发访问)

温度实时监控上报

温度实时监控上报服务器端,采用多线程以及互斥锁来实现多个客户端并发访问。

服务器端功能

1 ,通过命令行指定监听的端口;
2,程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
3, 程序能够捕捉kill信号正常退出;
4, 服务器要支持多个客户端并发访问;
5, 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如RPI0001/2019-0105 11:40:30/30.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 <pthread.h>
#include "sqlite3.h"

#define BACKLOG 13

//函数声明,打印帮助信息;捕捉信号并结束程序
void print_usage(char* progname);
void sig_stop(int signum);

//定义一个全局变量g_stop ,与sig_stop()函数配合,达到结束程序的目的
int g_stop = 0;

void* thread_worker(void* args);

typedef struct worker_ctx_s
{
    pthread_mutex_t    lock;
    sqlite3       *db;
    int           *client_fd;
}worker_ctx_t;

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;
    pid_t                   pid = -1;
    struct sockaddr_in      ser_addr;
    struct sockaddr_in      cli_addr;
    socklen_t               cliaddr_len = 20;
    worker_ctx_t            worker_ctx;
    pthread_t               tid;
    pthread_attr_t          thread_attr;
    char* zErrMsg = NULL;
    char                    sql1[128];
    //描述创建数据库中表的信息
    char* sql = "create table if not exists temperature(sn char(10), datetime char(50), temperature  char(10))";
    worker_ctx.db = NULL;
    worker_ctx.client_fd = &cli_fd;
    pthread_mutex_init(worker_ctx.lock, NULL);//锁的初始化
    /*命令行参数解析
        deamon:程序后台运行
        port:指定服务器开设端口
        help:打印帮助信息
    */
    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;
        }
    }

    //安装SIGUSR1信号
    signal(SIGUSR1, sig_stop);

    //创建socket_fd,以供后续使用
    //第一个参数为协议域,这里使用AF_INET(32位ipv4地址、16位端口)
    //第二个参数为type,指定socket类型,这里使用SOCK_STREAM
    //第三个参数为指定协议,当其为0时,自动选择与type类型相对应的默认协议
    ser_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (ser_fd < 0)
    {
        printf("Creat socket failure:%s\n", strerror(errno));
        return 0;
    }
    printf("Creat the ser_fd[%d]!\n", ser_fd);

    //设置服务器地址信息
    memset(&ser_addr, 0, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port = htons(port);		//调用htons(),将端口号由主机字节序转变成网络字节序
     //设置服务器IP地址(INADDR_ANY,代表监听所有IP),并调用htonl(0,将其由主机字节序转变成网络字节序
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //解决服务器程序停止后立刻运行,出现(端口)被占用的问题
    setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&ch, sizeof(ch));

    //绑定服务器地址(IP+端口),用于提供服务
    rv = bind(ser_fd, (struct sockaddr*) & ser_addr, sizeof(ser_addr));
    if (rv < 0)
    {
        printf("Bind server's port failure:%s\n", strerror(errno));
        return 0;
    }
    printf("The port[%d] has been bind by this server!\n", port);

    //调用listen(),将主动类型的socket套接字变为被动类型(等待客户端连接)
    //第一个参数为sockfd,指定所要监听的socket套接字,用于接收外部请求
    //第二个参数为backlog, TCP连接是一个过程,内核会在进程空间中维护一个连接队列以跟踪与服务器建立连接但还未着手处理的连接
    listen(ser_fd, BACKLOG);

    len = sqlite3_open("temper.db", &db);
    if (len)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        exit(1);
    }
    else
    {
        printf("You have opened a sqlite3 database named temper.db successfully!\n");
    }

    //创建temperature表,用来存放来自客户端的数据
    ret = sqlite3_exec(db, sql, 0, 0, &zErrMsg);
    if (ret != SQLITE_OK)
    {
        sqlite3_close(db);
        printf("Creat table failure \n");
        return 0;
    }
    printf("Create table successfully!\n");
    printf("Child process start to communicate with client by cli_fd[%d]...\n", cli_fd);
    printf("Now ready to connect the client and recive message from client...\n");
    printf("\n");
    sqlite3_close(db);

    if (pthread_attr_init(&thread_attr))
    {
        printf("pthread_attr_init() failure:%s\n", strerror(errno));
        goto cleanup;
    }
    if (pthread_attr_setdetachstate(&thread_attr, PTHREADD_CREATE_DETACHED))
    {
        printf("pthread_attr_setdetachstate() failure:%s\n", strerror(errno));
        goto cleanup;
    }

    while (!g_stop)
    {
        //接受来自客户端的请求,并创建一个cli_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;
        }

        if (pthread_create(&tid, &thread_attr, thread_worker, &worker_ctx))
        {
            printf("create thread failure:%ss\n", strerror(errno));
            goto cleanup;
        }
    }
cleanup:
    pthread_attr_destory(&thread_attr);
    close(ser_fd);
    return 0;
}

void* thread_worker(void* ctx)
{
    int                     a;
    int                     len;
    char* sn;
    char* cut;
    char* temp;
    char                    buf[512];
    char* datetime;
    char                    sql1[128];
    //描述创建数据库中表的信息
    char* sql = "create table if not exists temperature(sn char(10), datetime char(50), temperature  char(10))";
    worker_ctx_t* ctx1 = (worker_ctx_t*)ctx;
    if (!args)
    {
        printf("get innvalid arguements\n");
        pthread_exit(NULL);
    }

        pthread_mutex_lock(&ctx->lock);//申请锁,这里是阻塞锁,如果锁被别的线程持有则不会返回

        //调用sqlite3_open(),打开.db数据库文件。若没有,则创建
        len = sqlite3_open("temper.db", &db);
        if (len)
        {
            fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
            sqlite3_close(db);
            exit(1);
        }
        else
        {
            printf("You have opened a sqlite3 database named temper.db successfully!\n");
        }

            memset(buf, 0, sizeof(buf));

            a = read(*client_fd, buf, sizeof(buf));		//从buf中读数据(客户端写入)
            if (a < 0)		//返回值小于零,则表示接收数据出错
            {
                printf("Read information from client failure:%s\n", strerror(errno));
                close(*client_fd);
                exit(0);
            }
            else if (a == 0)		//返回值等于0,则表示客户端主动断开连接
            {
                printf("The connection with client has broken!\n");
                close(*client_fd);
                exit(0);
            }
            else		//返回值大于零,则表示接收数据成功
            {
                printf("The message recived from client[%s,%d] is \"%s\"\n",
                    inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
                //对接收到的数据进行分割操作,便于存储至数据库
                cut = strtok(buf, "/");
                if (cut != NULL)
                {
                    sn = cut;
                    //printf("%s\n", sn);
                    if ((cut = strtok(NULL, "/")) != NULL)
                    {
                        datetime = cut;
                        //printf("%s\n", datetime);
                        if ((cut = strtok(NULL, "/")) != NULL)
                        {
                            temp = cut;
                            //printf("%s\n", temp);
                        }
                    }
                }

                snprintf(sql1, 128, "insert into temperature values('%s', '%s', '%s');",sn, datetime, temp);
                sql1[127] = '\0';
                printf("%s\n", sql1);
                //将数据存储至temperature表中
                ret = sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
                if (ret != SQLITE_OK)
                {
                    sqlite3_close(db);
                    printf("insert data failure ; %s!\n", zErrMsg);
                    return 0;
                }
                printf("insert data successfully!\n");
                printf("\n");
                close(*client_fd);
                pthread_mutex_unlock(&ctx->lock);
                sleep(1);
                return;
}


//打印帮助信息函数
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;
}

//回调函数sig_stop()
void sig_stop(int signum)
{
    if (SIGUSR1 == signum) / 判断捕捉到信号是否为SIGUSR1
    {
        g_stop = 1;		//g_stop为1,while(!g_stop)循环结束,程序关闭
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值