当前位置: 首页 > news >正文

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;否则返回相应的错误码。

 

 

相关文章:

  • 基于单片机的仓库安防系统(论文+源码)
  • mount与busybox mount
  • 深兰科技与银川市苏银产业园签署协议,共建AI装备西部产业基地
  • 在 Kubernetes (K8s) 环境中,备份 PostgreSQL 数据库
  • Python----PyQt开发(PyQt高级:组件大小,界面位置,按钮,文本显示,文本输入,字体大小)
  • 502 Bad Gateway 错误详解:从表现推测原因,逐步排查直至解决
  • 如何判断网页是不是鸿蒙手机浏览器里打开
  • 道路运输安全员考试题库及答案
  • 算法-哈希表篇05-四数相加II
  • Python 进阶:元类编程
  • 单例设计模式
  • 算法题(67):最长连续序列
  • 一个让Stable Diffusion更稳定、更易用的Github开源项目
  • docker学习---第3步:docker实操大模型
  • 《pytorch》——优化器的解析和使用
  • 01.01、判定字符是否唯一
  • 【IC】AI处理器核心--第二部分 用于处理 DNN 的硬件设计
  • IoTDB 节点宕机后集群恢复
  • mysql的索引
  • C#+UDP接收数据,并将数据保存到redis,定时同步到数据库Sql Server中
  • 曾犯强奸罪教师出狱后办教培机构?柳州鱼峰区教育局回应
  • 沧州低空经济起飞:飞行汽车开启千亿赛道,通用机场布局文旅体验
  • 证券时报:中美互降关税落地,订单集中补发港口将迎高峰期
  • 4月份全国企业销售收入同比增长4.3%
  • 沙县小吃中东首店在沙特首都利雅得开业,首天营业额5万元
  • 吉林:消纳绿电,“氢”装上阵