温度上报实时监控项目——客户端

Part1 客户端

一、客户端功能要求

1、树莓派上运行socket客户端程序,每隔30秒以字符串“ID/时间/温度”形式上报 采样温度,其中ID为树莓派的编号,便于服务器端区别是哪个树莓派客户端,如“RPI0001/2019-01-05 11:40:30/30.0C”;
2、通过命令行参数指定服务器IP地址和端口以及间隔采样时间;
3、程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
4、程序能够捕捉kill信号正常退出;

二、功能分析

1、建立socket通信,此处编写一个connect_server()函数,与server进行connect;,使用snprintf()函数进行格式化字符串,以达到指定输出格式;为了获取时间和温度样本,此处分别编写temperper_get()函数和time_get()函数。
2、通过调用getopt_long()函数进行命令行参数解析,并编写打印帮助信息函数print_usage()函数。
3、要是程序后台运行,即须调用守护进程deamon()函数,并与之配合建立log日志系统。
4、捕捉信号,即须安装相关信号,调用signal()函数,并编写相应回调函数sig_stop(),使程序正常退出

三、模块代码

1、连接服务器建立通信模块——connect_server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

int connect_server(int port, char *ser_ip);

int connect_server(int port, char *ser_ip)
{
    int                      con_fd = -1; 
    struct sockaddr_in       ser_addr; 
    //创建socket_fd
    con_fd = socket(AF_INET, SOCK_STREAM, 0); 
    if (con_fd < 0) 
    { 
        printf("Creat socket failure:%s\n",strerror(errno)); 
        
        return -2; 
    } 
    
    //设置服务器端IP地址及端口信息
    memset(&ser_addr, 0, sizeof(ser_addr)); 	//ser_addr结构体变量占用内存区域清零
    ser_addr.sin_family = AF_INET; 				//设定协议族为AF_INET,即使用ipv4(32位)与端口号(16位)的组合
    ser_addr.sin_port = htons(port);			//调用htons()函数,将端口号由主机字节序转变成网络字节序
    inet_aton(ser_ip, &ser_addr.sin_addr); 		//将目前字符串形式的IP地址转变成一个32位的网络序列的IP地址
    
    //与服务器端建立连接,并判断是否成功连接
    //ipv4对应sockaddr_in类型,此时应进行强制类型转换为通用套接字sockaddr类型
    if (connect(con_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0) 
    { 
        printf("Connect server failure:%s\n", strerror(errno)); 
        return -3; 
    } 

    //连接成功,打印相关信息
    printf("Establish the connection with server successfully\n");

	//返回客户端创建的socket_fd
    return con_fd;
} 

2、温度(ds18b20数字温度传感器)采样模块——temperper_get.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>

int temperper_get(float *temper);

int temperper_get(float *temper)
{
    int              fd = -1;
    int              found = 0;
    char             f_name[32];
    char             cache[512];
    char             *ptr = NULL;
    char             path[128]="/sys/bus/w1/devices/";
    DIR              *dirp = NULL;
    struct dirent    *direntp = NULL;

	//打开温度传感器设备文件夹
    dirp = opendir(path);   
    if(dirp == NULL) 
    { 
        printf("open folder failure: %s\n", strerror(errno)); 
        
        return 0;
    }
    
    //查找温度传感器对应文件夹
    while ((direntp = readdir(dirp)) != NULL) 
    {
        if (strstr(direntp->d_name, "28-")) 
        {
            strncpy(f_name, direntp->d_name, sizeof(f_name)); 
            found = 1; 		//设置found为1,即代表找到相应文件夹
        } 
    }
    closedir(dirp); 		//关闭opendir()函数打开的文件夹
        
    //found == 0;即表明未检索到目的文件夹
    if (!found) 
    { 
        printf("Can not find the folder\n"); 
        
        return 0; 
    } 
    
    //找到相应文件夹后,切换至该文件夹下以获取温度数据
    strncat(path, f_name, sizeof(path)-strlen(path)); 		//将文件夹名连接到path路径后
    strncat(path, "/w1_slave", sizeof(path)-strlen(path)); 	//将设备文件夹下存放温度的文件连接在path路径后
   	
   	//打开存放温度数据的文件
    if ((fd = open(path, O_RDONLY)) < 0) 
    { 
        printf("open file failure: %s\n", strerror(errno)); 
        
        return 0; 
    } 
    
    //对定义buffer进行清零操作,并读进文件内容
    memset(cache, 0, sizeof(cache)); 
    if (read(fd, cache, sizeof(cache)) < 0) 
    {
        printf("Read data failure: %s\n", strerror(errno)); 
        
        return 0;
    } 
    
	//利用strstr(0函数进行字符串查找,目的是找到相应温度数值
    ptr = strstr(cache, "t=");
    if (!ptr) 
    {
        printf("Can not find \"t=\"!\n");
        
        return 0;
    } 
    
    //ptr指针后移两个字符单位(“t= ”),其后即为温度数据
    ptr += 2;
    *temper = atof(ptr)/1000; 	//将以字符串形式存储的温度数据转换成浮点数
    
    close(fd); 					//关闭open()打开文件时创建的文件描述符
    return 0; 
}

3、时间获取模块——time_get.c

#include <time.h>
#include <stdio.h>

void time_get(char *date_time);

void time_get(char *date_time)
{
    time_t            timep;
    struct tm         *p;		//p为一个指向tm结构体类型的指针

    time(&timep);				//获得time_t结构的时间,UTC时间(在计算机中看到的UTC时间都是从1970年01月01日 0:00:00开始计算秒数的)
    p = gmtime(&timep);			//gmtime()函数将时间和日期转换成格林威治(GMT)时间


	//调用snprintf(),格式化字符串,输出指定时间格式
    snprintf(date_time, 32, "%d-%d-%d %d:%d:%d", 1900+p->tm_year,
            1+p->tm_mon, p->tm_mday, (p->tm_hour + 8), p->tm_min, p->tm_sec);
	
    return ;
}

4、自定义头文件“header.h”

//“header.h”
void print_usage(char *progname);				//print_usage()函数声明;
void time_get(char *date_time);					//time_get函数声明;
int temperper_get(float *temper);				//temperper_get函数声明;
int connect_server(int port, char *ser_ip);		//connect_server函数声明;

5、main函数——monitor_tem.c

#include <stdio.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <syslog.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <dirent.h>
#include "header.h"

void sig_stop(int signum);				//目的是配合信号使用,将g_stop设置为1,使程序终止

int g_stop = 0;

int main(int argc, char *argv[])
{
    int                      rv = -1;
    int                      daemon_run;
    int                      opt;
    int                      idx;
    int                      S_time;
    int                      port;
    int                      log_fd;
    int                      sock_fd = -1;
    char                     *ser_ip;
    char                     buf[512];
    char                     date_time[32];
    char                     *id = "Mastema";		//定义设备ID,此处为自己随便设定
    char                     *hostname;
    float                    temper;
    struct hostent           *getname = NULL;
    /*命令行参数解析:
    		daemon:程序后台运行;
    		time:待设定的上报时间间隔;
    		name:设置待连服务器域名或者IP;
    		port:设置待连服务器端口;
    		help:命令行参数输入错误时,打印帮助信息。
    */
    struct option            opts[] = {
               {"daemon",no_argument,NULL,'d'},
               {"time",required_argument,NULL,'t'},
               {"name",required_argument,NULL,'n'},
               {"port",required_argument,NULL,'p'},
               {"help",no_argument,NULL,'h'},
               {NULL,0,NULL,0}    
    }; 
     
    while ((opt = getopt_long(argc,argv,"dt:n:p:h",opts,&idx)) != -1)
    {
         switch(opt)
         {
                case 'd':
                        daemon_run = 1;				//daemon_run为1,设定deamon()函数执行
                        break;
                case 't': 
                        S_time = atoi(optarg);		//将命令行参数中的时间(字符串形式)转换成整型
                        break;
                case 'n':
                        hostname = optarg;			//将命令行参数的域名或IP地址赋值给hostname
                        break;
                case 'p':
                        port = atoi(optarg);		//将命令行参数中的端口号(字符串形式)转换成整型
                        break;
                case 'h':
                        print_usage(argv[0]); 		//如有“-h”参数,则调用print_usage()函数打印帮助信息

                return 0;
         }
    }

    if (!port || !hostname)    			//判断是否输入端口号或域名(IP地址)
    {
        print_usage(argv[0]);
        return 0;
    }

	//DNS域名解析,将域名解析成服务器的公网IP地址,并将相关信息保存在getname指向的结构体中
    getname = gethostbyname(hostname); 
    if (getname == NULL)
    {
        printf("Get hostname failure : %s\n", strerror(h_errno));

        return 0;
    }

	//将保存在hostent结构体内的服务器IP地址(32位的网络字节序)转换成相应的点分十进制形式IP地址,并返回的char*指针赋值给给ser_ip
    ser_ip = inet_ntoa(*(struct in_addr *)getname->h_addr);
    if (ser_ip == NULL)
    {
        printf("Get ser_ip failure : %s\n", strerror(errno));

        return 0;
    }
    printf("ser_ip[%s]\n", ser_ip);
    
    //守护进程函数
    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);

		//设置deamon()函数两个参数为1
		//即保持当前目录不变,并且使标准输出及标准出错重定向仍打印输出信息,只不过此时打印信息将会全部打印至日志文件中!
        if ((daemon(1, 1)) < 0)
        {
            printf("Deamon failure : %s\n", strerror(errno));

            return 0;
        }
    }

	//安装信号
    signal(SIGUSR1, sig_stop);		//捕捉到kill信号后,调用sig_stop(),将g_stop设置为1,程序结束
    signal(SIGPIPE, SIG_IGN);		//屏蔽SIGPIPE信号,因为当服务器主动断开socket连接后,客户端会接收到SIGPIPE信号,自动退出。为达到服务器主动断开socket连接后,客户端重连服务器端的目的,这里必须屏蔽掉SIGPIPE信号

	//对buf栈区内存执行清零操作
    memset(buf, 0, sizeof(buf));
    while (!g_stop)
    {
        if ((temperper_get(&temper)) < 0)			//调用temperper_get()函数,获取温度
        {
            printf("Get temperature failure!\n");
            continue;
        }
        time_get(date_time);		//调用time_get()函数,获取时间

		//格式化字符串输出,形式为功能指定形式
        snprintf(buf, sizeof(buf),"%s/%s/%.1f%c", id, date_time, temper, 'C');

		//未能连接到服务器
        if (sock_fd < 0)
        {
        	//调用connect_server()函数,尝试连接服务器
            if ((sock_fd = connect_server(port, ser_ip)) < 0)
            {
                printf("Now establish the connection with server again!\n");
                printf("\n");
                continue;
            }
        }

		//成功连接服务器,开始传输数据
        if (sock_fd >= 0) 
        {
        	//向buf中写数据,并通过pipe传输给服务器端
            rv = write(sock_fd, buf, sizeof(buf));
            if (rv >= 0)
            {
                printf("Send messege to server successfully!\n");
            }
            else
            {
                printf("Send messege to server failure : %s\n", strerror(errno));
                close(sock_fd); 
                sock_fd = -1;		//上传数据失败,将sock_fd重设为-1,下次循环重新连接服务器
            }
        }

		//程序休眠S_time,达到一定时间间隔后,将数据上报服务器的目的
        sleep(S_time);
    }

    close(sock_fd);
    //syslog(LOG_NOTICE, "Program stop running\n");
    //closelog();

    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");
    printf("-t(--time):enter the sampling interval time.\n");
    printf("-i(--ip):enter the server's ipaddr.\n");

    return ;
}

//捕捉到相应信号后,设置g_stop为1
void sig_stop(int signum)
{
    if (SIGUSR1 == signum)
    {
        g_stop = 1;        
    }

    return ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值