基于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任务自身执行完整的清理:
- 调用
mbedtls_ssl_close_notify()发送TLS关闭通知(即使套接字已关闭,也要执行以清理SSL上下文) - 依次释放MbedTLS资源:
mbedtls_ssl_free()、mbedtls_x509_crt_free()等 - 关闭套接字并释放相关动态分配的内存
关键注意事项
- 所有涉及TLS上下文、套接字的操作需加互斥锁,避免并发访问导致崩溃
- 禁止在其他任务中直接释放TLS任务分配的内存,必须由TLS任务自身执行释放逻辑,防止内存泄漏或野指针
内容的提问来源于stack exchange,提问作者Michelangelo
相关产品推荐
相关产品推荐

