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

news2025/2/21 8:22:34

一、问题描述

最近用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.coloradmin.cn/o/2298050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python Pandas(7):Pandas 数据清洗

数据清洗是对一些没有用的数据进行处理的过程。很多数据集存在数据缺失、数据格式错误、错误数据或重复数据的情况&#xff0c;如果要使数据分析更加准确&#xff0c;就需要对这些没有用的数据进行处理。数据清洗与预处理的常见步骤&#xff1a; 缺失值处理&#xff1a;识别并…

【重构谷粒商城】06:Maven快速入门教程

重构谷粒商城06——Maven快速入门教程 前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到…

Elasticsearch:同义词在 RAG 中重要吗?

作者&#xff1a;来自 Elastic Jeffrey Rengifo 及 Toms Mura 探索 RAG 应用程序中 Elasticsearch 同义词的功能。 同义词允许我们使用具有相同含义的不同词语在文档中搜索&#xff0c;以确保用户无论使用什么确切的词语都能找到他们所寻找的内容。你可能会认为&#xff0c;由于…

React 低代码项目:组件设计

React 低代码项目&#xff1a;组件设计 Date: February 6, 2025 React表单组件 **目标&#xff1a;**使用 Ant Design 表单组件&#xff0c;开发登录、注册、搜索功能 内容&#xff1a; 使用 React 表单组件、受控组件使用 Ant Design 表单组件使用 表单组件的校验和错误提…

从0到1的回溯算法学习

回溯算法 前言这个算法能帮我们做啥算法模版力扣例题&#xff08; 以下所有题目代码都经过力扣认证 &#xff09;形式一 元素无重不可复选46.全排列思路详解代码 77.组合思路详解代码 78.子集思路详解代码 形式二 元素可重不可复选思考&#xff08;deepseek&#xff09;核心思想…

AVL树:高效平衡的二叉搜索树

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; 引言&#x1f914; 在数据结构的奇妙世界里&#xff0c;二叉搜索树&#xff08;BST&#xff09;原本是查找数据的好帮手。想象一下…

RHCA练习5:配置mysql8.0使用PXC实现高可用

准备4台CentOS7的虚拟机&#xff08;CentOS7-1、CentOS7-2、CentOS7-3、CentOS7-4&#xff09; 备份原yum源的配置&#xff1a; mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 更换阿里云镜像YUM源&#xff1a; curl -o /etc/yum.repos.…

Java 大视界 -- 边缘计算与 Java 大数据协同发展的前景与挑战(85)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

机器学习 - 理论和定理

在机器学习中&#xff0c;有一些非常有名的理论或定理&#xff0c;对理解机器学习的内在特性非常有帮助。本文列出机器学习中常用的理论和定理&#xff0c;并举出对应的举例子加以深化理解&#xff0c;有些理论比较抽象&#xff0c;我们可以先记录下来&#xff0c;慢慢啃&#…

2025.2.11——一、[极客大挑战 2019]PHP wakeup绕过|备份文件|代码审计

题目来源&#xff1a;BUUCTF [极客大挑战 2019]PHP 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;目录扫描、爆破 step 2&#xff1a;代码审计 1.index.php 2.class.php 3.flag.php step 3&#xff1a;绕过__wakeup重置 ​编辑 三、小结…

Vivado生成edif网表及其使用

介绍如何在Vivado中将模块设为顶层&#xff0c;并生成相应的网表文件&#xff08;Verilog文件和edif文件&#xff09;&#xff0c;该过程适用于需要将一个模块作为顶层设计进行综合&#xff0c;并生成用于其他工程中的网表文件的情况。 例如要将fpga_top模块制作成网表给其它工…

JAVA生产环境(IDEA)排查死锁

使用 IntelliJ IDEA 排查死锁 IntelliJ IDEA 提供了强大的工具来帮助开发者排查死锁问题。以下是具体的排查步骤&#xff1a; 1. 编写并运行代码 首先&#xff0c;我们编写一个可能导致死锁的示例代码&#xff1a; public class DeadlockExample {private static final Obj…

AI学习记录 - 最简单的专家模型 MOE

代码 import torch import torch.nn as nn import torch.nn.functional as F from typing import Tupleclass BasicExpert(nn.Module):# 一个 Expert 可以是一个最简单的&#xff0c; linear 层即可# 也可以是 MLP 层# 也可以是 更复杂的 MLP 层&#xff08;active function 设…

【2025深度学习系列专栏大纲:深入探索与实践深度学习】

第一部分:深度学习基础篇 第1章:深度学习概览 1.1 深度学习的历史背景与发展轨迹 1.2 深度学习与机器学习、传统人工智能的区别与联系 1.3 深度学习的核心组件与概念解析 神经网络基础 激活函数的作用与类型 损失函数与优化算法的选择 1.4 深度学习框架简介与选择建议 第2…

数据治理双证通关经验分享 | CDGA/CDGP备考全指南

历经1个月多的系统准备&#xff0c;本人于2024年顺利通过DAMA China的CDGA&#xff08;数据治理工程师&#xff09;和CDGP&#xff08;数据治理专家&#xff09;双认证。现将备考经验与资源体系化整理&#xff0c;助力从业者高效通关。 &#x1f31f; 认证价值与政策背景 根据…

亚信安全正式接入DeepSeek

亚信安全致力于“数据驱动、AI原生”战略&#xff0c;早在2024年5月&#xff0c;推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体&#xff0c;为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来&#xff0c;亚信安全人工智能实验室利…

unet学习(初学者 自用)

代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 以上面链接中的代码为例&#xff0c;逐行解释。 训练 unet的train.py如下&#xff1a; import torch.nn as nn import torch import gdal import numpy as np from torch…

CCFCSP第34次认证第一题——矩阵重塑(其一)

第34次认证第一题——矩阵重塑&#xff08;其一&#xff09; 官网链接 时间限制&#xff1a; 1.0 秒 空间限制&#xff1a; 512 MiB 相关文件&#xff1a; 题目目录&#xff08;样例文件&#xff09; 题目背景 矩阵&#xff08;二维&#xff09;的重塑&#xff08;reshap…

探索B-树系列

&#x1f308;前言&#x1f308; 本文将讲解B树系列&#xff0c;包含 B-树&#xff0c;B树&#xff0c;B*树&#xff0c;其中主要讲解B树底层原理&#xff0c;为什么用B树作为外查询的数据结构&#xff0c;以及B-树插入操作并用代码实现&#xff1b;介绍B树、B*树。 &#x1f4…

GRN前沿:DeepMCL:通过深度多视图对比学习从单细胞基因表达数据推断基因调控网络

1.论文原名&#xff1a;Inferring gene regulatory networks from single-cell gene expression data via deep multi-view contrastive learning 2.发表日期&#xff1a;2023 摘要&#xff1a; 基因调控网络&#xff08;GRNs&#xff09;的构建对于理解细胞内复杂的调控机制…