店铺网站怎么建,安徽观元建设有限公司网站,在线商城网站制作,wordpress获取指定目录的文章一、增加网络控制功能
实现需求TCP 心跳机制解决Soket异常断开问题
二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。
查看当前系统的TCP KeepAlive参数修改TCP KeepAlive参数
三、C语言实现TCP KeepAlive功能 四、setsockopt用于设置套接字选项的系…一、增加网络控制功能
实现需求TCP 心跳机制解决Soket异常断开问题
二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。
查看当前系统的TCP KeepAlive参数修改TCP KeepAlive参数
三、C语言实现TCP KeepAlive功能 四、setsockopt用于设置套接字选项的系统调用 五、代码实现 六、待定
一、增加网络控制功能
Linux网络编程TCP Socket编程实现过程 TCP Keepalive HOWTO
实现需求
Socket网络编程通过Socket远程接入控制垃圾识别功能。
TCP 心跳机制解决Soket异常断开问题
Socket客户端得断开情形无非就两种情况
客户端能够发送状态给服务器正常断开强制关闭客户端等客户端能够做出反应。客户端不能发送状态给服务器突然断网断电客户端卡死等客户端根本没机会做出反应服务器更不了解客户端状态导致服务器异常等待。
为了解决上述问题引入TCP心跳包机制 心跳包的实现心跳包就是服务器定时向客户端发送查询信息如果客户端有回应就代表连接正常类似于linux系统的看门狗机制。心跳包的机制有一种方法就是采用TCP_KEEPALIVE机制它是一种用于检测TCP连接是否存活的机制它的原理是在一定时间内没有数据往来时发送探测包给对方如果对方没有响应就认为连接已经断开。TCP_KEEPALIVE机制可以通过设置一些参数来调整如探测时间间隔、探测次数等。
二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。
在 Linux 内核中可以通过 sysctl 命令来查看和配置 TCP KeepAlive 参数。TCP KeepAlive 是一种机制用于在两个端点之间的连接空闲时检测连接的活跃性。
以下是一些与 TCP KeepAlive 相关的 sysctl 参数以及它们的含义
tcp_keepalive_time: 含义TCP KeepAlive 的开始时间。表示连接空闲的时间超过这个时间内核开始发送 KeepAlive 消息。默认值7200 秒2 小时
sysctl net.ipv4.tcp_keepalive_timetcp_keepalive_intvl: 含义两次 KeepAlive 消息之间的时间间隔。默认值75 秒
sysctl net.ipv4.tcp_keepalive_intvltcp_keepalive_probes: 含义在放弃连接之前发送的 KeepAlive 消息的次数。默认值9 次
sysctl net.ipv4.tcp_keepalive_probes这些参数的配置可以通过修改 /etc/sysctl.conf 文件来实现。例如要将 tcp_keepalive_time 设置为 600 秒可以在 /etc/sysctl.conf 中添加如下行
net.ipv4.tcp_keepalive_time 600然后可以运行以下命令使更改生效
sysctl -p请注意修改这些参数可能会对系统的整体性能产生影响因此在进行更改之前请仔细了解每个参数的含义以及它们的默认值。
查看当前系统的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_time3600三、C语言实现TCP KeepAlive功能
对于Socket而言可以在程序中通过socket选项开启TCP KeepAlive功能并配置参数。
对应的Socket选项分别为 SO_KEEPALIVE 、TCP_KEEPIDLE 、 TCP_KEEPCNT 、 TCP_KEEPINTVL 。
关于 setsockopt 函数的参数和相应的套接字选项以及协议的对应关系以及对应的描述。
setsockopt 参数描述对应的 Socket LevelSOL对应的协议ProtocolSO_KEEPALIVE启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。SOL_SOCKETIPPROTO_TCPTCP_KEEPIDLE指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间以秒为单位。SOL_TCPIPPROTO_TCPTCP_KEEPCNT指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。SOL_TCPIPPROTO_TCPTCP_KEEPINTVL指定个别保持活动探测之间的时间以秒为单位。SOL_TCPIPPROTO_TCPIPPROTO_TCPTCP传输控制协议的协议级别。用于设置特定于 TCP 的套接字选项。N/AIPPROTO_TCP
setsockopt 中参数与套接字选项和协议的关系。
Socket 选项和 TCP 参数
选项/参数描述SO_KEEPALIVE启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。TCP_KEEPIDLE指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间以秒为单位。TCP_KEEPCNT指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。TCP_KEEPINTVL指定个别保持活动探测之间的时间以秒为单位。 #
四、setsockopt用于设置套接字选项的系统调用
setsockopt 函数是用于设置套接字选项的系统调用。下面是 setsockopt 函数的基本格式
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);其中
sockfd套接字文件描述符。level选项所在的协议层。常用的有 SOL_SOCKET用于通用套接字选项和特定的协议层如 IPPROTO_TCP用于 TCP 协议选项。optname选项名称用于指定需要设置的具体选项。optval指向包含选项值的缓冲区的指针。optlen指定 optval 缓冲区的大小。
下面是一些常见的套接字选项和它们的作用 SO_KEEPALIVESOL_SOCKET 描述启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。 TCP_KEEPIDLESOL_TCP 描述指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间以秒为单位。 TCP_KEEPCNTSOL_TCP 描述指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。 TCP_KEEPINTVLSOL_TCP 描述指定个别保持活动探测之间的时间以秒为单位。 IPPROTO_TCP 描述TCP传输控制协议的协议级别。用于设置特定于 TCP 的套接字选项。
这些选项可用于控制套接字的行为如保持连接活动、调整发送和接收缓冲区的大小等。在使用时请注意确保提供正确的协议级别和选项名称。
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));五、代码实现
mysocket.h
#ifndef __MYSOCKET__H
#define __MYSOCKET__H#include stdio.h
#include stdlib.h
#include string.h
#include sys/types.h /* See NOTES */
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include netinet/tcp.h
#include errno.h
#include unistd.h#define IPADDR 192.168.1.254 //填写自己实际的ip地址
#define IPPORT 8192
#define BUF_SIZE 6int socket_init(const char *ipaddr, const char *port);#endifmysocket.c
#include mysocket.hint socket_init(const char *ipaddr, const char *port)
{int server_fd -1;struct sockaddr_in server_addr;memset(server_addr, 0, sizeof(struct sockaddr_in));// 创建服务器套接字server_fd socket(AF_INET, SOCK_STREAM, 0);if (server_fd -1) {perror(Socket creation failed);return -1;}// 设置服务器地址结构server_addr.sin_family AF_INET;server_addr.sin_port htons(port);
// server_addr.sin_addr.s_addr INADDR_ANY;inet_aton(ipaddr, server_addr.sin_addr);// 绑定服务器套接字if (bind(server_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) 0) {perror(Binding failed);return -1;}// 监听传入的连接请求if (listen(server_fd, 1) 0) { //只监听1个连接排队扔垃圾printf(Listening for incoming connections...\n);} else {perror(Listening failed);return -1;}return server_fd;
}main.c
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include errno.h
#include wiringPi.h
#include pthread.h#include uartTool.h
#include garbage.h
#include pwm.h
#include myoled.h
#include mysocket.hint serial_fd -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁用于线程之间的互斥访问// 判断进程是否在运行
static int detect_process(const char * process_name)
{int n -1; // 存储进程PID默认为-1FILE *strm;char buf[128] {0}; // 缓冲区// 构造命令字符串通过ps命令查找进程sprintf(buf, ps -ax | grep %s|grep -v grep, process_name);// 使用popen执行命令并读取输出if ((strm popen(buf, r)) ! NULL) {if (fgets(buf, sizeof(buf), strm) ! NULL) {printf(buf %s\n, buf); //打印缓存区的内容n atoi(buf); // 将进程ID字符串转换为整数printf(n %d\n, n); // 打印下进程的PID}}else {return -1; // popen失败} pclose(strm); // 关闭popen打开的文件流return n;
}// 获取语音线程
void *pget_voice(void *arg)
{unsigned char buffer[6] {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};int len 0;printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);// 串口未打开退出线程if (-1 serial_fd) {printf(%s|%s|%d: open serial failed\n, __FILE__, __func__, __LINE__);pthread_exit(0);}printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);// 循环读取串口数据while (1) {len my_serialGetstring(serial_fd, buffer);printf(%s|%s|%d, len %d\n, __FILE__, __func__, __LINE__, len);// 检测到特定数据发出信号唤醒其他线程if (len 0 buffer[2] 0x46) {printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);pthread_mutex_lock(mutex);buffer[2] 0x00;pthread_cond_signal(cond);pthread_mutex_unlock(mutex);system(WGET_CMD);}}pthread_exit(0);
}// 发送语音线程
void *psend_voice(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer (unsigned char *)arg;// 串口未打开退出线程if (-1 serial_fd) {printf(%s|%s|%d: open serial failed\n, __FILE__, __func__, __LINE__);pthread_exit(0);}// buffer不为空时通过串口发送数据(分类结果)if (NULL ! buffer) {my_serialSendstring(serial_fd, buffer, 6);}pthread_exit(0);
}// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{pthread_detach(pthread_self());unsigned char *buffer (unsigned char *)arg;// 根据垃圾类型控制PWMif (buffer[2] 0x43) { // 可回收垃圾printf(%s|%s|%d: buffer[2] 0x%x\n, __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}else if (buffer[2] 0x41) { // 干垃圾printf(%s|%s|%d: buffer[2] 0x%x\n, __FILE__, __func__, __LINE__, buffer[2]);pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}else if (buffer[2] 0x42) { // 湿垃圾printf(%s|%s|%d: buffer[2]0x%x\n, __FILE__, __func__, __LINE__,buffer[2]);pwm_write(PWM_WET_GARBAGE);delay(2000);pwm_stop(PWM_WET_GARBAGE);}else if (buffer[2] 0x44) { // 有害垃圾printf(%s|%s|%d: buffer[2]0x%x\n, __FILE__, __func__, __LINE__,buffer[2]);pwm_stop(PWM_HAZARDOUS_GARBAGE);delay(2000);pwm_write(PWM_HAZARDOUS_GARBAGE);}pthread_exit(0);
}// 在线程中显示 OLED
void *poled_show(void *arg)
{// 分离线程使其在退出时能够自动释放资源pthread_detach(pthread_self());// 初始化 OLEDmyoled_init();// 在 OLED 上显示垃圾分类结果oled_show(arg);// 退出线程 pthread_exit(0);
}// 垃圾分类线程
void *pcategory(void *arg)
{unsigned char buffer[6] {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};char *category NULL;pthread_t send_voice_tid, trash_tid, oled_tid;while (1) {printf(%s|%s|%d: \n, __FILE__, __func__, __LINE__);pthread_mutex_lock(mutex);pthread_cond_wait(cond, mutex);pthread_mutex_unlock(mutex);printf(%s|%s|%d: \n, __FILE__, __func__, __LINE__);buffer[2] 0x00;// 在执行wget命令之前添加调试输出printf(Executing wget command...\n);// 使用系统命令拍照system(WGET_CMD);// 在执行wget命令之后添加调试输出printf(Wget command executed.\n);// 判断垃圾种类if (0 access(GARBAGE_FILE, F_OK)) {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(trash_tid, NULL, psend_voice, (void *)buffer);// 开语音播报线程pthread_create(send_voice_tid, NULL, popen_trash_can, (void *)buffer);//oled显示线程pthread_create(oled_tid, NULL, poled_show, (void *)buffer);// buffer[2] 0x00;// 删除拍照文件remove(GARBAGE_FILE); }pthread_exit(0);
}void *pget_socket(void *arg)
{int server_fd -1; // 服务器 socket 文件描述符int client_fd -1; // 客户端 socket 文件描述符char buffer[6]; // 用于存储接收到的数据的缓冲区int nread -1; // 接收到的数据长度struct sockaddr_in client_addr; // 客户端地址信息结构体int clen sizeof(struct sockaddr_in);memset(client_addr, 0, sizeof(struct sockaddr_in));// 初始化服务器 socketserver_fd socket_init(IPADDR, IPPORT);printf(%s|%s|%d:server_fd %d\n, __FILE__, __func__, __LINE__, server_fd);if (-1 server_fd) {pthread_exit(0); // 初始化失败退出线程}sleep(3); // 等待 socket 初始化完成while (1) {// 接受客户端连接client_fd accept(server_fd, (struct sockaddr *)client_addr, clen);// 配置 TCP KeepAlive 参数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_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)keepalive, sizeof(keepalive));setsockopt(client_fd, SOL_TCP, TCP_KEEPIDLE, (void *)keepidle, sizeof(keepidle));setsockopt(client_fd, SOL_TCP, TCP_KEEPCNT, (void *)keepcnt, sizeof(keepcnt));setsockopt(client_fd, SOL_TCP, TCP_KEEPINTVL, (void *)keepintvl, sizeof(keepintvl));printf(%s|%s|%d: Accept a connection from %s:%d\n, __FILE__, __func__, __LINE__, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));if (client_fd -1) {perror(accept);continue; // 如果接受连接失败继续下一次循环}while (1) {memset(buffer, 0, sizeof(buffer));// 从客户端接收数据nread recv(client_fd, buffer, sizeof(buffer), 0); //n_read read(c_fd, buffer, sizeof(buffer));printf(%s|%s|%d:nread %d, buffer %s\n, __FILE__, __func__, __LINE__, nread, buffer);// 根据接收到的数据执行相应的操作if (nread 0) {if (strstr(buffer, open)) {pthread_mutex_lock(mutex);pthread_cond_signal(cond);pthread_mutex_unlock(mutex);}if (strstr(buffer, k1)) {pwm_write(PWM_RECOVERABLE_GARBAGE);delay(2000);pwm_stop(PWM_RECOVERABLE_GARBAGE);}if (strstr(buffer, k2)) {pwm_write(PWM_GARBAGE);delay(2000);pwm_stop(PWM_GARBAGE);}if (strstr(buffer, k3)) {pwm_write(PWM_WET_GARBAGE);delay(2000);pwm_stop(PWM_WET_GARBAGE);}if (strstr(buffer, k4)) {pwm_stop(PWM_HAZARDOUS_GARBAGE);delay(2000);pwm_write(PWM_HAZARDOUS_GARBAGE);}}else if (0 nread || -1 nread) {break; // 如果接收到的数据长度为 0 或者出错跳出循环}}close(client_fd); // 关闭客户端连接}pthread_exit(0); // 退出线程
}int main(int argc, char *argv[])
{int ret -1;int len 0;char *category NULL;pthread_t get_voice_tid, category_tid, get_socket_tid;wiringPiSetup();// 初始化串口和垃圾分类模块garbage_init ();// 用于判断mjpg_streamer服务是否已经启动ret detect_process (mjpg_streamer);if (-1 ret) {printf(detect process failed\n);goto END;}// 打开串口serial_fd my_serialOpen (SERIAL_DEV, BAUD);if (-1 serial_fd) {printf(open serial failed\n);goto END;}// 开语音线程printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);pthread_create(get_voice_tid, NULL, pget_voice, NULL);// 开网络线程printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);pthread_create(get_socket_tid, NULL, pget_socket, NULL);// 开阿里云交互线程printf(%s|%s|%d\n, __FILE__, __func__, __LINE__);pthread_create(category_tid, NULL, pcategory, NULL);// 创建互斥锁和条件变量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();return 0;
}六、待定