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

news/2024/10/18 9:25:52/

在使用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/news/1427903.html

相关文章

【R语言】混合图:小提琴图+箱线图

{ggstatsplot} 是 {ggplot2} 包的扩展&#xff0c;用于创建图形&#xff0c;其中包含信息丰富的绘图本身中包含的统计测试的详细信息。在典型的探索性数据分析工作流程中&#xff0c;数据可视化和统计建模是两个不同的阶段&#xff1a;可视化通知建模&#xff0c;而建模又可以建…

28、Lua 如何输出树状结构的table?

为了让游戏前端数据输出更加条理&#xff0c;做了一个简单树状结构来打印数据。 ccmlog.lua local function __tostring(value, indent, vmap)local str indent indent or vmap vmap or {}--递归结束条件if (type(value) ~ table) thenif (type(value) string) then--字符…

Python学习笔记1:变量命名

跟学极客时间的教程系列笔记&#xff1a; 1&#xff09; Python 还支持更灵活的动态解包语法。只要用星号表达式 &#xff08;*variables&#xff09;作为变量名&#xff0c;它便会贪婪 地捕获多个值对象&#xff0c;并将捕获到的内容作为 列表赋值给 variables。比如&#…

Java测试编程题

题目1 1.创建5个线程对象 线程名设置为&#xff08;Thread01&#xff0c;Thread02&#xff0c;Thread03&#xff0c;Thread04&#xff0c;Thread05&#xff09;使用 代码实现5个线程有序的循环打印&#xff0c;效果如下&#xff1a; Thread01正在打印1 Thread02正在打印2 Threa…

python如何对resample+ndarray进行切片提取数据

目录 1. 代码 2. 代码解读2.1. 设置重采样的时间间隔2.2. 对 DataFrame 进行重采样2.3. 将 Resampler 对象转换为数组2.4. 遍历特定时间点并提取数据2.5 总结 1. 代码 # Specfiy time-averageing-interval time_interval 10min df_Tai df.resample(time_interval) np_Tai n…

PDF被加密无法打印的解决办法

思路很清晰&#xff1a;先解密→再打印 分享四个工具&#xff0c;可以轻松解密PDF&#xff1a; ⭐i love pdf I LOVE PDF是一款免费的PDF网站&#xff0c;界面设计简洁&#xff0c;首页没有广告&#xff0c;但每个功能的操作界面是有广告的&#xff0c;不会影响使用。 部分功…

力扣练习题(2024/4/17)

1 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的 子…

【网站项目】学习资料销售平台 小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…