网络socket编程之温度实时监控上报项目(服务器端)

本文介绍了一个支持多客户端并发访问的温度上报服务器实现方案,采用多线程方式处理客户端请求,并详细展示了线程创建及管理的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:前面讲了网络socket编程流程和温度上报的客户端,服务器端的功能和客户端有很多相同,我就不同的地方说明一下。

  1. 程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;(服务器端)
  2. .程序能够捕捉kill信号正常退出;(服务器客户端都有)
  3. 服务器要支持多个客户端并发访问,可以选择多路复用、多进程或多线程任意一种实现;(我选择的是多线程,之后会有一篇博客讲解它们之间的区别)
  4. 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如RPI0001/2019-01-05 11:40:30/30.0C”;(之前错写在客户端了,这次就不说了,下次单独写博客)
    该数据库要求一个客户端创建一个表,表名为客户端树莓派的ID,如RPI0001, 表里面有三个字段,分别为ID(自动增长的主键)、时间、温度值。

先直接上部分代码(与客户端重复的不再介绍):
源码:https://gitee.com/lsj123456/temperature_project.git

int thread_start(pthread_t *thread_id,THREAD_BODY *thread_workbody,void *thread_arg)
{
      int                      rv=-1;
      pthread_attr_t           thread_attr;    //定义线程的属性变量,该属性的类型定义如下:
                                                              /*typedef struct
                                                              {
                                                                 int detachstate; 线程的分离状态
                                                                 int schedpolicy; 线程调度策略
           													     struct sched_param schedparam; 线程的调度参数
															     int inheritsched; 线程的继承性
															     int scope; 线程的作用域
															     size_t guardsize; 线程栈末尾的警戒缓冲区大小
															     int stackaddr_set;
															     void * stackaddr; 线程栈的位置
 																 size_t stacksize; 线程栈的大小
															 }pthread_attr_t;*/

 
       if(pthread_attr_init(&thread_attr))//对该属性设置前,先初始化它
       {
                 printf("pthread_attr_init()failure:%s\n",strerror(errno)); 
                 pthread_attr_destroy(&thread_attr);
       }
        
       if(pthread_attr_setstacksize(&thread_attr,120*1024))//设置线程栈的大小为120K
       {
                 printf("pthread_attr_setstacksize()failure:%s\n",strerror(errno));
                 pthread_attr_destroy(&thread_attr);
       } 
       if(pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED))//设置线程的属性为分离状态
       {
                 printf("pthread_attr_setdatachstate()failure:%s\n",strerror(errno));
                 pthread_attr_destroy(&thread_attr);
       }
       if(pthread_create(thread_id,&thread_attr,thread_workbody,thread_arg))//创建一个线程
	   {
	             printf("create thread failure:%s\n",strerror(errno));
                 pthread_attr_destroy(&thread_attr);
       }                             //第一个参数是一个Pthread-t类型的指针,用来返回线程的ID
             rv=0;                  //第二个参数是线程的属性
             return rv;               //第三个参数是创建的子线程要执行的任务
}                                          //第四个参数arg就是传给了所调用的函数的参数,如果有多个参数需要传递给子线程则需要封装到一个结构体里面
 
void *thread_worker(void *ctx)//线程实现的功能
{
        int         clifd;
        int         rv;
        char        buf[1024];
        int         i;
         
            clifd=(int)ctx;
            printf("child thread start to communicate with socket client...\n");
         while(!g_stop)
          { 
            memset(buf,0,sizeof(buf));
            rv=read(clifd,buf,sizeof(buf));
            if(rv<0)
            {
                    printf("read data from server by server[%d] failure:%s\n",clifd,strerror(errno));
                    close(clifd);
                    pthread_exit(NULL);
            }
            else if(rv==0)
            {
                 printf("server_fd [%d] get disconnect\n",clifd);
                 close(clifd);
                 pthread_exit(NULL);
            }
            {
                printf("read %d bytes data from server:%s\n",rv,buf);
            }
              rv=write(clifd,buf,rv);
            if(rv<0)
            {
                    printf("write to server by server_fd [%d] failure:%s\n",clifd,strerror(errno));
                    close(clifd);
                    pthread_exit(NULL);
            }
                    printf("write to server by server_fd[%d] [%s]successfully!\n",clifd,buf);
      }
}

注意对于多线程编程在编译时,一定要加上-lpthread 选项告诉链接器在链接的时候要连接pthread库:

iot@Public_RPi:~/liushoujin/temperature_project $ gcc temper.server2.c  -lpthread

主函数部分:

int main(int argc,char **argv)
{
        int                    opt=-1;
        int                    server_fd=-1;
        int                    rv=-1;
        int                    port;
        char                   buf[256];
        float                  temp; 
        char                   datatime[32]; 
        struct sockaddr_in     servaddr;
        struct sockaddr_in     cliaddr;
        char                   *servip=(char *)malloc(ip_len);
        char                   *user="liushoujin";
        char                   *progname=basename(argv[0]);
        int                    clifd=-1;
        int                    on=1;
     	int                    log_fd=-1;
        socklen_t              len;
        pthread_t              tid;     
  
        opt=getopt(argc,argv,"xy");
        if(opt!='x' && opt!='y')
        {
            printf("argument parsing failure:%s\n",strerror(errno));
            return -1;
        }
        if(opt=='x')
        {
            dns("master.iot-yun.com",&servip);
        }
        if(opt=='y')
        {
            dns("studio.iot-yun.com",&servip);
        }
        server_fd = socket( AF_INET, SOCK_STREAM, 0);
            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]master.iot-yun.com...\n",port);
             }
             if(opt == 'y')
             {
             servaddr.sin_port =htons(port=8034);
             printf("Get temperature form studio.iot-yun.com...\n");
             }
             servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
             signal(SIGTERM,sig_handler);
             signal(SIGALRM,sig_handler);
             signal(SIGINT,sig_handler);
     
             setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));	//解决服务器程序停止后立刻运行,出现(端口)被占用的问题
             rv=bind(server_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
             if(rv<0)
             {
                  printf("socket[%d] bind on port[%d] failure:%s\n",server_fd,port,strerror(errno));
                  return -1;
             }
             listen(server_fd,13);
             printf("start to listen port[%d]\n",port);
             
	     log_fd = open("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);   //标准输出、标准出错重定向
	     
	     if ((daemon(1, 1)) < 0)//守护进程,程序后台运行
	     {
		     printf("Deamon failure : %s\n", strerror(errno));
		     return 0;
	     }
              
             while(!g_stop)
             {
                 getdatatime(datatime);
                 ds18b20_get_temperature(&temp);
                 snprintf(buf,sizeof(buf),"%s/%s/%f",user,datatime,temp);
                 clifd=accept(server_fd,(struct sockaddr *)&cliaddr,&len);
               if(clifd<0) 
               { 
                  printf("accept new client failure:%s\n",strerror(errno));
                  continue;
               }
                  printf("accept new clent[%s:%d] successfully!\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
                 thread_start(&tid,thread_worker,(void *)clifd);//线程开始工作
            
             }
                 syslog(LOG_NOTICE,"program %s stop running...\n",progname);
                 closelog();
                 close(clifd);
}     

主函数中其他函数的实现之前博客都有介绍

项目发现问题:
1.多线程存在线程之间共享一个资源的问题,有待解决
2. 尽管我增加了setsockopt()这个函数好像只解决端口被占用的问题,解决不了地址被占用的问题。因为服务器端一直在后台运行,当我修改服务器端程序在编译时,地址被占用。此时可以使用命令netstat -tlnp查看端口号被那个进程占用,使用命令kill -9 PID强制杀死进程。然后就可以修改编译执行服务器端。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值