网络socket编程之温度实时监控上报项目(客户端)

前言:放寒假之前收到了老师布置的的这个项目实战,能够实现一个项目完整的功能。之前上课有学习一些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);
  }
  1. 参数解析,由于可能出现多个服务器的情况,通过参数解析可以访问到指定的树莓派服务器

      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.提前在树莓派连接的路由器上开通相应的端口号(防止端口号重复)

后序:因为是本人第一次独自完成项目,难免会有很多瑕疵和不足,希望以后有更多锻炼的机会吧。之后也会把项目服务器端的相关代码和知识点介绍一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值