• STM32 LWIP Server、Client如何判断网络异常


    平台NUCLEO-H723ZG & LAN8742

    方法1:利用TCP的keepalive机制

      1. 在lwipopts.h中添加宏#define LWIP_TCP_KEEPALIVE 1 来使能keeplive机制。

    在这里插入图片描述

      1. 代码如下,主要是使能心跳包机制以及设置相应的时间的次数。当拔掉网线后约1s多时间后串口显示客户端断开连接。
    #define PORT 5001
    #define RECV_BUF_SIZE (1024)
    
    static void
    tcp_socket_echo_server_thread(void *arg)
    {
        struct sockaddr_in server_addr, client_addr;
        int server_fd = -1, cliend_fd = -1;
        socklen_t sin_size;
        int recv_buf_len,write_buf_len;
    
        int so_keepalive_val = 1;    //使能心跳机制
        int tcp_keepalive_idle = 1;  //发送心跳空闲周期 单位:秒
        int tcp_keepalive_intvl = 1; //发送心跳间隔 单位:秒
        int tcp_keepalive_cnt = 1;   //重发次数
        int tcp_nodelay = 1;         //不延时发送到合并包
    
        int err = 0;
        char *recv_data = (char *)pvPortMalloc(RECV_BUF_SIZE);
    
        if (recv_data == NULL)
        {
            LOGI("No memory\n");
            goto __exit;
        }
    
        server_fd = socket(AF_INET, SOCK_STREAM, 0);
         //使能心跳机制,默认没有使能
        err = setsockopt(server_fd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));
        LOGI("err : %d\r\n", err);
        if (server_fd < 0)
        {
            LOGI("Socket error\n");
            goto __exit;
        }
    
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(PORT);
        memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
    
        if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
        {
            LOGI("Unable to bind\n");
            goto __exit;
        }
    
        if (listen(server_fd, 5) == -1)
        {
            LOGI("Listen error\n");
            goto __exit;
        }
    
        while (1)
        {
            sin_size = sizeof(struct sockaddr_in);
    
            cliend_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size);
    
            LOGI("new client connected from (%s, %d)\n",
                 inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
            //配置心跳检测参数,默认参数时间很长。必须在accept之后,因为不是同一个socket。
            err = setsockopt(cliend_fd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
            err = setsockopt(cliend_fd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
            err = setsockopt(cliend_fd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
    		//err = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int));
            while (1)
            {
                recv_buf_len = read(cliend_fd, recv_data, RECV_BUF_SIZE);
                if (recv_buf_len <= 0)
                {
                    LOGI("read error!\r\n");
                    break;
                }
                LOGI("recv %d len data\n", recv_buf_len);
                write_buf_len = write(cliend_fd, recv_data, recv_buf_len);
                if (write_buf_len <= 0)
                {
                    LOGI("write error!\r\n");
                    break;
                }
            }
    
            if (cliend_fd >= 0)
            {
                LOGI("close %d\n", cliend_fd);
                close(cliend_fd);
            }
            cliend_fd = -1;
        }
    
    __exit:
    
        if (server_fd >= 0)
            close(server_fd);
        if (recv_data)
            vPortFree(recv_data);
        vTaskDelete(NULL);
        printf("Task exited!\r\n");
    }
    
    void tcp_socket_echo_server_init(void)
    {
        sys_thread_new("tcp_socket_echo_server_thread", tcp_socket_echo_server_thread, NULL, 2048, osPriorityLow);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    也可以直接修改全局的默认时间,但是不推荐,这里主要是这三个量的含义:

    TCP_KEEPIDLE_DEFAULT: 空闲多少时间内没有数据传输,就会发送keepalive的package来检查是否连接
    TCP_KEEPINTVL_DEFAULT:发送package的时间间隔
    TCP_KEEPCNT_DEFAULT:检查没有连接的次数就会报错
    
    • 1
    • 2
    • 3

    方法2:读于PHY芯片的BSR寄存器判断

    这里使用的是NUCLEO-H723ZG的开发板,PHY芯片型号为LAN8742。

    uint32_t regvalue = 0;
    HAL_ETH_ReadPHYRegister(&EthHandle, LAN8742A_PHY_ADDRESS, LAN8742_BSR, &regvalue);
    if(regvalue & LAN8742_BSR_LINK_STATUS)
    {
        BSP_LED_On(LED3);
    }
    else
    {
        BSP_LED_Off(LED3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法3:利用netif接口

    if (netif_is_link_up(&gnetif))
    {
        BSP_LED_Off(LED2);
    }
    else
    {
        BSP_LED_On(LED2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意,方法2和3判断的是Server的以太网硬件是否连接到了网络,之间检测服务器自己的故障。方法2和3我将其放到一个单独的Task中循环查询判断。这样并不会影响LWIP的正常工作。

    Client使keepalive来判断连接是否正常

    断开网线后串口会显示断开,重新插上网线又会连接到服务器

    #define SERVER_PORT (6666UL)    //目标地址端口号
    #define SERVER_IP "192.168.1.1" /*目标地址IP*/
    
    static void
    tcp_socket_echo_client_thread(void *arg)
    {
        struct sockaddr_in server_addr, client_addr;
        int server_fd = -1, cliend_fd = -1;
        socklen_t sin_size;
        int recv_buf_len, write_buf_len;
    
        int so_keepalive_val = 1;    //使能心跳机制
        int tcp_keepalive_idle = 1;  //发送心跳空闲周期 单位:秒
        int tcp_keepalive_intvl = 1; //发送心跳间隔 单位:秒
        int tcp_keepalive_cnt = 1;   //重发次数
        int tcp_nodelay = 1;         //不延时发送到合并包
    
        int err = 0;
        char *recv_data = (char *)pvPortMalloc(RECV_BUF_SIZE);
    
        if (recv_data == NULL)
        {
            LOGI("No memory\n");
            goto __exit;
        }
    
        while (1)
        {
            server_fd = socket(AF_INET, SOCK_STREAM, 0);
            //使能心跳机制,默认没有使能
            err = setsockopt(server_fd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));
            LOGI("err : %d\r\n", err);
            if (server_fd < 0)
            {
                LOGI("socket error\n");
                goto __exit;
            }
    
            server_addr.sin_family = AF_INET;
            server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
            server_addr.sin_port = htons(SERVER_PORT);
    
    		//err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
    		//err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
    		//err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
                
            if (connect(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) < 0)
            {
                LOGI("connect error\r\n");
                close(server_fd);
                continue;
            }
            else
            {
                //配置心跳检测参数,默认参数时间很长,不配置的话要等待很长时间。
                //可以在connect之前配置。如果断网,read就会阻塞相应的次数后就不阻塞了,就会一直报错,直到网线重新连接。
                err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
                err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
                err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
            }
    
            while (1)
            {
                recv_buf_len = read(server_fd, recv_data, RECV_BUF_SIZE);
                if (recv_buf_len <= 0)
                {
                    close(server_fd);
                    LOGI("read error!\r\n");
                    break;
                }
                LOGI("recv %d len data\n", recv_buf_len);
                write_buf_len = write(server_fd, recv_data, recv_buf_len);
                if (write_buf_len <= 0)
                {
                    LOGI("write error!\r\n");
                    break;
                }
            }
            vTaskDelay(100);
        }
    
    __exit:
        if (server_fd >= 0)
            close(server_fd);
        if (recv_data)
            vPortFree(recv_data);
        vTaskDelete(NULL);
        printf("Task exited!\r\n");
    }
    
    void tcp_socket_echo_client_init(void)
    {
        sys_thread_new("tcp_socket_echo_client_thread", tcp_socket_echo_client_thread, NULL, 2048, osPriorityLow);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
  • 相关阅读:
    XSAN数据恢复-存储空间架构迁移时误格式化存储系统的XSAN数据恢复案例
    Chrome 调试学习
    Real Distributed APEX
    UNet涉及的重点函数记录
    天猫用户重复购买预测(速通二)
    【论文发表】2022 HIRE--首篇基于异构图神经网络的高阶关系知识蒸馏方法
    良心之作,7 个值得收藏的 GitHub 开源项目!
    开开心心带你学习MySQL数据库之第七篇
    续:关于JS中代理Proxy的一些知识
    组件安全概述
  • 原文地址:https://blog.csdn.net/qq_42820594/article/details/126197207