本文共 6873 字,大约阅读时间需要 22 分钟。
前言:放寒假之前收到了老师布置的的这个项目实战,能够实现一个项目完整的功能。之前上课有学习一些socket网络编程,Linux下文件I/O,多进程、多线程、多路复用等知识,至于之后项目中用到的sqlite3数据库是之后自己通过网上了解到的。这些知识都是零零散散的,这个项目刚好拿来练习,可以把知识点都串联起来。其实寒假前就已经基本完成了所有的功能,一直没有写一篇博客,都快忘却了,所以得写一篇博客回忆复习一下
首先大致介绍一下这个项目和思路
该项目由客户端和服务器端组成,树莓派上通过1-Wire协议连接DS18B20温度传感器,每隔一定时间采样上报给服务器程序,服务器端收到来自各个客户端的数据之后保存到数据库中。程序放到后台运行,并通过syslog记录程序的运行出错、调试日志。程序能够捕捉信号正常退出。首先需要画出流程图,得出编程思路,再进行编程。流程图如下:
1.通过域名解析,将树莓派的域名解析成IP地址,通过上网查询,dns函数可以实现该功能。
void dns(char *domain_name,char **ip) { struct hostent *server_name=NULL; server_name=gethostbyname(domain_name); inet_ntop(server_name->h_addrtype,server_name->h_addr,*ip,32); }
参数解析,由于可能出现多个服务器的情况,通过参数解析可以访问到指定的树莓派服务器
opt=getopt(argc,argv,"xy"); if(opt!='x' && opt!='y') { printf("argument parsing failure:%s\n",strerror(errno)); return -1; } if(opt=='x') { dns("xxxxxxxx.com",&servip); } if(opt=='y') { dns("yyyyyyyy.com",&servip); }
3.使用socket函数初始化
server_fd = socket( AF_INET, SOCK_STREAM, 0); //返回一个文件描述符server_fd if(server_fd < 0) { printf("Fail to create a client socket [%d]: %s\n", server_fd, strerror(errno) ); return -1; } printf("creat a client socket[%d] successufully!\n",server_fd); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; if(opt == 'x') //参数解析制定端口 { servaddr.sin_port = htons(port=9033); printf("Get temperature from [%d]xxxxxxxx.com...\n",port); } if(opt == 'y') { servaddr.sin_port =htons(port=8034); printf("Get temperature form [%d]yyyyyyyy.com...\n",port); }
4.while循环
while(!g_stop) //g_stop是一个信号,收到信号时跳出while循环,程序结束 { getdatatime(datime); //获得时间的函数 ds18b20_get_temperature(&temp); //采样温度的函数 snprintf(buf,sizeof(buf),"%s/%s/%f℃ ",user,datime,temp); //下面四个函数是sqlite3数据库的数据存储 db=db_connect(db_name); //连接到数据库 db_create(); //创建表 db_insert(); //插入数据 db_select(); //显示表中数据 if(rv<0) { rv=connect(server_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));//连接到服务器 if(rv<0) { printf("connect to server[%s:%d] failure: %s\n",servip,port,strerror(errno)); close(rv); break; } printf("connect to server[%s:%d] successfully!\n",servip,port); } rv=write(server_fd,buf,strlen(buf)); //将字符串buf中的内容写给服务器 if(rv<0) { printf("write to server by server_fd [%d] failure:%s\n",server_fd,strerror(errno)); return -1; break; } printf("write to server by server_fd[%d] [%s]successfully!\n",server_fd,buf); rv=read(server_fd,buf,sizeof(buf)); //从服务器中读取内容 if(rv<0) { printf("read data from server by server[%d] failure:%s\n",server_fd,strerror(errno)); return -1; break; } else if(rv==0) { printf("server_fd [%d] get disconnect\n",server_fd); return -1; break; } else if(rv>0) { printf("read %d bytes data from server:%s\n",rv,buf); } sleep(60); //每隔一分钟循环一次 }
5.安装信号
void sig_handler(int signum){ if(SIGTERM==signum) { printf("SIGTERM signal detected\n"); g_stop=1; } else if(SIGALRM==signum) { printf("SIGALRM signal detected\n"); g_stop=1; } else if(SIGINT==signum) { printf("SIGINT signal detected\n"); g_stop=1; }}void signal_code(int signum){ if(SIGBUS==signum) { printf("SIGBUS signal detected\n"); } else if(SIGILL==signum) { printf("SIGILL signal detected\n"); } else if(SIGSEGV==signum) { printf("SIGSEGV signal detected\n"); } exit(1);}
调用
signal(SIGTERM,sig_handler); //kill命令默认发送的信号,程序终止 signal(SIGALRM,sig_handler); //alarm()系统调用发送的信号 signal(SIGINT,sig_handler); //Ctrl+C按键终止程序的信号 signal(SIGBUS,signal_code); //运行非本CPU相关编译器编译的程序 signal(SIGILL,signal_code); //强制杀死程序信号,任何程序都不可以捕捉该信号 signal(SIGSEGV,signal_code); //段错误系统给程序发送的信号
6.获取时间函数
int getdatatime(char *datime){ time_t seconds; struct tm *pTM; time(&seconds); pTM=localtime(&seconds); sprintf(datime,"%04d-%02d-%02d %02d:%02d:%02d\n",pTM->tm_year+1900,pTM->tm_mon+1,pTM->tm_mday,pTM->tm_hour,pTM->tm_min,pTM->tm_sec); return 0;}
7.温度采样函数,该树莓派上的温度传感器ds18b20测量到的温度会通过字符串的形式被记录到一个特定的文件夹下面,文件内容会随着温度的变化而变化。这时我们需要用到Linux下文件I/O的相关操作去获取温度值。
int ds18b20_get_temperature(float *temp){ char w1_path[50]="/sys/bus/w1/devices/"; //文件夹的路径 char chip[20]; char buf[128]; DIR *dirp; struct dirent *direntp; int fd=-1; char *ptr; int found=0; if(!temp) { return -1; } if((dirp=opendir(w1_path))==NULL) //打开相关文件夹 { printf("opendir error:%s\n",strerror(errno)); return -2; } while((direntp=readdir(dirp))!=NULL) if(strstr(direntp->d_name,"28-")) //通过readdir函数找到文件名是“”28-“开头的文件夹” { strcpy(chip,direntp->d_name); found=1; break; } } closedir(dirp); if(!found) { printf("can not find bs18b20 in %s\n",w1_path); return -3; } strncat(w1_path,chip,sizeof(w1_path)-strlen(w1_path)); strncat(w1_path,"/w1_slave",sizeof(w1_path)-strlen(w1_path)); if((fd=open(w1_path,O_RDONLY))<0) //打开记录温度的文件 { printf("open %s error:%s\n",w1_path,strerror(errno)); return -4; } if(read(fd,buf,sizeof(buf))<0) //读取到文件中指定位置的温度字符串 { printf("read %s error:%s\n",w1_path,strerror(errno)); return -5; } ptr=strstr(buf,"t="); if(!ptr) { printf("error:can not get temperature\n"); return -6; } ptr+=2; *temp=atof(ptr)/1000.0; //强制类型转换,除以1000是温度的真实值 close(fd); return 0;}
8.有关数据库的函数(这一块也不详解了,之后会单独写篇博客讲解,其实数据库应该放到服务器端的,哈哈)
int callback(void *notused,int argc,char **argv,char **name){ for(int i=0;i
以上是客户端的相关代码,具体可以参考本人码云上的代码
在Linux下运行结果: 进入sqlite3 数据库:项目需要准备:
1.保证pc端通过公网ip或者域名在SecureCRT上远程登录 2.在树莓派上安装好sqlite3数据库和相关函数接口 sudo apt-get install sqlite3和sudo apt-get install libsqlite3-dev 3.提前在树莓派连接的路由器上开通相应的端口号(防止端口号重复)后序:因为是本人第一次独自完成项目,难免会有很多瑕疵和不足,希望以后有更多锻炼的机会吧。之后也会把项目服务器端的相关代码和知识点介绍一下。
转载地址:http://hyhgn.baihongyu.com/