前言:放寒假之前收到了老师布置的的这个项目实战,能够实现一个项目完整的功能。之前上课有学习一些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<argc;i++)
{
printf("%s=%s\n",name[i],argv[i]?argv[i]:"NULL");
}
printf("\n");
return 0;
}
sqlite3 *db_connect(char *db_name)
{
int rc;
rc=sqlite3_open(db_name,&db);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"sql error:%s\n",sqlite3_errmsg(db));
}
else
{
printf("open sqlite successfully!\n");
}
return db;
}
void db_create()
{
int rc;
char *error=0;
char *sql="CREATE TABLE IF NOT EXISTS TEMP(""ID INT PRIMARY KEY,"
"USER CHAR(10),"
"DATIME CHAR(50),"
"TEMPERATURN CHAR(10));";
rc=sqlite3_exec(db,sql,callback,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"SQL error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("create table successfully!\n");
}
}
void db_insert()
{
int rc;
char *error;
char datime[32];
float temp;
char *user="liushoujin";
char *sql=(char *)malloc(256);
getdatatime(datime);
ds18b20_get_temperature(&temp);
snprintf(sql,256,"insert into TEMP(USER,DATIME,TEMPERATURN)values('%s','%s','%f');",user,datime,temp);
rc=sqlite3_exec(db,sql,callback,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"SQL error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("insert successfully!\n");
}
}
void db_select()
{
int rc;
char *error;
char *strsql="select * from TEMP;";
rc=sqlite3_exec(db,strsql,callback,NULL,&error);
if(rc!=SQLITE_OK)
{
fprintf(stderr,"SQL error:%s\n",error);
sqlite3_close(db);
sqlite3_free(error);
exit(1);
}
else
{
printf("select successfully!\n");
}
}
以上是客户端的相关代码,具体可以参考本人码云上的代码
https://gitee.com/lsj123456/temperature_project.git
在Linux下运行结果:
进入sqlite3 数据库:
项目需要准备:
1.保证pc端通过公网ip或者域名在SecureCRT上远程登录
2.在树莓派上安装好sqlite3数据库和相关函数接口
sudo apt-get install sqlite3和sudo apt-get install libsqlite3-dev
3.提前在树莓派连接的路由器上开通相应的端口号(防止端口号重复)
后序:因为是本人第一次独自完成项目,难免会有很多瑕疵和不足,希望以后有更多锻炼的机会吧。之后也会把项目服务器端的相关代码和知识点介绍一下。