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);
}

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

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

方法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);
}

方法3:利用netif接口

if (netif_is_link_up(&gnetif))
{
    BSP_LED_Off(LED2);
}
else
{
    BSP_LED_On(LED2);
}

注意,方法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);
}
  • 2
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
引用\[1\]:关于如何实现STM32F407开发板和PC端的以太网通信,如何完美Ping通。 相关文章链接:【HAL库】STM32CubeMX开发----STM32F407----ETH+LAN8720A+LWIP----ping通 一、TCPclient客户端----相关函数 。 引用\[2\]:1.打开STM32CubeMX,新建一个工程,我的板子是官方NUCLEO-F429ZI,自带网口,适合搞网络协议栈。首先打开外设ETH,注意引脚要与实际对应。 2.参数设置,注意PHY地址要设为0 3.打开调试串口USART1,参数按默认的即可 4.打开FreeRTOS和LWIP,参数也不用更改,注意它默认使用了DHCP自动获取IP,当然也可以自己手动设置,自己设置的好处是提前知道自己的IP,测试时可以直接用电脑ping开发板,来检查网络是否通畅。 5.最后勾上串行调试口,并使能180MHz的时钟 6.为了提高MDK编译速度,工程中使用的AC6编译器,但编译LWIP和FreeRTOS还需要更改一些东西,如果使用AC5就不用管6 7 8三点。首先在cc.h文件中注释掉如下代码 7.然后在lwip.h文件中添加如下代码 8.从STM32CubeMX的库包路径下(E:\Program Files (x86)\CubeMXPack\Repository\STM32Cube_FW_F4_V1.25.0\Middlewares\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM4F)拷贝这两个文件到工程如下的路径。STM32CubeMX它默认安装库包的路径应该在C盘,此处我更改过,该路径在CubeMX软件中也能看到。 9.添加串口printf重定向函数 10.终于到了APP函数了,注释都写在源代码里,我就不啰嗦了。服务器关闭监听后再开启,开发板可以重连,有需要的同学可以看看 。 引用\[3\]:创建 两个文件 TCPclient.c 和 TCPclient.h,用来存储功能程序。 步骤1:创建 TCPclient 初始化函数 void TCP_Client_Init(void) { struct tcp_pcb *client_pcb = NULL; ip4_addr_t server_ip; //服务器的IP地址 client_pcb = tcp_new(); //创建一个TCP控制块 IP4_ADDR(&server_ip, 192,168,1,20); //将目标服务器IP写入结构体,这里是PC机本地IP地址 if(client_pcb != NULL) { //开始与目标服务器进行连接,参数包括服务器IP和服务器端口 tcp_connect(client_pcb, &server_ip, 18099, client_connected); //注册异常处理函数 tcp_err(client_pcb, client_error); } } 步骤2:创建 注册回调函数 /* TCP客户端连接到服务器回调函数 */ static err_t client_connected(void *arg, struct tcp_pcb *pcb, err_t err) { //注册一个周期性回调函数---最快500ms一次 tcp_poll(pcb,client_poll,1); //注册一个发送回调函数 tcp_sent(pcb, client_send); //注册一个接收回调函数 tcp_recv(pcb,client_recv); return ERR_OK; } 步骤3:编写 数据处理函数 1. 周期轮询处理函数 。 问题:stm32 lwip 客户端 回答: 要在STM32上实现LWIP客户端,可以按照以下步骤进行操作。首先,打开STM32CubeMX并创建一个新的工程,选择适合的开发板和外设ETH。确保引脚与实际对应,并设置PHY地址为0。然后打开调试串口和使能180MHz的时钟。接下来,打开FreeRTOS和LWIP,并根据需要设置IP地址(可以使用DHCP自动获取或手动设置)。最后,勾选串行调试口并保存工程。为了提高编译速度,可以根据使用的编译器进行相应的更改。添加串口printf重定向函数,并在APP函数中实现TCP客户端的初始化和相关回调函数。具体的代码实现可以参考相关文章中的示例代码。\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [【HAL库】STM32CubeMX开发----STM32F407----ETH+LWIP----TCPclient客户端](https://blog.csdn.net/MQ0522/article/details/128242918)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [使用LWIP的netconn接口在STM32上建立一个客户端](https://blog.csdn.net/qq_43527819/article/details/110050717)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值