lwip协议栈TCP/IP异常断开调试笔记
创作时间:
作者:
@小白创作中心
lwip协议栈TCP/IP异常断开调试笔记
引用
CSDN
1.
https://blog.csdn.net/weixin_43777852/article/details/143934050
本文记录了在使用lwip协议栈实现TCP/IP通信时遇到的两个典型问题及其解决方案,包括服务端异常断开后的处理和FreeRTOS断言错误的调试方法。
问题一:异常掉线
异常断开模拟
- 单片机端做服务端(只监听一个客户端),电脑做客户端连接
- 尝试连接确定通信正常,断开网线。电脑客户端点击断开
- 经过一段时间(超过TCP/IP 3次握手时间)
- 接回网线后发现可以连接上但通信异常
原因分析
在服务端代码中,当客户端异常断开时,服务端会一直阻塞在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)解决了该问题。
热门推荐
修仙小说语言提升秘籍:你get了吗?
量子力学入门:从基础概念到核心原理
睡眠师推荐:12种高效助眠技巧大揭秘!
央行最新数据:50万存款家庭占比仅0.37%
央行购债政策对国债市场流动性的影响
古德寺:武汉最神秘的寺庙,中西合璧的建筑奇观
工业自动化涉及哪些技术?
输出型爱好:让生活更有趣,让自我更丰盈
中医气功:凝神聚气,静心察气,通调身、心、息
中文可以转换成二进制吗?该怎么做?
如何设置SMTP邮件服务器?步骤详解与常见问题解答
百汇骨科专家推荐:7个有效缓解坐骨神经痛的康复训练
游戏玩家如何选择外设:鼠标、键盘、耳机
跑车如何月入过万,满帮卡车司机张玉文的9大心得
低学历成功逆袭概率多少?刘强东李彦宏都是学霸
emoji遇上音乐:当表情符号奏响旋律
别人夸你“漂亮优秀”,千万别只回“没有没有”,高情商回应来了
键盘选购指南:如何选择最佳机械键盘轴体提升游戏体验
Simulink Logical Operator模块使用指南
御膳房里的欢乐时光:一幅清代宫廷厨房的趣味漫画
中年危机中的优雅转身:一个夫妻共同应对的真实故事
《Fate/stay night》中的吉尔伽美什:最强王者的人设解析
张筱玺:用《日光之城》照亮西藏故事
如何正确地接纳自己缺点和自己和解,才能遇到更好的自己
《周易》的前世今生:从占卜到哲学
让爱情更甜蜜:情感沟通的艺术
深度解析:投资理财的正确方法与技巧
新高考改革下,如何用中华优秀传统文化提升历史教学?
陆林院士教你恢复常规应答模式维护心理健康
揭秘库里神准投篮:从训练到实战,普通人也能学