文章目录
1. 客户端要求
- 树莓派上运行socket客户端程序,每隔30秒(可指定)以字符串“ID/时间/温度”形式上报采样温度,其中ID为树莓派的编号,便于服务器端区别是哪个树莓派客户端,如
RPI0001/2019-01-05 11:40:30/30.0C
- 通过命令行参数指定服务器IP地址和端口以及间隔采样时间, getopt或getopt_long;
- 程序放到后台运行(daemon),并通过syslog记录程序的运行出错、调试日志;
- 程序能够捕捉kill信号正常退出;
2. 客户端流程图
3. 功能分析
- 建立socket通信,此处编写一个
client_main()
函数,与server进行connect;使用snprintf()函数进行格式化字符串,以达到指定输出格式;为了获取时间和温度样本,此处分别编写get_temper()
函数和get_time()
函数。 - 客户端连接服务器,将其打包成
client_init()
函数,直接调用。 - 通过调用long_options()函数进行命令行参数解析,并编写打印帮助信息函数
print_usage()
函数。 - 要是程序后台运行,即须调用守护进程deamon()函数,并与之配合建立log日志系统。
- 捕捉信号,即须安装相关信号,调用signal()函数,并编写相应回调函数signal_stop(),使程序正常退出。
- 再将这些调用函数的声明全部放在头文件
header.h
中,在主函数client_main()中包含该头文件即可。
4. 代码模块
header.h头文件
/********************************************************************************
* Copyright: (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
* All rights reserved.
*
* Filename: header.h
* Description: This head file is temperature project head file
*
* Version: 1.0.0(11/12/2019)
* Author: Tang Zhiqiang <t_zhiqiang@163.com>
* ChangeLog: 1, Release initial version on "11/12/2019 07:29:02 PM"
*
********************************************************************************/
void print_usage(char *progname);
void get_time(char *data_time);
int get_temper(float *temper);
int client_init(int port, char * serv_ip);
get_temper函数获取DS18B20采集的数据
/*********************************************************************************
* Copyright: (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
* All rights reserved.
*
* Filename: get_temper.c
* Description: This file is get RPI temperature in file.
*
* Version: 1.0.0(11/09/2019)
* Author: Tang Zhiqiang <t_zhiqiang@163.com>
* ChangeLog: 1, Release initial version on "11/09/2019 06:00:53 PM"
*
********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include "header.h"
//#define filepath /sys/bus/w1/devices/28-041731f7c0ff/
//int get_temper(float *temper);
int get_temper(float *temper)
{
char filepath[120]="/sys/bus/w1/devices/";
char f_name[50];
char data_array[1024];
char *data_p=NULL;
struct dirent *file=NULL;
DIR *dir=NULL;
int data_fd = -1;
int found = 0;
//float *temper;
//opendir打开目录失败,返回NULL 成功:返回该路径下的所有文件和目录
if((dir = opendir(filepath)) == NULL)
{
printf("opendir file failure: %s\n",strerror(errno));
return -1;
}
while((file = readdir(dir)) != NULL)
{
//if((strcmp(file->d_name, ".", 1) == 0) || (strcmp(file->d_name, ".", 1) == 0))
//continue; //ignore '.' and '..' file
if(strstr(file->d_name, "28-"))
{ //memset(f_name, 0, sizeof(f_name));
strncpy(f_name, file->d_name, sizeof(f_name)); //locate reserve temperature data file path
found = 1; //设置found为1,即代表找到相应文件夹
printf("reserve temperature data file path: %s\n",f_name);
}
//closedir(dir);
}
closedir(dir);
/* found == 0; 未找到目的文件夹 */
if(!found)
{
printf("Can not find the folder\n");
return 0;
}
/* 找到相应文件夹后,切换至该文件夹下以获取温度数据 */
strncat(filepath, f_name, sizeof(filepath)-strlen(filepath)); //将文件夹名连接到filepath路径后;sizeof计算数据总体大小,strlen计算当前存放字符串的大小
strncat(filepath, "/w1_slave", sizeof(filepath)-strlen(filepath)); //将设备文件夹下存放温度的文件连接到filepath路径后
data_fd=open(filepath, O_RDONLY);
if(data_fd < 0)
{
printf("open file failure: %s\n", strerror(errno));
return -2;
}
memset(data_array, 0, sizeof(data_array));
if(read(data_fd, data_array, sizeof(data_array)) < 0)
{
printf("read file failure: %s\n", strerror(errno));
return -3;
}
/* data_p指针后移两个字符单位,其后即为温度数据 */
data_p=strstr(data_array, "t="); //strst()成功:返回第一次出现该字符串位置的指针
data_p += 2; //local temperature data
*temper=atof(data_p)/1000; //"()" priority super "/"
close(data_fd);
return 0;
}
get_time函数获取当前系统时间,并将其返回成需要的格式
/*********************************************************************************
* Copyright: (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
* All rights reserved.
*
* Filename: get_time.c
* Description: This file is get current systrm time.
*
* Version: 1.0.0(11/09/2019)
* Author: Tang Zhiqiang <t_zhiqiang@163.com>
* ChangeLog: 1, Release initial version on "11/09/2019 05:59:44 PM"
*
********************************************************************************/
#include <time.h>
#include <stdio.h>
#include "header.h"
//#define ip TZQ
//void get_time(char *data_time);
void get_time(char *data_time)
{
struct tm *p;
time_t timep;
//char datime[50];
time(&timep); // 获取当前的系统时间(CUT时间)
p=gmtime(&timep); //gmtime()函数参数timep 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构strcut tm*返回
snprintf(data_time, 50, "%d-%02d-%02d %02d:%02d:%02d", (1900+p->tm_year), (1+p->tm_mon), p->tm_mday, (p->tm_hour+8), p->tm_min, p->tm_sec);
//小时+8是因为我们是+8区,比CUT多8个小时
return ;
}
client_init函数初始化客户端,连接其服务端
/*********************************************************************************
* Copyright: (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
* All rights reserved.
*
* Filename: client_init.c
* Description: This file is cliet initial function.
*
* Version: 1.0.0(11/09/2019)
* Author: Tang Zhiqiang <t_zhiqiang@163.com>
* ChangeLog: 1, Release initial version on "11/09/2019 05:57:49 PM"
*
********************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <netinet/in.h>
#include "header.h"
//int client_init(int port, char *serv_ip);
int client_init(int port, char *serv_ip)
{
int con_fd = -1;
int rv = -1;
struct sockaddr_in serv_addr;
con_fd=socket(AF_INET, SOCK_STREAM, 0);
if(con_fd < 0)
{
printf("Create socket failure: %s\n", strerror(errno));
return -1;
}
printf("Create socket[%d] sucessfully!\n", con_fd);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(port);
inet_aton(serv_ip, &serv_addr.sin_addr);
if(con_fd >= 0)
{
rv=connect(con_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if(rv < 0)
{
printf("Connect to server[%s:%d] failure: %s\n", serv_ip, port, strerror(errno));
return -2;
}
printf("Connect to server[%s:%d] sucessfully!\n", serv_ip, port);
}
return con_fd;
}
client_main函数为客户端的主函数
/*********************************************************************************
* Copyright: (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
* All rights reserved.
*
* Filename: client_main.c
* Description: This file socket client monitor RPI temperature.
*
* Version: 1.0.0(10/22/2019)
* Author: Tang Zhiqiang <t_zhiqiang@163.com>
* ChangeLog: 1, Release initial version on "10/22/2019 04:32:59 PM"
*
********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sqlite3.h>
#include "header.h"
#define id "t_zhiqang"
int g_sigstop = 0;
//void print_usage(char *progname);
void signal_stop(int signum)
{
if(SIGTERM == signum)
{
printf("SIGTERM signal detected\n");
g_sigstop = 1;
}
}
int main(int argc, char *argv[])
{
int sock_fd = -1;
int rv = -1;
struct sockaddr_in serv_addr;
char *serv_ip = NULL;
int serv_port = 0;
char buf[1024];
int ch;
char da_time[50];
int interv_time = 0;
float temper;
int daemon_run = 0;
int log_fd = -1;
static struct option long_options[] = {
{"d", no_argument, NULL, 'd'},
{"ipaddr", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{"interv_time", required_argument, NULL, 't'},
{0, 0, 0, 0}
};
while((ch=getopt_long(argc, argv, "di:p:ht:", long_options, NULL)) != -1)
{
switch (ch)
{
case 'd':
daemon_run = 1;
break;
case 'i':
serv_ip=optarg;
break;
case 'p':
serv_port=atoi(optarg); //optarg返回的是字符串,atoi()将char转换为int型
break;
case 'h':
print_usage(argv[0]);
break;
case 't':
interv_time=atoi(optarg);
break;
default:
print_usage(argv[0]);
exit(0);
break;
}
}
if(!serv_ip || !serv_port)
{
print_usage(argv[0]);
return -1;
}
//openlog("socket_client_RPI", LOG_CONS | LOG_PID, 0); //可选
//printf("Program %s running failure: %s\n",argv[0], strerror(errno)); //将错误或调试信息写入系统自带的syslog日志
signal(SIGTERM, signal_stop); //安装signal信号
signal(SIGPIPE, SIG_IGN); //屏蔽SIGPIPE信号,因为当服务器主动断开socket连接后,客户端会接收到SIGPIPE信号,自动退出。
//为达到服务器主动断开socket连接后,客户端重连服务器端的目的,这里必须屏蔽掉SIGPIPE信号
//守护进程函数
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;
}
}
while (!g_sigstop)
{
/*采样SN、时间、温度...*/
get_time(da_time);
if((get_temper(&temper)) < 0)
{
printf("Get temperature failure: %s\n", strerror(errno));
continue;
}
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf), "%s/%s/%.2f%c", id, da_time, temper, 'C');
//未连接到服务器
if(sock_fd < 0)
{
//调用client_ini()函数,连接服务器
if((sock_fd=client_init(serv_port, serv_ip)) < 0)
{
printf("connect server point fialure: %s\n", strerror(errno));
continue; //连接服务端失败,重新连接
}
}
/* connect server OK!*/
if(sock_fd >= 0)
{
if(write(sock_fd, buf, sizeof(buf)) < 0)
{
printf("write data to server fialure: %s\n", strerror(errno));
close(sock_fd);
sock_fd = -1;
return -3;
}
}
printf("Send messege to server sucessfully!\n");
sleep(interv_time); //命令行参数设定上传数据间隔时间
}
//closelog();
close(sock_fd);
//sqlite3_close(db);
return 0;
}
void print_usage(char *progname)
{
printf("%s usage.\n", progname);
printf("%s is a socket client progname, which used to verify server and echo back string from it\n", progname);
printf("\nMandatory arguments to long options are mandatory for short option too:\n");
printf("-b[darmon] set program running on backgroud\n");
printf("-t[interv_time] RPI temperature interval time\n");
printf("-p[port] Socket client port address\n");
printf("-i[ip] Socket client ip address\n");
printf("-h[help] Display this help information\n");
printf("\nExample: %s -t 30 -p 8088 -i 192.168.174.5\n", progname);
return;
}
5. 客户端运行结果
运行结果需要在服务器运行的前提下才可以,运行结果为:
这里需要注意:环境生产可执行文件后,环境变量需要加进去,不然找不到程序header.h头文件。
(这个没有写进makefile中,环境变量只是临时生效,重启失效)
6. 方便查看,贴出多进程的服务器运行结果:
具体可以看下面的详解
服务器详解