基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能

开始实现网络控制功能之前可以先看看下面这篇博文对网络通信的概念和流程有个清晰的认知。

https://mp.csdn.net/mp_blog/creation/editor/134140612xx

项目源码:

https://pan.baidu.com/s/1xHPFHsKiUMN1P0OphbDh9g
提取码:sail

项目流程图:

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第1张图片

代码编写:

增加两个文件:sokect.h和sokect.c,修改main.c中的代码

1、增加一个线程用于网络通信

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第2张图片

2、编写线程入口函数

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第3张图片

3、调试

编译运行后打开网络调试助手(需要关闭网络防火墙,有火绒安全的也需要关闭)

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第4张图片

我们发送1,显示读取到一个字节n_read=1,内容buffer=1

发送“open” 指令可开始垃圾识别

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第5张图片

 项目效果:

异常情况及解决方法-TCP心跳包

Socket客户端得断开情形无非就两种情况:

1.客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。

2.客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应,服务器 更不了解客户端状态,导致服务器异常等待。(模拟断网断电)

为了解决上述问题,引入TCP心跳包机制

TCP心跳包机制:

心跳包的实现,心跳包就是服务器定时向客户端发送查询信息,如果客户端有回应就代表连接正常,类似于linux系统的看门狗机制。心跳包的机制有一种方法就是采用TCP_KEEPALIVE机制,它是一种用于检测TCP连接是否存活的机制,它的原理是在一定时间内没有数据往来时,发送探测包给对方,如果对方没有响 应,就认为连接已经断开。TCP_KEEPALIVE机制可以通过设置一些参数来调整,如探测时间间隔、探测次数等。

Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。 查看当前系统的TCP KeepAlive参数

sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_intvl

基于OrangePi Zero 2实现垃圾分类智能垃圾桶项目(9)增加网络控制功能_第6张图片

 修改TCP KeepAlive参数

sysctl net.ipv4.tcp_keepalive_time=3600

C语言实现TCP KeepAlive功能 

对于Socket而言,可以在程序中通过socket选项开启TCP KeepAlive功能,并配置参数。对应的Socket选项 分别为 SO_KEEPALIVE 、 TCP_KEEPIDLE 、 TCP_KEEPCNT 、 TCP_KEEPINTVL 。

需要包含头文件:

#include 
int keepalive = 1;         // 开启TCP KeepAlive功能

int keepidle = 5;       // tcp_keepalive_time 3s内没收到数据开始发送心跳包

int keepcnt = 3;           // tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒

int keepintvl = 3;         // tcp_keepalive_intvl 每3s发送一次心跳包

setsockopt(client_socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive)); setsockopt(client_socketfd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle)); setsockopt(client_socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt)); setsockopt(client_socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl));

sokect.h

#ifndef __SOCKET__H
#define __SOCKET__H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define IPADDR "172.20.10.11"
#define IPPORT "8090"//不要用8080,因为摄像头的进程已经占用了这个端口

int socket_init(const char *ipaddr,const char *ipport);
#endif

sokect.c

#include "socket.h"

int socket_init(const char *ipaddr,const char *ipport){
    int s_fd=-1;
    int ret=-1;
    struct sockaddr_in s_addr;
    memset(&s_addr,0,sizeof(struct sockaddr_in));
    /*typedef struct sockaddr_in {
    sa_family_t sin_family; //AF_INET 
    in_port_t sin_port;    // 端口号 
    struct in_addr sin_addr; // IP 地址 
    } sockaddr_in;*/
    
    //socket
    s_fd=socket(AF_INET,SOCK_STREAM,0);//套接字所使用的协议簇:AF_INET(IPV4),套接字的类型:流式套接字SOCK_STREAM
    if(s_fd==-1){
        perror("socket");
        exit(-1);
    }

    s_addr.sin_family=AF_INET;
    s_addr.sin_port=htons(atoi(ipport));//htons()函数的作用是将主机字节顺序转换成网络字节顺序(大端模式)
    inet_aton(ipaddr,&s_addr.sin_addr);//用于解析IPv4地址字符串,并将其转换成二进制格式。

    //bind
    ret=bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
   if(ret==-1){
        perror("bind");
        return -1;
    }

    //listen
    ret=listen(s_fd,1);//参数二指定等待连接队列的最大长度
    if(ret==-1){
        perror("listen");
        return -1;
    }

    return s_fd;

}

main.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"
#include "socket.h"

static int detect_process(const char *process_name){
    int n=-1;
    FILE *strm;
    char buf[128]={0};
    sprintf(buf,"ps -ax | grep %s|grep -v grep",process_name);
    if((strm=popen(buf,"r"))!=NULL){
        if(fgets(buf,sizeof(buf),strm)!=NULL){
            n=atoi(buf);
        }
    }
    else{
        return -1;
    }
    pclose(strm);
    return n;
}

#if 0
int main(int argc,char *argv[]){
    int serial_fd =-1;
    unsigned char buffer[6]={0xAA,0X55,0X00,0X00,0X55,0XAA};//串口发送数据起始位是AA 55,停止位是55 AA
    //初始化阿里云接口
    garbage_init();//打开Python解释器
    int ret=-1;
    ret=detect_process("mjpg_streamer");
    if(-1==ret){
        goto END;
    }

    //orangepi串口初始化
    serial_fd=myserialOpen(SERIAL_DEV,BAUD);
    if(serial_fd==-1){//判断是否打开成功
        goto END;
    }

    //orangepi读取语音模块数据

    while(1){
        int len=0;//用于存放串口接收数据函数的返回值
        //int serialGetstring (const int fd, unsigned char *buffer);
        len=serialGetstring(serial_fd,buffer);
        if(len>0 && buffer[2]==0x46){//判断是否接收到开始识别指令
            buffer[2]=0x00;//接收到开始识别指令后清零
            system(WGET_CMD);//在终端输出拍照指令使摄像头拍照
            if(access(GARBAGE_FILE,F_OK)==0){//判断照片是否存在
            char *category=NULL;//用于存储返回值(垃圾类型)
            //char *garbage_category(char *category);阿里云接口函数
            category=garbage_category(category);
                if(strstr(category,"干垃圾")){
                    buffer[2]=0x41;
                }else if(strstr(category,"湿垃圾")){
                    buffer[2]=0x42;
                }else if(strstr(category,"可回收垃圾")){
                    buffer[2]=0x43;
                }else if(strstr(category,"有害垃圾")){
                    buffer[2]=0x44;
                }else{
                    buffer[2]=0x45;
                }
            }else{
                buffer[2]=0x45;
            }
            //void serialSendstring (const int fd, const unsigned char *s,int len)
            serialSendstring(serial_fd,buffer,6);//将内容回传给语音模块,用于播报垃圾类型
            if(buffer[2]==0x43){

                pwm_start(PWM_RWCYCLABEL);
                delay(5000);
                pwm_stop(PWM_RWCYCLABEL);

            }
            buffer[2]=0x00;
            remove(GARBAGE_FILE);//清除本次照片,为下一次拍照留空间
        }

    }
    close(serial_fd);

END:
    garbage_final();//关闭(释放)Python解释器
    return 0;
}
#endif

int serial_fd =-1;//全局变量,在线程函数里面也需要用到fd
pthread_cond_t  cond;//互斥锁
pthread_mutex_t mutex;//条件变量
pthread_t get_voice_tid,category_tid,get_socket_tid;

void *pget_voice(void *arg){
    int len=0;
    unsigned char buffer[6]={0xAA,0X55,0X00,0X00,0X55,0XAA};//串口发送数据起始位是AA 55,停止位是55 AA
    if(serial_fd==-1){//判断是否打开成功
        printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);
        pthread_exit(0);
    }
    while(1){
        len=serialGetstring(serial_fd,buffer);
        if(len>0 && buffer[2]==0x46){//判断是否接收到开始识别指令
            pthread_mutex_lock(&mutex);//加锁
            buffer[2]=0x00;
            pthread_cond_signal(&cond);//触发
            pthread_mutex_unlock(&mutex);//解锁
        }
    }
    pthread_exit(0);
}

void *psend_voice(void *arg){
    pthread_detach(pthread_self());//与pcategory父线程分离,防止父线程等待时间过长,子线程执行完后自行释放资源
    unsigned char *buffer=(unsigned char *)arg;
    if(serial_fd==-1){//判断是否打开成功
        printf("%s|%s|%d:open serial failed\n",__FILE__,__func__,__LINE__);
        pthread_exit(0);
    }

    if(NULL!=buffer){
        serialSendstring(serial_fd,buffer,6);//将内容回传给语音模块,用于播报垃圾类型
        printf("Serial get string success!\n");
    }else{
        printf("%s|%s|%d:sendstring NULL!\n",__FILE__,__func__,__LINE__);
    }
    pthread_exit(0);
}

void *popen_trash(void *arg){
    pthread_detach(pthread_self());//与pcategory父线程分离,防止父线程等待时间过长,子线程执行完后自行释放资源
    unsigned char *buffer=(unsigned char *)arg;
    if(NULL!=buffer){
    if(buffer[2]==0x43){
                pwm_start(PWM_RWCYCLABEL);
                delay(2000);
                pwm_stop(PWM_RWCYCLABEL);

            }
    }
    pthread_exit(0);
}

void *pshow_oled(void *arg){//oled显示垃圾类型
    pthread_detach(pthread_self());
    myoled_init();

    oled_show(arg);
    pthread_exit(0);
}

void *pcategory(void *arg){
    pthread_t send_voice_tid,trash_tid,oled_tid;//语音播报、垃圾桶开盖和OLED显示线程ID
    char *category=NULL;//用于存储返回值(垃圾类型)
    unsigned char buffer[6]={0xAA,0x55,0x00,0x00,0x55,0xAA};//串口发送数据起始位是AA 55,停止位是55 AA
        while(1){
            pthread_mutex_lock(&mutex);//加锁
            pthread_cond_wait(&cond,&mutex);//等待触发信号
            pthread_mutex_unlock(&mutex);//解锁
            //buffer[2]=0x00;
            system(WGET_CMD);//在终端输出拍照指令使摄像头拍照
            if(access(GARBAGE_FILE,F_OK)==0){//判断照片是否存在
            
            //char *garbage_category(char *category);阿里云接口函数
            category=garbage_category(category);
                if(strstr(category,"干垃圾")){
                    buffer[2]=0x41;
                }else if(strstr(category,"湿垃圾")){
                    buffer[2]=0x42;
                }else if(strstr(category,"可回收垃圾")){
                    buffer[2]=0x43;
                }else if(strstr(category,"有害垃圾")){
                    buffer[2]=0x44;
                }else{
                    buffer[2]=0x45;
                }
            }else{
                buffer[2]=0x45;
            }
            //语音播报线程
            pthread_create(&send_voice_tid,NULL,psend_voice,(void *)buffer);
            //垃圾桶开盖线程
            pthread_create(&trash_tid,NULL,popen_trash,(void *)buffer);
            //OLED显示进程
            pthread_create(&oled_tid,NULL,pshow_oled,(void *)buffer);
            //因为做了父子线程分离,所以无需执行pthread_join()释放资源

            remove(GARBAGE_FILE);//清除本次照片,为下一次拍照留空间 
        }
        pthread_exit(0);
}

void *pget_socket(void *arg){
    int s_fd=-1;
    int c_fd=-1;
    char buffer[6];
    int n_read=-1;
    struct sockaddr_in c_addr;
    memset(&c_addr,0,sizeof(struct sockaddr_in));

    s_fd=socket_init(IPADDR,IPPORT);
    if(-1==s_fd){
    printf("%s|%s|%d:s_fd=%d\n", __FILE__, __func__, __LINE__, s_fd);
    pthread_exit(0);
    }
    int len=sizeof(struct sockaddr_in);

    while(1){
        c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&len);
        int keepalive = 1; // 开启TCP KeepAlive功能
        int keepidle = 5;// tcp_keepalive_time 3s内没收到数据开始发送心跳包
        int keepcnt = 3;// tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒
        int keepintvl = 3; // tcp_keepalive_intvl 每3s发送一次心跳包

        setsockopt(c_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
        setsockopt(c_fd, SOL_TCP, TCP_KEEPIDLE, (void *) &keepidle, sizeof (keepidle));
        setsockopt(c_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof (keepcnt));
        setsockopt(c_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof (keepintvl));

        printf("%s|%s|%d: Accep aconnection from %s:%d\n",__FILE__,__func__,__LINE__,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
        if(c_fd==-1){
            perror("accept");
            continue;
        }
        while(1){
            memset(buffer,0,sizeof(buffer));
            n_read=recv(c_fd,buffer,sizeof(buffer),0);
            printf("%s|%s|%d:nread=%d, buffer=%s\n", __FILE__, __func__, __LINE__, n_read, buffer);
            if (n_read > 0){
                if (strstr(buffer, "open")){
                    pthread_mutex_lock(&mutex);
                    pthread_cond_signal(&cond);
                    pthread_mutex_unlock(&mutex);
                }
            }
                else if(0 == n_read || -1 == n_read){
                    break;
                }
        }
        close(c_fd);
    }

    pthread_exit(0);
}

int main(int argc,char *argv[]){
    
    unsigned char buffer[6]={0xAA,0X55,0X00,0X00,0X55,0XAA};//串口发送数据起始位是AA 55,停止位是55 AA
    //初始化阿里云接口
    garbage_init();//打开Python解释器
    int ret=-1;
    ret=detect_process("mjpg_streamer");
    if(-1==ret){
        printf("detect process failed\n");
        goto END;
    }

    wiringPiSetup();
    
    //orangepi串口初始化
    serial_fd=myserialOpen(SERIAL_DEV,BAUD);
    if(serial_fd==-1){//判断是否打开成功
    printf("opne serial failed!\n");
        goto END;
    }
    
    //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
    //void *(*start_routine) (void *), void *arg);
    //语音模块线程
    pthread_create(&get_voice_tid,NULL,pget_voice,NULL);
    //阿里云线程
    pthread_create(&category_tid,NULL,pcategory,NULL);
    //网络通信线程
    pthread_create(&get_socket_tid,NULL,pget_socket,NULL);
    //int pthread_join(pthread_t thread, void **retval);
    pthread_join(get_voice_tid,NULL);
    pthread_join(category_tid,NULL);
    pthread_join(get_socket_tid,NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    close(serial_fd);

END:
    garbage_final();//关闭(释放)Python解释器
    return 0;
    return 0;
}

你可能感兴趣的:(网络,php,开发语言)