问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

lwip协议栈TCP/IP异常断开调试笔记

创作时间:
作者:
@小白创作中心

lwip协议栈TCP/IP异常断开调试笔记

引用
CSDN
1.
https://blog.csdn.net/weixin_43777852/article/details/143934050

本文记录了在使用lwip协议栈实现TCP/IP通信时遇到的两个典型问题及其解决方案,包括服务端异常断开后的处理和FreeRTOS断言错误的调试方法。

问题一:异常掉线

异常断开模拟

  1. 单片机端做服务端(只监听一个客户端),电脑做客户端连接
  2. 尝试连接确定通信正常,断开网线。电脑客户端点击断开
  3. 经过一段时间(超过TCP/IP 3次握手时间)
  4. 接回网线后发现可以连接上但通信异常

原因分析

在服务端代码中,当客户端异常断开时,服务端会一直阻塞在recv函数调用处,导致无法处理新的连接请求。

void StartDefaultTask(void *argument)
{
    /* init code for LWIP */
    MX_LWIP_Init();
    /* USER CODE BEGIN StartDefaultTask */
    struct sockaddr_in server_addr,client_addr;
    socklen_t sin_size;
    int recv_data_len;
    static uint8_t recv_data[RECV_DATA];
    again:
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LWIP_TCP_DEBUG("Socket error\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    //
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(LOCAL_PORT);
    memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        LWIP_TCP_DEBUG("Unable to bind\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    if (listen(sockfd, BACKLOG) == -1)
    {
        LWIP_TCP_DEBUG("Listen error\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    /* Infinite loop */
    for(;;)
    {
        sin_size = sizeof(struct sockaddr_in);
        connected = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
        LWIP_TCP_DEBUG("new client connected from (%s, %d)\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        int tcp_nodelay = 1;//don't delay send to coalesce packets
        setsockopt(connected,IPPROTO_TCP,TCP_NODELAY,(void *) &tcp_nodelay,sizeof(int));
        while(1)
        {
            recv_data_len = recv(connected, recv_data, RECV_DATA, 0);
            if (recv_data_len <= 0)
            {
                break;
            }
            //			write(connected,recv_data,recv_data_len);
            writeToRxBuf(recv_data, recv_data_len);
        }
        if (connected >= 0)
        {
            close(connected);
        }
        connected = -1;
        //osDelay(1);
    }
    /* USER CODE END StartDefaultTask */
}

解决方案

通过启用TCP的keepalive机制,可以及时检测到客户端的异常断开,避免服务端长时间阻塞。

void StartDefaultTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN StartDefaultTask */
    struct sockaddr_in server_addr,client_addr;
    socklen_t sin_size;
    int recv_data_len;
    static uint8_t recv_data[RECV_DATA];
    int so_keepalive_val = 1;    //使能心跳机制
    int tcp_keepalive_idle = 3;  //发�?�心跳空闲周�? 单位:秒
    int tcp_keepalive_intvl = 3; //发�?�心跳间�? 单位:秒
    int tcp_keepalive_cnt = 3;   //重发次数
//	int tcp_nodelay = 1;         //不延时发送到合并�?
    int err = 0;
    again:
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LWIP_TCP_DEBUG("Socket error\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    //使能心跳机制,默认没有使�?
    err = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));
    if(err){}
    //
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(LOCAL_PORT);
    memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
    {
        LWIP_TCP_DEBUG("Unable to bind\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    if (listen(sockfd, BACKLOG) == -1)
    {
        LWIP_TCP_DEBUG("Listen error\n");
        close(sockfd);
        vTaskDelay(100);
        goto again;
    }
    /* Infinite loop */
    for(;;)
    {
        sin_size = sizeof(struct sockaddr_in);
        connected = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
        //配置心跳�?测参数,默认参数时间很长。必须在accept之后,因为不是同�?个socket�?
        err = setsockopt(connected, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
        err = setsockopt(connected, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
        err = setsockopt(connected, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
        LWIP_TCP_DEBUG("new client connected from (%s, %d)\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        int tcp_nodelay = 1;//don't delay send to coalesce packets
        setsockopt(connected,IPPROTO_TCP,TCP_NODELAY,(void *) &tcp_nodelay,sizeof(int));
        while(1)
        {
            recv_data_len = recv(connected, recv_data, RECV_DATA, 0);
            //			recv_data_len = recv(connected, recv_data, RECV_DATA, MSG_DONTWAIT);
            //	        if (recv_data_len == -1)
            //	        {
            //	            if (errno == EAGAIN || errno == EWOULDBLOCK)
            //	            {
            //	            	osDelay(1);
            //	                continue;
            //	            }
                            perror("read");
                            exit(-1);
            //	            break;
            //	        }else if(recv_data_len > 0){
            //	        	writeToRxBuf(recv_data, recv_data_len);
                            printf("recv client data : %s\n", recv_buf);
            //	        }else if(recv_data_len == 0){
                            printf("client closed\n");
            //	            break;
            //	        }
            if (recv_data_len <= 0)
            {
                break;
            }
            //						write(connected,recv_data,recv_data_len);
            writeToRxBuf(recv_data, recv_data_len);
        }
        if (connected >= 0)
        {
            close(connected);
        }
        connected = -1;
        //osDelay(1);
    }
  /* USER CODE END StartDefaultTask */
}

问题二:卡死configASSERT( pxQueue->uxItemSize == 0 );

现象

客户端(电脑)显示还连着,可以发送数据,但是收不到数据。原因是已经先输入断言的死循环了。

调试方法

通过FreeRTOS社区论坛的讨论,发现可能是编译器或IDE相关的问题。最终通过更换IDE(从IAR换到Keil)解决了该问题。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号