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