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

相关文章

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(