esp32s3中使用双通道通信解决TCP粘包问题

2024-04-21 06:36

本文主要是介绍esp32s3中使用双通道通信解决TCP粘包问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在使用esp32 idf例程中的tcp_server和tcp_client通信测试时发现,

在tcp_server端,接收到一帧数据之后必须马上回复至少一个字节,才能保证每帧数据不粘包,

如果不回复操作,300ms以内的通信时延会导致tcp严重粘包,后续解析这些数据费时费力,

可能跟lwip的回环读写机制有关,这严重打乱了双向通信逻辑。

换一种方式,使用udp广播来作为数据传输通道,使用tcp连接来做状态检测,这样就可以

避免粘包问题。

udp广播服务如下


/*** udp服务器,高速通信,控制器控制命令传输通道(不需要应答的)* */
static void udp_server_task(void *pvParameters)
{unsigned char rx_buffer[128];char addr_str[128];int addr_family = (int)pvParameters;//ipv4 or ipv6int ip_protocol = 0;struct sockaddr_in6 dest_addr;while (1) {if (addr_family == AF_INET) {struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;/**** 接收广播地址:* 192.168.100.1* 192.168.100.255* 255.255.255.255*/dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);dest_addr_ip4->sin_family = AF_INET;dest_addr_ip4->sin_port = htons(UDP_SERVER_PORT);ip_protocol = IPPROTO_IP;} else if (addr_family == AF_INET6) {bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));dest_addr.sin6_family = AF_INET6;dest_addr.sin6_port = htons(UDP_SERVER_PORT);ip_protocol = IPPROTO_IPV6;}global_udpsock_handle = socket(addr_family, SOCK_DGRAM, ip_protocol);if (global_udpsock_handle < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);/**因为正在创建的时候网络可能还没有完全连接上,不能退出*/vTaskDelay(100 / portTICK_PERIOD_MS);//100mscontinue;}ESP_LOGI(TAG, "UDP Socket created");#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)int enable = 1;lwip_setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
#endif#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)if (addr_family == AF_INET6) {// Note that by default IPV6 binds to both protocols, it is must be disabled// if both protocols used at the same time (used in CI)int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));}
#endif
//        // Set timeout 接收广播数据超时时间
//        struct timeval timeout;
//        timeout.tv_sec = 10;
//        timeout.tv_usec = 0;
//        setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);/*** E (106995) BOT-TAG: Socket unable to bind: errno 112* */int opt = 1;setsockopt(global_udpsock_handle, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));int err = bind(global_udpsock_handle, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err < 0) {ESP_LOGE(TAG, "udp Socket unable to bind: errno %d", errno);close(global_udpsock_handle);/**因为正在创建的时候网络可能还没有完全连接上,不能退出*/vTaskDelay(100 / portTICK_PERIOD_MS);//100mscontinue;}ESP_LOGI(TAG, "udp Socket bound, port %d",UDP_SERVER_PORT);struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6socklen_t socklen = sizeof(source_addr);#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)struct iovec iov;struct msghdr msg;struct cmsghdr *cmsgtmp;u8_t cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];iov.iov_base = rx_buffer;iov.iov_len = sizeof(rx_buffer);msg.msg_control = cmsg_buf;msg.msg_controllen = sizeof(cmsg_buf);msg.msg_flags = 0;msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_name = (struct sockaddr *)&source_addr;msg.msg_namelen = socklen;
#endifESP_LOGI(TAG, "udp start Waiting for data");/*** 重新启动udp server可以清除之前接收的缓存数据,防止对下一个连接影响* */while (1) {//tcp没有有效连接则处于睡眠等待状态if(global_tcpsock_handle <= 0){vTaskDelay(100 / portTICK_PERIOD_MS);//100mscontinue;}#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)int len = recvmsg(global_udpsock_handle, &msg, 0);
#elseint len = recvfrom(global_udpsock_handle, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
#endif// Error occurred during receivingif (len < 0) {ESP_LOGE(TAG, "udp recvfrom failed: errno %d", errno);/*** E (615075) BOT-TAG: udp Socket unable to bind: errno 9* *///sock关闭后稍等一下,不用立即去创建和bindvTaskDelay(100 / portTICK_PERIOD_MS);break;}// Data receivedelse {// Get the sender's ip address as stringif (source_addr.ss_family == PF_INET) {inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)for ( cmsgtmp = CMSG_FIRSTHDR(&msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&msg, cmsgtmp) ) {if ( cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO ) {struct in_pktinfo *pktinfo;pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsgtmp);ESP_LOGI(TAG, "dest ip: %s\n", inet_ntoa(pktinfo->ipi_addr));}}
#endif} else if (source_addr.ss_family == PF_INET6) {inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);}//rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string...//ESP_LOGI(TAG, "udp Received %d bytes from %s:", len, addr_str);//ESP_LOGI(TAG, "%s", rx_buffer);//print0x(rx_buffer,len);cmd_resolve_high_speed(rx_buffer, len);//                int err = sendto(global_udpsock_handle, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
//                if (err < 0) {
//                    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
//                    break;
//                }}}//        if (global_udpsock_handle != -1) {
//            ESP_LOGE(TAG, "Shutting down socket and restarting...");
//            shutdown(global_udpsock_handle, 0);
//            close(global_udpsock_handle);
//        }}vTaskDelete(NULL);
}

tcp状态监听服务如下


/*** tcp服务端,慢速通道,处理维护心跳包* */
static void tcp_server_task(void *pvParameters)
{char addr_str[128];int addr_family = (int)pvParameters;//ipv4 or ipv6int ip_protocol = 0;int keepAlive = 1;int option = 1;int keepIdle = KEEPALIVE_IDLE;int keepInterval = KEEPALIVE_INTERVAL;int keepCount = KEEPALIVE_COUNT;struct sockaddr_storage dest_addr;#ifdef CONFIG_EXAMPLE_IPV4if (addr_family == AF_INET) {struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);dest_addr_ip4->sin_family = AF_INET;dest_addr_ip4->sin_port = htons(TCP_SERVER_PORT);ip_protocol = IPPROTO_IP;}
#endif
#ifdef CONFIG_EXAMPLE_IPV6if (addr_family == AF_INET6) {struct sockaddr_in6 *dest_addr_ip6 = (struct sockaddr_in6 *)&dest_addr;bzero(&dest_addr_ip6->sin6_addr.un, sizeof(dest_addr_ip6->sin6_addr.un));dest_addr_ip6->sin6_family = AF_INET6;dest_addr_ip6->sin6_port = htons(PORT);ip_protocol = IPPROTO_IPV6;}
#endifint listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);if (listen_sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);vTaskDelete(NULL);return;}int opt = 1;setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)// Note that by default IPV6 binds to both protocols, it is must be disabled// if both protocols used at the same time (used in CI)setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endifESP_LOGI(TAG, "tcp Socket created");int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err != 0) {ESP_LOGE(TAG, "tcp Socket unable to bind: errno %d", errno);ESP_LOGE(TAG, "IPPROTO: %d", addr_family);goto CLEAN_UP;}ESP_LOGI(TAG, "tcp Socket bound, port %d", TCP_SERVER_PORT);err = listen(listen_sock, 1);if (err != 0) {ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);goto CLEAN_UP;}while (1) {ESP_LOGI(TAG, "Socket listening");//建立握手随机数校验标志generate_com_check();hp_check_load();struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6socklen_t addr_len = sizeof(source_addr);global_tcpsock_handle = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);if (global_tcpsock_handle < 0) {ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);break;}// Set tcp keepalive optionsetsockopt(global_tcpsock_handle, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));setsockopt(global_tcpsock_handle, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));setsockopt(global_tcpsock_handle, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));setsockopt(global_tcpsock_handle, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));setsockopt(global_tcpsock_handle, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(int));// Convert ip address to string
#ifdef CONFIG_EXAMPLE_IPV4if (source_addr.ss_family == PF_INET) {inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);}
#endif
#ifdef CONFIG_EXAMPLE_IPV6if (source_addr.ss_family == PF_INET6) {inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);}
#endifESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);//接收控制器端的下发数据do_tcpsock_recv();//用户主动关闭sockdo_tcpsock_close();do_udpsock_close();}CLEAN_UP:close(listen_sock);vTaskDelete(NULL);
}

  

这篇关于esp32s3中使用双通道通信解决TCP粘包问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/922388

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]