OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 168|回复: 7

stm32f407+lan8720实现UCOSII 下LWIP网络联网问题

[复制链接]

9

主题

26

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3320
金钱
3320
注册时间
2021-12-3
在线时间
198 小时
发表于 2025-5-21 15:23:50 | 显示全部楼层 |阅读模式
1金钱
#include "tcp_server_demo.h"
#include "lwip/opt.h"
#include "lwip_comm.h"
#include "led.h"
#include "lwip/lwip_sys.h"
#include "lwip/api.h"
//////////////////////////////////////////////////////////////////////////////////         
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//NETCONN API编程方式的TCP服务器测试代码         
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/8/15
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved                                                                          
//*******************************************************************************
//修改信息
//无
//////////////////////////////////////////////////////////////////////////////////           


u8 tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];        //TCP客户端接收数据缓冲区
u8 *tcp_server_sendbuf="Explorer STM32F407 NETCONN TCP Server send data\r\n";      
u8 tcp_server_flag;                                                                //TCP服务器数据发送标志位

//TCP客户端任务
#define TCPSERVER_PRIO                6
//任务堆栈大小
#define TCPSERVER_STK_SIZE        300
//任务堆栈
OS_STK TCPSERVER_TASK_STK[TCPSERVER_STK_SIZE];

//tcp服务器任务
static void tcp_server_thread(void *arg)
{
        OS_CPU_SR cpu_sr;
        u32 data_len = 0;
        struct pbuf *q;
        err_t err,recv_err;
        u8 remot_addr[4];
        struct netconn *conn, *newconn;
        static ip_addr_t ipaddr;
        static u16_t                         port;
      
        LWIP_UNUSED_ARG(arg);

        conn = netconn_new(NETCONN_TCP);  //创建一个TCP链接
        netconn_bind(conn,IP_ADDR_ANY,TCP_SERVER_PORT);  //绑定端口 8号端口
        netconn_listen(conn);                  //进入监听模式
        conn->recv_timeout = 10;          //禁止阻塞线程 等待10ms
        while (1)
        {
                err = netconn_accept(conn,&newconn);  //接收连接请求
                if(err==ERR_OK)newconn->recv_timeout = 10;

                if (err == ERR_OK)    //处理新连接的数据
                {
                        struct netbuf *recvbuf;

                        netconn_getaddr(newconn,&ipaddr,&port,0); //获取远端IP地址和端口号
                       
                        remot_addr[3] = (uint8_t)(ipaddr.addr >> 24);
                        remot_addr[2] = (uint8_t)(ipaddr.addr>> 16);
                        remot_addr[1] = (uint8_t)(ipaddr.addr >> 8);
                        remot_addr[0] = (uint8_t)(ipaddr.addr);
                        printf("主机%d.%d.%d.%d连接上服务器,主机端口号为:%d\r\n",remot_addr[0], remot_addr[1],remot_addr[2],remot_addr[3],port);
                       
                        while(1)
                        {
                                if((tcp_server_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
                                {
                                        err = netconn_write(newconn ,tcp_server_sendbuf,strlen((char*)tcp_server_sendbuf),NETCONN_COPY); //发送tcp_server_sendbuf中的数据
                                        if(err != ERR_OK)
                                        {
                                                printf("发送失败\r\n");
                                        }
                                        tcp_server_flag &= ~LWIP_SEND_DATA;
                                }
                              
                                if((recv_err = netconn_recv(newconn,&recvbuf)) == ERR_OK)          //接收到数据
                                {               
                                        OS_ENTER_CRITICAL(); //关中断
                                        memset(tcp_server_recvbuf,0,TCP_SERVER_RX_BUFSIZE);  //数据接收缓冲区清零
                                        for(q=recvbuf->p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
                                        {
                                                //判断要拷贝到TCP_SERVER_RX_BUFSIZE中的数据是否大于TCP_SERVER_RX_BUFSIZE的剩余空间,如果大于
                                                //的话就只拷贝TCP_SERVER_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
                                                if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len)) memcpy(tcp_server_recvbuf+data_len,q->payload,(TCP_SERVER_RX_BUFSIZE-data_len));//拷贝数据
                                                else memcpy(tcp_server_recvbuf+data_len,q->payload,q->len);
                                                data_len += q->len;         
                                                if(data_len > TCP_SERVER_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出      
                                        }
                                        OS_EXIT_CRITICAL();  //开中断
                                        data_len=0;  //复制完成后data_len要清零。      
                                        printf("%s\r\n",tcp_server_recvbuf);  //通过串口发送接收到的数据
                                        netbuf_delete(recvbuf);
                                }else if(recv_err == ERR_CLSD)  //关闭连接
                                {
                                        netconn_close(newconn);
                                        netconn_delete(newconn);
                                        printf("主机:%d.%d.%d.%d断开与服务器的连接\r\n",remot_addr[0], remot_addr[1],remot_addr[2],remot_addr[3]);
                                        break;
                                }
                        }
                }
        }
}


//创建TCP服务器线程
//返回值:0 TCP服务器创建成功
//                其他 TCP服务器创建失败
INT8U tcp_server_init(void)
{
        INT8U res;
        OS_CPU_SR cpu_sr;
      
        OS_ENTER_CRITICAL();        //关中断
        res = OSTaskCreate(tcp_server_thread,(void*)0,(OS_STK*)&TCPSERVER_TASK_STK[TCPSERVER_STK_SIZE-1],TCPSERVER_PRIO); //创建TCP服务器线程
        OS_EXIT_CRITICAL();                //开中断
      
        return res;
}
以上是正点原子的代码,正常开机可以联网收发数据,拔掉网线再插上就连不上网,必须断电重启才可以。怎样修改以上代码,才能实现网线热插拔自动重联网?


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

4

主题

912

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
4518
金钱
4518
注册时间
2019-9-4
在线时间
915 小时
发表于 2025-5-22 09:38:41 | 显示全部楼层
好的,您提供的代码是基于正点原子 STM32F407 开发板,使用 UCOSII 操作系统和 LwIP 网络协议栈(netconn API)实现的 TCP 服务器。当前遇到的核心问题是网线热插拔后无法自动重新联网。
为了实现网线热插拔后的自动重连,我们需要关注以下几个关键点:
  • 物理层链路状态检测 (PHY Status Check):需要能够检测到网线是连接上了还是断开了。
  • 网络接口 (netif) 管理:当链路状态改变时,需要通知 LwIP 更新网络接口的状态,并相应地处理 IP 地址的获取(例如通过 DHCP)。
  • TCP 服务器的健壮性:服务器需要能够处理因链路断开导致的现有连接异常,并在网络恢复后能继续接受新的连接。

下面是如何修改和添加代码来实现这一功能的思路和步骤:
一、 实现 PHY 链路状态检测及 LwIP 通知
这通常不是在 TCP 服务器应用层代码中直接完成的,而是需要在 LwIP 的网络接口层(通常是 ethernetif.c 或类似文件)或者一个专门的监控任务中实现。
  • 创建 PHY 状态监控任务 (如果 LwIP 驱动中没有内置此功能)
    • 这个任务会周期性地通过 SMI/MII 总线读取 LAN8720 PHY 芯片的状态寄存器,以判断链路是否连接。LAN8720 的 Basic Status Register (BSR) 中的 "Link Status" 位可以指示链路状态。
    • 当检测到链路状态从“断开”变为“连接”时,调用 netif_set_link_up(struct netif *netif)。
    • 当检测到链路状态从“连接”变为“断开”时,调用 netif_set_link_down(struct netif *netif)。

    示例代码结构 (需要您根据实际的 STM32 HAL/LL 库和 PHY 驱动进行调整):
    假设您有一个全局的网络接口变量,例如 extern struct netif gnetif; (通常在 lwip_comm.c 或 main.c 中定义和初始化)。
          // 在一个新的 .c 文件或者一个合适的地方添加,例如 phy_monitor.c#include "lwip/opt.h"#include "lwip/netif.h"#include "lan8720.h" // 假设您有访问LAN8720寄存器的函数#include "lwip_comm.h" // 为了 gnetif (如果在这里声明)#include "os_cpu.h"    // For OS_CPU_SR#include "ucos_ii.h"   // For UCOSII API// 假设 gnetif 在其他地方定义extern struct netif gnetif;#define PHY_LINK_CHECK_INTERVAL_MS 1000 // 每1秒检查一次#define PHY_LINK_TASK_PRIO         (TCPSERVER_PRIO + 1) // 确保优先级合适#define PHY_LINK_TASK_STK_SIZE     128OS_STK PhyLinkTaskStk[PHY_LINK_TASK_STK_SIZE];static u8_t last_link_state = 0; // 0 = down, 1 = up// 您需要实现这个函数来读取LAN8720的链路状态// 返回值: 1 表示链路UP, 0 表示链路DOWNu8_t LAN8720_ReadLinkStatus(void) {    // 示例:具体实现依赖于您的ETH HAL驱动和PHY访问函数    // uint32_t phy_reg_val;    // if (HAL_ETH_ReadPHYRegister(&heth, LAN8720_PHY_ADDRESS, PHY_BSR, &phy_reg_val) == HAL_OK) {    //     if (phy_reg_val & PHY_LINKED_STATUS) { // PHY_BSR_LS_BIT or similar macro    //         return 1; // Link is Up    //     }    // }    // return 0; // Link is Down    // 这是一个占位符,您需要用实际的PHY读取逻辑替换它    // 例如,很多STM32的LwIP移植会在ethernetif.c中有一个类似的函数    // 或者直接通过ETH_ReadPHYRegister    uint16_t bsr_val;    // 伪代码: your_read_phy_register_function(LAN8720_PHY_ADDRESS, 1, &bsr_val); // 1 is BSR address    // if (bsr_val & (1 << 2)) return 1; // Bit 2 is Link Status    // else return 0;    return ethernetif_link_status(&gnetif); // 如果您的驱动有类似函数}void phy_link_check_thread(void *arg) {    LWIP_UNUSED_ARG(arg);    u8_t current_link_state;    // 确保 gnetif 已经初始化    while(gnetif.hwaddr_len == 0) { // 简单等待netif被添加        OSTimeDlyHMSM(0, 0, 0, 100);    }    last_link_state = LAN8720_ReadLinkStatus();    if(last_link_state) {        netif_set_link_up(&gnetif);    } else {        netif_set_link_down(&gnetif);    }    while (1) {        current_link_state = LAN8720_ReadLinkStatus();        if (current_link_state != last_link_state) {            if (current_link_state) { // Link transitions to UP                printf("Network cable connected.\r\n");                netif_set_link_up(&gnetif); // 通知LwIP链路已连接                                            // LwIP的netif status callback通常会处理DHCP重启等            } else { // Link transitions to DOWN                printf("Network cable disconnected.\r\n");                netif_set_link_down(&gnetif); // 通知LwIP链路已断开            }            last_link_state = current_link_state;        }        OSTimeDlyHMSM(0, 0, 0, PHY_LINK_CHECK_INTERVAL_MS);    }}void phy_link_task_init(void) {    INT8U err;    err = OSTaskCreate(phy_link_check_thread,                       (void *)0,                       &PhyLinkTaskStk[PHY_LINK_TASK_STK_SIZE - 1],                       PHY_LINK_TASK_PRIO);    if (err != OS_ERR_NONE) {        printf("Failed to create PHY Link Check Task\r\n");    }}```    **初始化**:在您的系统初始化代码中(例如 `main` 函数,LwIP 初始化之后),调用 `phy_link_task_init();`。   


  • LwIP 网络接口回调 (Netif Status Callback)
    LwIP 允许为网络接口注册一个状态改变回调函数。当 IP 地址改变、接口 UP/DOWN 时,这个回调会被调用。确保这个回调函数能够正确处理:
    • 当链路恢复 (netif_set_link_up 被调用后) 且接口配置为使用 DHCP 时,它应该重新启动 DHCP 客户端以获取新的 IP 地址。
    • 许多正点原子的 LwIP 移植例程中,ethernetif_init() 函数内部或者 netif_add() 时设置的 netif->status_callback (例如 netif_set_status_callback(&gnetif, lwip_netif_status_callback);) 会处理这个逻辑。当 netif_is_link_up() 时,如果 DHCP 使能,会调用 dhcp_start() 或类似的函数。

    您需要检查您的 ethernetif.c (或类似的网络接口驱动文件) 和 LwIP 的初始化过程,确保:
    • netif_set_link_up(&gnetif) 会触发 netif_set_up(&gnetif)(如果接口之前是 down 的)。
    • 如果使用 DHCP (LWIP_DHCP 为 1),dhcp_start(&gnetif) 会在链路和接口都 UP 之后被调用。
    • netif_set_link_down(&gnetif) 会导致 netif_set_down(&gnetif) 被调用,并且 DHCP 客户端(如果运行)会被停止 (dhcp_stop(&gnetif) 或 dhcp_release_and_stop(&gnetif))。


二、 增强 TCP 服务器代码的健壮性
您的 TCP 服务器代码需要能够优雅地处理因网络断开导致的连接错误,并在网络恢复后能继续工作。
      // tcp_server_demo.c (您的代码)// ... (includes 和其他定义保持不变) ...// 假设 gnetif 在其他地方定义,并且是全局可访问的extern struct netif gnetif; //tcp服务器任务static void tcp_server_thread(void *arg){        // OS_CPU_SR cpu_sr; // 在UCOSII中,OS_ENTER_CRITICAL/OS_EXIT_CRITICAL宏内部处理cpu_sr        u32 data_len = 0;        struct pbuf *q;        err_t err,recv_err;        u8 remot_addr[4];        struct netconn *conn, *newconn;        static ip_addr_t ipaddr; // 保持 static 以便在函数调用间持久        static u16_t port;       // 保持 static              LWIP_UNUSED_ARG(arg);        // 创建主监听连接        conn = netconn_new(NETCONN_TCP);        if (conn == NULL) {            printf("TCP Server: Failed to create new netconn (conn).\r\n");            return; // 任务无法继续        }              err = netconn_bind(conn, IP_ADDR_ANY, TCP_SERVER_PORT);        if (err != ERR_OK) {            printf("TCP Server: Failed to bind netconn (conn), err=%d.\r\n", err);            netconn_delete(conn);            return; // 任务无法继续        }                netconn_listen(conn); // 进入监听模式                // conn->recv_timeout = 10; // 设置监听conn的超时会导致accept非阻塞。                                 // 如果希望accept阻塞直到有连接,或者有较长超时,可以修改或移除这里。                                 // 如果移除,accept将阻塞。如果设置为0,也会阻塞。                                 // 短超时会导致accept频繁返回ERR_TIMEOUT,增加CPU消耗。                                 // 对于监听socket,通常建议阻塞或长超时。                                 // 假设我们暂时保持,但需注意其影响。        while (1) // 主循环,接受新连接        {                // 检查网络接口和链路状态,如果网络未就绪,则延迟并重试 accept                // 这是为了避免在网络故障时 netconn_accept 持续快速返回错误或超时                while (!netif_is_up(&gnetif) || !netif_is_link_up(&gnetif) || ip_addr_isany(netif_ip4_addr(&gnetif))) {                    // printf("TCP Server: Waiting for network to be ready...\r\n");                    // 如果 conn 设置了短超时,这里可以不用 OSTimeDly,否则accept会阻塞                    // 如果 conn 是阻塞 accept,这里需要一个延时来避免在网络恢复前卡死                    OSTimeDlyHMSM(0, 0, 1, 0); // 等待1秒                }                err = netconn_accept(conn, &newconn); // 接收连接请求                                if (err == ERR_OK)                 {                        // newconn->recv_timeout = 10; // 设置已连接socket的接收超时,10ms可能太短,根据需要调整                                                    // 例如设置为1000 (1秒) 或根据应用需求                        newconn->recv_timeout = 1000; // 改为1秒超时,以便在无数据时不会卡死,也能检测到连接问题                        netconn_getaddr(newconn, &ipaddr, &port, 0); //获取远端IP地址和端口号                                               remot_addr[3] = (u8_t)(ipaddr.addr >> 24);                        remot_addr[2] = (u8_t)(ipaddr.addr >> 16);                        remot_addr[1] = (u8_t)(ipaddr.addr >> 8);                        remot_addr[0] = (u8_t)(ipaddr.addr);                        printf("Host %d.%d.%d.%d:%d connected to server.\r\n", remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port);                                               while(1) // 处理单个客户端连接的循环                        {                                // 检查链路状态,如果链路断开,主动关闭这个 newconn                                if (!netif_is_link_up(&gnetif)) {                                    printf("Link down, closing connection with %d.%d.%d.%d:%d\r\n", remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port);                                    // 后续的 netconn_recv 或 netconn_write 应该会失败                                    // 或者直接关闭                                    // netconn_close(newconn); // 会在下面recv_err处理中覆盖                                    // netconn_delete(newconn);                                    // break; // 跳出对此客户端的处理                                }                                if((tcp_server_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送                                {                                        // 发送前也检查一下链路                                        if (netif_is_link_up(&gnetif)) {                                            err_t write_err = netconn_write(newconn ,tcp_server_sendbuf,strlen((char*)tcp_server_sendbuf),NETCONN_COPY);                                            if(write_err != ERR_OK)                                            {                                                    printf("Send failed, err: %d. Closing connection with %d.%d.%d.%d:%d\r\n", write_err, remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port);                                                    // 发送失败通常意味着连接已损坏                                                    netconn_close(newconn); // 尝试关闭                                                    netconn_delete(newconn); // 删除                                                    tcp_server_flag &= ~LWIP_SEND_DATA; // 清除标志                                                    goto next_client; // 跳到外层循环等待下一个客户端                                            }                                        } else {                                            printf("Link down, cannot send data.\r\n");                                            // 可以选择清除发送标志或等待链路恢复                                        }                                        tcp_server_flag &= ~LWIP_SEND_DATA;                                }                                                              recv_err = netconn_recv(newconn, &recvbuf); //接收到数据                                                                if(recv_err == ERR_OK)                                {                                                       OS_ENTER_CRITICAL();                                        memset(tcp_server_recvbuf,0,TCP_SERVER_RX_BUFSIZE);                                        data_len = 0; // 每次接收前重置 data_len                                        for(q=recvbuf->p;q!=NULL;q=q->next)                                        {                                                if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len)) {                                                    memcpy(tcp_server_recvbuf+data_len,q->payload,(TCP_SERVER_RX_BUFSIZE-data_len));                                                    data_len = TCP_SERVER_RX_BUFSIZE; // 已满                                                } else {                                                    memcpy(tcp_server_recvbuf+data_len,q->payload,q->len);                                                    data_len += q->len;                                                         }                                                if(data_len >= TCP_SERVER_RX_BUFSIZE) break;                                         }                                        OS_EXIT_CRITICAL();                                        // data_len=0;  // 这行是错误的,data_len是本次接收到的总长度,不应在这里清零                                        if (data_len > 0) { // 确保真的收到了数据再打印                                           printf("Received from %d.%d.%d.%d:%d: %s\r\n", remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port, tcp_server_recvbuf);                                        }                                        netbuf_delete(recvbuf); // 释放接收到的pbuf                                }                                 else if (recv_err == ERR_TIMEOUT)                                 {                                    // 接收超时是正常的,因为设置了 newconn->recv_timeout                                    // 在这里可以检查应用层的心跳或者其他逻辑                                    // 如果长时间超时且无数据,客户端可能已掉线,但TCP本身未断开                                    // 或者,如果此时链路已断开,则应关闭连接                                    if (!netif_is_link_up(&gnetif)) {                                        printf("Link down during recv timeout. Closing connection with %d.%d.%d.%d:%d\r\n", remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port);                                        netconn_close(newconn);                                        netconn_delete(newconn);                                        goto next_client; // 或 break;                                    }                                }                                else // 其他错误 (ERR_CLSD, ERR_RST, ERR_ABRT, ERR_CONN等) 都表示连接问题                                {                                        if (recv_err == ERR_CLSD) {                                            printf("Host %d.%d.%d.%d:%d disconnected (closed by remote).\r\n",remot_addr[0], remot_addr[1],remot_addr[2],remot_addr[3], port);                                        } else {                                            printf("Receive error (err: %d) on connection with %d.%d.%d.%d:%d. Closing.\r\n", recv_err, remot_addr[0], remot_addr[1], remot_addr[2], remot_addr[3], port);                                        }                                        netconn_close(newconn);                                        netconn_delete(newconn);                                        goto next_client; // 或 break;                                }                        } // end inner while for client handling                next_client:; // 标签用于跳出内层循环                }                else if (err == ERR_TIMEOUT)                {                    // netconn_accept 超时,如果 conn->recv_timeout 设置了。                    // 这是正常的,表示在超时期间内没有新连接。                    // 可以检查一下全局状态或简单地继续循环。                    // printf("TCP Server: No new connection within timeout.\r\n");                    // 在这里可以短暂延时,避免CPU空转太快,如果 accept 超时很短                    if (conn->recv_timeout > 0 && conn->recv_timeout < 100) { // 如果超时非常短                        OSTimeDlyHMSM(0,0,0,50); // 短暂延时                    }                }                else // netconn_accept 返回其他错误                {                        printf("TCP Server: netconn_accept failed with err = %d.\r\n", err);                        // 如果accept失败,可能监听socket本身出了问题,或者LwIP栈状态有问题                        // 此时可以尝试延时后重试,或者更极端地,重新创建监听 conn                        // 如果错误是 ERR_MEM 之类的,那问题更严重                        if (err == ERR_ABRT || err == ERR_CONN) { // 比较严重的错误                            printf("TCP Server: Recreating listening socket due to accept error.\r\n");                            netconn_close(conn);                            netconn_delete(conn);                                                        conn = netconn_new(NETCONN_TCP);                            if (conn == NULL) {                                printf("TCP Server: CRITICAL - Failed to recreate listening netconn. Halting task.\r\n");                                return;                             }                            err_t bind_err = netconn_bind(conn, IP_ADDR_ANY, TCP_SERVER_PORT);                            if (bind_err != ERR_OK) {                                printf("TCP Server: CRITICAL - Failed to re-bind. Halting task. Err: %d\r\n", bind_err);                                netconn_delete(conn);                                return;                            }                            netconn_listen(conn);                            // conn->recv_timeout = 10; // 重新设置(如果需要)                             printf("TCP Server: Listening socket recreated.\r\n");                        }                        OSTimeDlyHMSM(0, 0, 1, 0); // 发生错误后等待一段时间再重试                }        }}// ... (tcp_server_init 函数保持不变,但要确保在main中调用了 phy_link_task_init)   
IGNORE_WHEN_COPYING_START
content_copy  download
Use code with caution. CIGNORE_WHEN_COPYING_END

关键修改点解释:
  • phy_link_check_thread 和 phy_link_task_init:新增的物理链路监控任务。
  • LAN8720_ReadLinkStatus:这是一个需要您根据硬件和驱动具体实现的函数,用于读取PHY的链路状态。很多时候,LwIP 的 ethernetif 驱动中已经有类似机制或函数,可以直接调用或参考。例如,正点原子的例程中 ethernetif_link_check_thread 实现了类似功能,并调用 ethernetif_set_link()。您需要确保这个机制是有效的。
  • TCP 服务器线程 (tcp_server_thread) 内的健壮性增强
    • 在 netconn_accept 之前,增加了对网络状态 (netif_is_up, netif_is_link_up, IP地址是否有效) 的检查。
    • 调整了 newconn->recv_timeout,10ms 对于检测断连可能太短,可以适当加长。
    • 在处理已连接客户端的循环中,增加了对 netif_is_link_up(&gnetif) 的检查。如果链路断开,可以更早地判断连接已失效。
    • netconn_write 失败时,认为连接已损坏,关闭并删除 newconn,然后使用 goto next_client; 跳出当前客户端处理循环,准备接受下一个连接。
    • netconn_recv 返回 ERR_TIMEOUT 时,也检查链路状态。如果链路断开,则关闭连接。
    • 对 netconn_recv 的其他错误类型(如 ERR_RST, ERR_CONN 等)也统一作为连接断开处理。
    • 对 netconn_accept 的错误处理:如果 accept 返回 ERR_TIMEOUT,这是正常的(如果设置了 conn->recv_timeout)。对于其他严重错误,增加了重新创建监听 conn 的逻辑,这是一种更强的恢复机制。
    • data_len=0; 在 for 循环之前是正确的,在 OS_EXIT_CRITICAL() 之后清零是错误的,因为它会丢失刚接收数据的长度信息。应在下一次 memset 前或作为循环初始化的一部分。
    • OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL() 仅保护了 tcp_server_recvbuf 的访问和 data_len 的累加,这是合理的。

  • 监听 conn 的 recv_timeout
    原代码中 conn->recv_timeout = 10; 会让 netconn_accept 每10ms超时一次。这对于监听socket来说,通常不是最佳实践,因为它会导致CPU频繁唤醒。如果主要目的是等待连接,建议:
    • 移除 conn->recv_timeout = 10;,让 netconn_accept 阻塞。
    • 或者设置一个更长的超时,例如 conn->recv_timeout = 1000; (1秒)。
      如果选择阻塞式 accept,则 tcp_server_thread 中 while (!netif_is_up(&gnetif) || ...) 循环里的 OSTimeDlyHMSM 是有意义的,以避免在网络未就绪时 accept 卡死。


集成和测试步骤:
  • 实现 LAN8720_ReadLinkStatus():这是最关键的一步,确保能准确获取链路状态。参考您的 STM32 以太网驱动和 LAN8720 数据手册。
  • 初始化 PHY 监控任务:在 main() 函数中,LwIP 初始化 (lwip_init() 或 MX_LWIP_Init()) 和网络接口添加 (netif_add()) 之后,创建并启动 phy_link_check_thread 任务。
  • 编译并烧录:将修改后的代码编译并下载到 STM32F407。
  • 测试热插拔
    • 启动设备,等待网络连接成功,TCP 服务器应该可以正常工作。
    • 拔掉网线。通过串口打印确认PHY监控任务检测到链路断开,LwIP 的 netif_set_link_down 被调用。如果之前有客户端连接,该连接应该会超时或出错并关闭。
    • 重新插上网线。通过串口打印确认PHY监控任务检测到链路恢复,LwIP 的 netif_set_link_up 被调用。如果使用 DHCP,观察 DHCP 客户端是否重新获取 IP 地址。
    • 一旦网络恢复,TCP 服务器应该能够再次接受新的客户端连接。


请仔细检查您的 LwIP 移植版本和相关的驱动代码(特别是 ethernetif.c),因为链路状态的处理与驱动实现紧密相关。正点原子的LwIP例程通常会有一个 ethernetif_set_link() 函数以及一个链路检查线程,您可以基于那个框架进行修改或确认其行为。

回复

使用道具 举报

9

主题

26

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3320
金钱
3320
注册时间
2021-12-3
在线时间
198 小时
 楼主| 发表于 2025-5-23 08:18:30 | 显示全部楼层
上述回复是DEEPSEEK上的吧,我也在DeepSeek上寻求过,而且按他的输入代码测试过,也是不行的。我现在的问题是:在网络助手连接正常发送数据时插拔网线再重连来发送接收数据,就不行了。而在网络助手点击断开不发送数据时插拔网线再点击网络助手连接就可以正常发送接收数据?
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165524
金钱
165524
注册时间
2010-12-1
在线时间
2116 小时
发表于 2025-5-24 22:33:46 | 显示全部楼层
A571157242 发表于 2025-5-22 09:38
好的,您提供的代码是基于正点原子 STM32F407 开发板,使用 UCOSII 操作系统和 LwIP 网络协议栈(netconn A ...

AI YYSD
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复

使用道具 举报

2

主题

34

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
349
金钱
349
注册时间
2022-4-13
在线时间
59 小时
发表于 7 天前 | 显示全部楼层
留个邮箱,给你发个参考的代码
回复

使用道具 举报

9

主题

26

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3320
金钱
3320
注册时间
2021-12-3
在线时间
198 小时
 楼主| 发表于 6 天前 | 显示全部楼层
65968535,您好!非常感谢!我的邮箱:gsr64@163.com.
回复

使用道具 举报

9

主题

26

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3320
金钱
3320
注册时间
2021-12-3
在线时间
198 小时
 楼主| 发表于 6 天前 | 显示全部楼层
A571157242,您好!非常感谢您的回复。
回复

使用道具 举报

2

主题

34

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
349
金钱
349
注册时间
2022-4-13
在线时间
59 小时
发表于 6 天前 | 显示全部楼层
gsr64 发表于 2025-5-27 07:54
65968535,您好!非常感谢!我的邮箱:.

已发,请注意查收
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-2 20:20

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表