stm32 lwip tcp服务端频繁接收连接失效问题解决(tcp_recved)

一、问题描述

最近用stmf429单片机作为TCP服务端遇到一个问题,就是客户端特别频繁的发送消息,过一段时间以后,客户端的请求不再被客户端接收到,而且服务器端监控的掉线回调函数也不会被调用,好像这个连接就凭空的消失了。

二、问题排除

首先,我以为是QT的客户端出现问题了,因为请求过于频繁,所以tcp的发送缓存区满了,但是我在发送消息的时候加了缓存区内容判断,有内容就不发送,还是不管用,然后用了串口助手软件以同样速度向单片机发送消息,过不了多久也就失去连接了,所以排除了客户端的问题。确定是单片机服务器端的问题,然后我又改了服务器端的返回消息那里,也是加发送缓存区的判断,同样是不管用,最后发现,是没有加tcp_recved的原因。

三、 tcp_recved描述

tcp_recved是 lwIP(Lightweight IP,轻量级 TCP/IP 协议栈)中用于 TCP 连接的数据接收确认操作的重要函数调用。

1.函数作用

tcp_recved 函数的主要作用是告知 lwIP 协议栈,应用程序已经成功处理了指定长度的数据,这样 lwIP 可以更新接收窗口的大小,允许发送方继续发送更多的数据。在 TCP 协议中,接收方会维护一个接收窗口,用于告知发送方自己当前能够接收的数据量。当应用程序处理完一部分数据后,需要调用 tcp_recved 函数来调整这个窗口大小,以保证数据的正常传输。

2.函数原型及参数

void tcp_recved(struct tcp_pcb *tpcb, u16_t len);
  • 参数
    • tpcb:指向 struct tcp_pcb 类型的指针,该结构体代表一个 TCP 连接的协议控制块,包含了该连接的所有状态信息。
    • len:表示应用程序已经成功处理的数据长度,单位是字节。

3. 使用场景

在 TCP 服务器或客户端的接收回调函数中,当应用程序接收到数据并完成处理后,需要调用 tcp_recved 函数。例如,在 client_recv_handler 回调函数中,当处理完接收到的 pbuf(数据包缓冲区)中的数据后,就会调用该函数。

4.实现原理

  • 更新接收窗口tcp_recved 函数会根据传入的 len 参数,更新 TCP 连接的接收窗口大小。接收窗口的大小表示接收方当前还能接收多少字节的数据。当应用程序处理完数据后,接收窗口会相应地增大,允许发送方发送更多的数据。
  • 发送确认信息:lwIP 会根据更新后的接收窗口信息,向发送方发送确认信息(ACK),告知发送方可以继续发送的数据范围。

4.示例代码

#include "lwip/tcp.h"// 接收回调函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{if (p != NULL) {// 处理接收到的数据// 这里可以添加具体的数据处理逻辑,如解析数据、存储数据等// ...// 告知 lwIP 已经处理完这些数据tcp_recved(tpcb, p->tot_len);// 释放 pbuf 占用的内存pbuf_free(p);} else if (err == ERR_OK) {// 连接关闭处理tcp_close(tpcb);}return ERR_OK;
}

5.注意事项

  • 及时调用:在处理完接收到的数据后,应及时调用 tcp_recved 函数,否则接收窗口不会更新,发送方可能会认为接收方没有足够的空间来接收新的数据,从而暂停数据发送,导致网络性能下降,我这次就是因为没有调用这个函数,客户端与服务器端频繁通讯出现了连接失效的问题。
  • 数据长度准确:传入 tcp_recved 函数的 len 参数必须准确表示应用程序已经处理的数据长度,否则可能会导致接收窗口大小计算错误,影响数据的正常传输。
  • 内存管理:在调用 tcp_recved 函数后,通常需要释放 pbuf 占用的内存,避免内存泄漏。可以使用 pbuf_free 函数来释放内存。

四、TCP服务端完整代码

#include "tcp_demo.h"
extern u8* GETDATA_BUFF8;
extern short *GETDATA_BUFF16;
extern u8 nowtype;extern OS_SEM	Gather_SEM;#define TCP_SERVER_PORT 8088  // 定义TCP服务器监听的端口号,可按需修改
#define MAX_CONNECTIONS 2      // 最大允许同时连接的客户端数量
#define RX_BUFFER_SIZE 1024    // 接收缓冲区大小// 用于存储客户端连接的结构体数组
struct tcp_pcb *tcp_server_pcbs[MAX_CONNECTIONS];// 用于保护对tcp_server_pcbs数组操作的互斥信号量
OS_SEM tcp_server_pcbs_sem;// 处理客户端连接的函数
err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err);
// 处理客户端接收数据的函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
// 处理客户端发送数据的函数(示例中简单回显数据给客户端)
err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 处理客户端断开连接的函数
void client_err_handler(void *arg, err_t err);
// TCP服务器任务函数
void tcp_server_task(void *p_arg);//TCP客户端任务
#define TCP_PRIO		7
//任务堆栈大小
#define TCP_STK_SIZE	300
//任务控制块
OS_TCB	TcpTaskTCB;
//任务堆栈
CPU_STK TCP_TASK_STK[TCP_STK_SIZE];int close_tcp=0;void tcp_server_task(void *p_arg)
{struct tcp_pcb *server_pcb;err_t err;OS_ERR os_err;// 创建一个TCP协议控制块(PCB)用于服务器端server_pcb = tcp_new();if (server_pcb == NULL){printf("Error creating TCP PCB\n");return;}// 绑定服务器的IP地址和端口号err = tcp_bind(server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);if (err!= ERR_OK){printf("Error binding TCP socket: %d\n", err);tcp_close(server_pcb);return;}// 将服务器PCB设置为监听状态,等待客户端连接server_pcb = tcp_listen(server_pcb);if (server_pcb == NULL){printf("Error listening for connections\n");tcp_close(server_pcb);return;}// 设置接受客户端连接的回调函数tcp_accept(server_pcb, client_connection_handler);while (1){// 任务可以在这里进行适当的阻塞等待,避免过度占用CPU资源OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&os_err); //延时100ms}
}err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err)
{OS_ERR os_err;int client_index;if (err!= ERR_OK){return err;}// 获取互斥信号量,保护对tcp_server_pcbs数组的访问OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量if(tcp_server_pcbs[close_tcp]!=NULL){tcp_close(tcp_server_pcbs[close_tcp]);}tcp_server_pcbs[close_tcp]=newpcb;if(close_tcp==0){close_tcp=1;}else{close_tcp=0;}// 设置接收、发送和错误处理的回调函数tcp_recv(newpcb, client_recv_handler);tcp_sent(newpcb, client_send_handler);tcp_err(newpcb, client_err_handler);OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);return ERR_OK;
}err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{OS_ERR os_err;if (err == ERR_OK && p!= NULL){// 将接收到的pbuf数据复制到本地缓冲区(这里简单示例,可优化)/**char rx_buffer[RX_BUFFER_SIZE];int copied_bytes = 0;struct pbuf *q = p;while (q!= NULL){int bytes_to_copy = (q->len < (RX_BUFFER_SIZE - copied_bytes))? q->len : (RX_BUFFER_SIZE - copied_bytes);memcpy(&rx_buffer[copied_bytes], q->payload, bytes_to_copy);copied_bytes += bytes_to_copy;q = q->next;}**/// 这里可以对接收到的数据进行处理,比如根据协议解析等,现在简单回显数据给客户端//tcp_write(tpcb, rx_buffer, copied_bytes, TCP_WRITE_FLAG_COPY);//tcp_output(tpcb);OSSemPend(&Gather_SEM,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量u16_t free_space = tcp_sndbuf(tpcb);if (free_space >= 1441) {// 缓冲区有足够空间,写入数据tcp_write(tpcb, GETDATA_BUFF8, 1441, TCP_WRITE_FLAG_COPY);tcp_output(tpcb);} else {// 缓冲区空间不足,进行相应处理,如等待或丢弃数据// 这里简单打印提示信息printf("Send buffer is not enough!\n");}OSSemPost (&Gather_SEM,OS_OPT_POST_1,&os_err);				//发送信号量// 释放接收到的pbuf内存tcp_recved(tpcb, p->tot_len);pbuf_free(p);}else if (err == ERR_OK && p == NULL){OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量// 客户端关闭了连接,正常处理for (int i = 0; i < MAX_CONNECTIONS; i++){if (tcp_server_pcbs[i] == tpcb){tcp_server_pcbs[i] = NULL;break;}}OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);tcp_close(tpcb);}else{// 出现错误情况,关闭连接tcp_close(tpcb);}return ERR_OK;
}err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len)
{// 这里可以根据实际发送情况做一些后续处理,当前示例只是简单回显,无需额外操作return ERR_OK;
}void client_err_handler(void *arg, err_t err)
{struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;OS_ERR os_err;// 获取互斥信号量,保护对tcp_server_pcbs数组的访问OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量for (int i = 0; i < MAX_CONNECTIONS; i++){if (tcp_server_pcbs[i] == tpcb){tcp_server_pcbs[i] = NULL;break;}}OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);tcp_close(tpcb);
}//创建TCP线程
//返回值:0 TCP创建成功
//		其他 TCP创建失败
u8 tcp_demo_init(void)
{OS_ERR err;CPU_SR_ALLOC();OS_CRITICAL_ENTER();//进入临界区//创建TCP任务OSTaskCreate((OS_TCB 	* )&TcpTaskTCB,		(CPU_CHAR	* )"tcp task", 		(OS_TASK_PTR )tcp_server_task, 			(void		* )0,					(OS_PRIO	  )TCP_PRIO,     (CPU_STK   * )&TCP_TASK_STK[0],	(CPU_STK_SIZE)TCP_STK_SIZE/10,	(CPU_STK_SIZE)TCP_STK_SIZE,		(OS_MSG_QTY  )0,					(OS_TICK	  )0,					(void   	* )0,					(OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,(OS_ERR 	* )&err);OS_CRITICAL_EXIT();	//退出临界区return err;
}

 

1. 全局变量和宏定义

  • 端口和连接相关
    • TCP_SERVER_PORT:定义了 TCP 服务器监听的端口号,这里设为 8088。
    • MAX_CONNECTIONS:规定了服务器最大允许同时连接的客户端数量,为 2。
    • RX_BUFFER_SIZE:设置了接收缓冲区的大小,为 1024 字节。
  • 客户端连接数组和信号量
    • tcp_server_pcbs:是一个用于存储客户端连接的 struct tcp_pcb 类型的数组,长度为 MAX_CONNECTIONS
    • tcp_server_pcbs_sem:用于保护对 tcp_server_pcbs 数组操作的互斥信号量。
  • 其他外部变量GETDATA_BUFF8GETDATA_BUFF16nowtype 是外部定义的变量,Gather_SEM 是一个外部信号量。

2. 函数声明

声明了多个处理 TCP 连接、数据接收、发送、错误处理以及服务器任务的函数,具体如下:

  • client_connection_handler:处理客户端连接请求。
  • client_recv_handler:处理客户端发送的数据。
  • client_send_handler:处理数据发送完成后的操作。
  • client_err_handler:处理连接错误。
  • tcp_server_task:TCP 服务器的主任务函数。

3. TCP 服务器任务函数 tcp_server_task

  • 创建和绑定 PCB:使用 tcp_new() 创建一个 TCP 协议控制块(PCB),并通过 tcp_bind() 将其绑定到指定的 IP 地址(IP_ADDR_ANY 表示监听所有可用的 IP 地址)和端口号。
  • 监听连接:调用 tcp_listen() 将服务器 PCB 设置为监听状态,等待客户端连接。
  • 设置回调函数:使用 tcp_accept() 设置接受客户端连接的回调函数为 client_connection_handler
  • 任务循环:在一个无限循环中,使用 OSTimeDlyHMSM() 函数进行 100ms 的延时,避免任务过度占用 CPU 资源。

4. 客户端连接处理函数 client_connection_handler

  • 错误检查:检查连接是否存在错误,若有则直接返回错误码。
  • 管理连接数组:获取互斥信号量 tcp_server_pcbs_sem,保护对 tcp_server_pcbs 数组的访问。如果数组中已有连接,关闭该连接,然后将新的连接添加到数组中。
  • 设置回调函数:为新的客户端连接设置接收、发送和错误处理的回调函数,分别为 client_recv_handlerclient_send_handler 和 client_err_handler
  • 释放信号量:操作完成后,释放互斥信号量。

5. 客户端数据接收处理函数 client_recv_handler

  • 正常接收数据:当接收到数据(err == ERR_OK && p != NULL)时,先获取 Gather_SEM 信号量,检查发送缓冲区是否有足够空间。若空间足够,将 GETDATA_BUFF8 中的 1441 字节数据写入发送缓冲区并尝试发送;若空间不足,打印提示信息。处理完后释放 Gather_SEM 信号量,调用 tcp_recved() 告知 lwIP 已处理的数据长度,并释放接收到的 pbuf 内存。
  • 客户端关闭连接:当客户端关闭连接(err == ERR_OK && p == NULL)时,获取 tcp_server_pcbs_sem 信号量,从 tcp_server_pcbs 数组中移除该连接,然后释放信号量并关闭连接。
  • 错误处理:若出现其他错误情况,直接关闭连接。

6. 客户端数据发送处理函数 client_send_handler

该函数目前只是简单返回 ERR_OK,可根据实际发送情况添加后续处理逻辑。

7. 客户端连接错误处理函数 client_err_handler

获取 tcp_server_pcbs_sem 信号量,从 tcp_server_pcbs 数组中移除出现错误的连接,释放信号量并关闭连接。

8. TCP 任务创建函数 tcp_demo_init

使用 uC/OS-II 的 OSTaskCreate() 函数创建 tcp_server_task 任务,指定任务的优先级、堆栈等参数。如果任务创建成功,返回 0;否则返回相应的错误码。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/17468.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

让 DeepSeek R1 锐评一下自己

让 DeepSeek R1 锐评一下自己 1. 技术领域覆盖广泛&#xff0c;内容实用性突出2. 内容系统性强&#xff0c;适合阶段性学习3. 持续更新与技术敏锐度4. 潜在改进方向总结 突发奇想&#xff0c;让 AI 锐评一下自己~ Gorit 是一名活跃于技术领域的 CSDN 博主&#xff0c;其内容主…

19.4.9 数据库方式操作Excel

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本节所说的操作Excel操作是讲如何把Excel作为数据库来操作。 通过COM来操作Excel操作&#xff0c;请参看第21.2节 在第19.3.4节【…

Android原生的HighCPU使用率查杀机制

摘要 原生的HighCPU使用率查杀机制是基于读取/proc/pid/stat中的utime stime后&#xff0c;根据CPU使用率 (utime stime / totalTime)*100%进行实现&#xff0c;当检测后台进程的CPU使用率超过阈值时&#xff0c;执行查杀和统计到电池数据中。 细节点&#xff1a; 1. 原生根…

Linux学习笔记之进程

进程 进程的定义 进程是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配的基本单位&#xff0c;也是操作系统结构的基础。   例如当QQ程序运行的时候&#xff0c;计算机会先从磁盘读取QQ程序到内存&#xff0c;然后OS管理这个程序&#xff0c;…

深入理解 MyBatis 框架的核心对象:SqlSession

Mybatis框架中的SqlSession对象详解 引言 MyBatis 是一个优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息&#xff0…

Tcp_socket

Tcp不保证报文完整性&#xff08;面向字节流&#xff09; 所以我们需要在应用层指定协议&#xff0c;确保报文完整性 // {json} -> len\r\n{json}\r\n bool Encode(std::string &message) {if(message.size() 0) return false;std::string package std::to_string(m…

【运维心得】Centos7安装Redis7.4.2并处理相关告警

概述 单机版的redis安装比较简单&#xff0c;这里重点记录下告警的处理。 安装步骤 1. 确认版本 可以通过官方仓库或者知名的网站获取最新安装包&#xff0c;截止20250213&#xff0c;未找到官方安装包。 rpmfind: RPM resource redis(x86-64)https://rpmfind.net/linux/rpm2h…

社区版IDEA中配置TomCat(详细版)

文章目录 1、下载Smart TomCat2、配置TomCat3、运行代码 1、下载Smart TomCat 由于小编的是社区版&#xff0c;没有自带的tomcat server&#xff0c;所以在设置的插件里面搜索&#xff0c;安装第一个&#xff08;注意&#xff1a;安装时一定要关闭外网&#xff0c;小编因为这个…

K8s之存储卷

一、容忍、crodon和drain 1.容忍 即使节点上有污点&#xff0c;依然可以部署pod。 字段&#xff1a;tolerations 实例 当node01上有标签test11&#xff0c;污点类型为NoSchedule&#xff0c;而node02没有标签和污点&#xff0c;此时pod可以在node01 node02上都部署&#xff0c…

【MySQL — 数据库基础】深入解析 MySQL 的联合查询

1. 插入查询结果 语法 insert into table_name1 select* from table_name2 where restrictions ;注意&#xff1a;查询的结果集合&#xff0c;列数 / 类型 / 顺序 要和 insert into 后面的表相匹配&#xff1b;列的名字不要求相同&#xff1b; create table student1(id int , …

spring cloud 使用 webSocket

1.引入依赖,(在微服务模块中) <!-- Spring WebSocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 2.新建文件 package com.ruoyi.founda…

《aarch64汇编从入门到精通》-204页PPT+实验

&#x1f534;【课程特色】 ✅1、依照官方文档总结制作&#xff0c;体系更完整&#xff0c;不遗漏知识&#xff1b; ✅2、基于Armv8/Armv9架构讲解汇编。真正的ARM汇编&#xff1b; ✅3、资料更全。200多页PPT资料&#xff0c;其它参考资料; ✅4、学完ARM架构&#xff0c;再学汇…

企业使用统一终端管理(UEM)工具提高端点安全性

什么是统一终端管理(UEM) 统一终端管理(UEM)是一种从单个控制台管理和保护企业中所有端点的方法&#xff0c;包括智能手机、平板电脑、笔记本电脑、台式机和 IoT设备。UEM 解决方案为 IT 管理员提供了一个集中式平台&#xff0c;用于跨所有作系统和设备类型部署、配置、管理和…

20250213 隨筆 雪花算法

雪花算法&#xff08;Snowflake Algorithm&#xff09; 雪花算法&#xff08;Snowflake&#xff09; 是 Twitter 在 2010 年開發的一種 分布式唯一 ID 生成算法&#xff0c;它可以在 高併發場景下快速生成全局唯一的 64-bit 長整型 ID&#xff0c;且不依賴資料庫&#xff0c;具…

QT 异步编程之多线程

一、概述 1、在进行桌面应用程序开发的时候&#xff0c;假设应用程序在某些情况下需要处理比较复制的逻辑&#xff0c;如果只有一个线程去处理&#xff0c;就会导致窗口卡顿&#xff0c;无法处理用户的相关操作。这种情况下就需要使用多线程&#xff0c;其中一个线程处理窗口事…

leetcode 543. 二叉树的直径

题目如下 数据范围 示例 显然直径等于左右子树高之和的最大值。通过代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* Tr…

IP 路由基础 | 路由条目生成 / 路由表内信息获取

注&#xff1a;本文为 “IP 路由” 相关文章合辑。 未整理去重。 IP 路由基础 秦同学学学已于 2022-04-09 18:44:20 修改 一. IP 路由产生背景 我们都知道 IP 地址可以标识网络中的一个节点&#xff0c;并且每个 IP 地址都有自己的网段&#xff0c;各个网段并不相同&#xf…

sql:时间盲注和boolen盲注

关于时间盲注&#xff0c;boolen盲注的后面几个获取表、列、具体数据的函数补全 时间盲注方法 import time import requests# 获取数据库名 def inject_database(url):dataname for i in range(1, 20):low 32high 128mid (low high) // 2while low < high:payload &q…

zyNo.22

常见Web漏洞解析 命令执行漏洞 1.Bash与CMD常用命令 &#xff08;1&#xff09;Bash 读取文件&#xff1a;最常见的命令cat flag 在 Bash 中&#xff0c;cat 以及的tac、nl、more、head、less、tail、od、pr 均为文件读取相关命令&#xff0c;它们的区别如下&#xff1a; …

《Python 中 JSON 的魔法秘籍:从入门到精通的进阶指南》

在当今数字化时代&#xff0c;网络编程无处不在&#xff0c;数据的高效传输与交互是其核心。JSON 作为一种轻量级的数据交换格式&#xff0c;凭借其简洁、易读、跨语言的特性&#xff0c;成为网络编程中数据传输与存储的关键技术。无论是前后端数据交互&#xff0c;还是不同系统…