开始实现网络控制功能之前可以先看看下面这篇博文对网络通信的概念和流程有个清晰的认知。
https://mp.csdn.net/mp_blog/creation/editor/134140612xx
https://pan.baidu.com/s/1xHPFHsKiUMN1P0OphbDh9g
提取码:sail
增加两个文件:sokect.h和sokect.c,修改main.c中的代码
编译运行后打开网络调试助手(需要关闭网络防火墙,有火绒安全的也需要关闭)
我们发送1,显示读取到一个字节n_read=1,内容buffer=1
发送“open” 指令可开始垃圾识别
Socket客户端得断开情形无非就两种情况:
1.客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。
2.客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应,服务器 更不了解客户端状态,导致服务器异常等待。(模拟断网断电)
为了解决上述问题,引入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
修改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));
#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
#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;
}
#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;
}