You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Mbed TLS与LwIP的客户端连接强制关闭方法问询

问题描述

我有一台搭载FreeRTOS、LWIP、MbedTLS的STM32F4设备,与服务器建立TLS连接后需要长期保持,用于进行30秒到10分钟间隔的双向消息交互。有时需要关闭连接来临时释放约40KB内存给其他任务,但此时TLS任务多数时候阻塞在mbedtls_ssl_read()函数上。

核心问题:如何从其他任务中断此TLS连接,让mbedtls_ssl_read()退出,并像服务器主动终止连接那样释放内存?

已尝试操作

我从其他任务调用了以下函数关闭连接:

shutdown(sock, SHUT_RDWR);
mbedtls_net_close(net_context);

通过Wireshark观察到设备(客户端)发送FIN、ACK包,服务器回复ACK包后无后续动作,但调用这些函数前后,任务的调用栈完全一致(栈终止于xQueueGenericReceive()函数)。设备与第三方自定义服务器、常规HTTPS服务器的连接行为类似,仅后者会产生4个数据包。

套接字超时设置

连接前设置了较长的套接字超时:

timeOut.tv_sec = 20 * 60;
timeOut.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void*)&timeOut, sizeof(struct timeval));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (void*)&timeOut, sizeof(struct timeval));

设置短超时会触发连接终止,但正常运行时不希望触发超时。

TLS任务工作流程(伪代码)

loop:
  if(TLS connection enabled)
    prepare TLS structs includes certs (malloc)
    set socket options (timeouts)
    set bio
    handshake
    verify cert
    loop2:
      mbedtls_ssl_read
      mbedtls_ssl_write or exit loop
    mbedtls_ssl_close_notify
    free all allocated structs

等待数据时的调用栈

Thread #5 (Suspended : Container)   
    xQueueGenericReceive() at queue.c
    sys_arch_mbox_fetch() at sys_arch.c
    netconn_recv_data() at api_lib.c
    netconn_recv_data_tcp() at api_lib.c
    netconn_recv_tcp_pbuf_flags() at api_lib.c
    lwip_recv_tcp() at sockets.c
    lwip_recvfrom() at sockets.c
    lwip_read() at sockets.c
    mbedtls_net_recv() at net_sockets.c
    mbedtls_ssl_fetch_input() at ssl_msg.c
    ssl_get_next_record() at ssl_msg.c
    mbedtls_ssl_read_record() at ssl_msg.c
    mbedtls_ssl_read() at ssl_msg.c
    HTTPD_SocketRead() at httpd.c
    HTML_Process() at html.c
    HTTPD_Cloud_Serve() at httpd.c
    Cloud_Task() at acloud.c

解决方案

针对FreeRTOS+LWIP+MbedTLS的环境,要从其他任务中断阻塞的mbedtls_ssl_read()并正确释放资源,可按以下步骤操作:

1. 结合FreeRTOS事件标志实现可中断等待

修改TLS任务的循环逻辑,不要单纯阻塞在mbedtls_ssl_read(),而是用短超时读取+退出信号检查的方式:

  • 定义一个FreeRTOS事件组,用于传递退出信号
  • 使用mbedtls_ssl_read_timeout()替代mbedtls_ssl_read(),设置1秒左右的短超时,每次超时后检查退出信号
  • 其他任务需要关闭连接时,设置事件组标志,触发TLS任务退出循环

示例修改后的伪代码:

// 全局定义事件组(需线程安全)
EventGroupHandle_t tls_exit_event = xEventGroupCreate();
#define EXIT_TLS_BIT (1 << 0)

loop:
  if(TLS connection enabled)
    prepare TLS structs includes certs (malloc)
    set socket options (timeouts)
    set bio
    handshake
    verify cert
    loop2:
      // 检查是否收到退出信号
      if(xEventGroupWaitBits(tls_exit_event, EXIT_TLS_BIT, pdFALSE, pdFALSE, 0) == EXIT_TLS_BIT) {
          break;
      }
      // 带1秒超时的读取
      ret = mbedtls_ssl_read_timeout(&ssl_ctx, buf, buf_len, 1000);
      if(ret == MBEDTLS_ERR_SSL_TIMEOUT) {
          continue;
      } else if(ret <= 0) {
          // 处理连接错误或服务器主动关闭
          break;
      }
      // 处理收到的数据,执行mbedtls_ssl_write等逻辑
    // 执行标准关闭流程
    mbedtls_ssl_close_notify(&ssl_ctx);
    free all allocated structs
    // 重置退出信号
    xEventGroupClearBits(tls_exit_event, EXIT_TLS_BIT)

其他任务触发关闭的代码:

// 设置退出信号
xEventGroupSetBits(tls_exit_event, EXIT_TLS_BIT);
// 关闭套接字
shutdown(sock, SHUT_RDWR);
mbedtls_net_close(net_context);

2. 利用LWIP的连接终止机制

如果上述方法无法唤醒阻塞任务,可直接操作LWIP的连接对象:

  • 若使用netconn接口,从其他任务调用netconn_abort(conn),会强制终止TCP连接并唤醒阻塞在netconn_recv的任务,使其返回错误码
  • 若使用socket接口,调用close(sock)替代单纯的shutdown,确保LWIP释放套接字资源,触发lwip_read返回错误

3. 确保完整的资源释放流程

无论用哪种方法触发退出,必须让TLS任务自身执行完整的清理:

  1. 调用mbedtls_ssl_close_notify()发送TLS关闭通知(即使套接字已关闭,也要执行以清理SSL上下文)
  2. 依次释放MbedTLS资源:mbedtls_ssl_free()、mbedtls_x509_crt_free()等
  3. 关闭套接字并释放相关动态分配的内存

关键注意事项

  • 所有涉及TLS上下文、套接字的操作需加互斥锁,避免并发访问导致崩溃
  • 禁止在其他任务中直接释放TLS任务分配的内存,必须由TLS任务自身执行释放逻辑,防止内存泄漏或野指针

内容的提问来源于stack exchange,提问作者Michelangelo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 23:10:10