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

server/2024/9/24 8:02:38/

在使用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);
}

  


http://www.ppmy.cn/server/20286.html

相关文章

OceanBase 分布式数据库【信创/国产化】- OceanBase Demo 环境搭建

本心、输入输出、结果 文章目录 OceanBase 分布式数据库【信创/国产化】- OceanBase Demo 环境搭建前言OceanBase 数据更新架构部署背景信息组件介绍部署前提条件下载并安装 all-in-one 安装包单机部署 OceanBase 数据库执行输出中的连接命令连接数据库配置 OceanBase 密码Ocea…

Linux - tar (tape archive)

tar 的全称是 Tape Archive。它最初是在 Unix 系统中用于将数据写入磁带的工具&#xff0c;但现在它通常用于创建、维护、修改和提取文件的归档文件。尽管 tar 可以用于压缩和解压缩文件&#xff0c;但它本身并不进行压缩&#xff0c;而是通常与 gzip 或 bzip2 等压缩工具一起使…

腾讯云邮件推送如何设置?群发邮件的技巧?

腾讯云邮件推送功能有哪些&#xff1f;怎么有效使用邮件推送&#xff1f; 腾讯云邮件推送以其稳定、高效的特点&#xff0c;受到了众多企业的青睐。那么&#xff0c;腾讯云邮件推送如何设置呢&#xff1f;又有哪些群发邮件的技巧呢&#xff1f;下面AokSend就来详细探讨一下。 …

grpc笔记

教程地址 【狂神说】gRPC最新超详细版教程通俗易懂 | Go语言全栈教程_哔哩哔哩_bilibili rpc 定义&#xff1a;Remote Procedure Call——远程过程调用&#xff0c;通俗的含义是&#xff1a;远程定义好方法名、参数和返回值&#xff0c;RPC可以像调用本地方法那样调用远端方…

[Java EE] 多线程(四):线程安全问题(下)

1.5 volatile关键字 我们在了解这个关键字之前,我们首先要把产生线程安全的第4个原因补齐,我们来说说由于内存可见性引起的线程安全问题. 我们来看下面这样一段代码: import java.util.Scanner;public class Demo16 {public static int count 0;public static void main(Str…

docker简介

Docker 是一种开源的容器化平台&#xff0c;用于打包、发布和运行应用程序及其依赖项。它基于 Linux 内核的 cgroups 和 namespaces 功能&#xff0c;实现了轻量级的虚拟化技术&#xff0c;使得开发人员能够在一个统一的环境中开发、测试和部署应用程序&#xff0c;同时也简化了…

第67天:APP攻防-Frida反证书抓包移动安全系统资产提取评估扫描

思维导图 案例一&#xff1a;内在-资产提取-AppinfoScanne AppinfoScanner 一款适用于以 HW 行动/红队/渗透测试团队为场景的移动端(Android、iOS、WEB、H5、静态网站)信息收集扫描工具&#xff0c;可以帮助渗透测试工程师、攻击队成员、红队成员快速收集到移动端或者静态 WEB …

C语言入门课程学习笔记1

C语言入门课程学习笔记1 第1课 - 概论第2课 -helloworld第3课 -数据输出第4课 -数据类型与变量第5课 - 深入数据类型与变量第6课 - 类型与变量编程练习第7课 - 程序中的数据输入 本文学习自狄泰软件学院 唐佐林老师的 C语言入门课程&#xff0c;图片全部来源于课程PPT&#xff…