目录
一、问题描述
最近遇到一个问题,就是我的stm32设备作为tcp客户端和上位机交互,如果在连接过程中网线被拔断,等待时间稍微长一点再插上的话,tcp将不能再连接到服务器端,除非重启设备,所以我开始研究怎么解决这个lwip的小问题。
二、项目构成
**MCU :**STM32F429IGTx
**网口芯片 :**LAN8720
操作系统 :UCOSIII
**协议栈:**LWIP
**调试工具:**sscom5.13.1(可开启TCP服务端)
三、问题解决
1.问题代码
我们写了一个socket的tcp客户端作为一个单独的任务执行,recv这个函数阻塞,没数据的时候一直被阻塞,但是不影响其它任务,有了数据发过来,或者正常的tcp断开,recv函数就会收到数据往下执行,但是这时候我们遇到了一个问题,那就是TCP连接状态下,网线被拔出,recv这个函数没有做任何的反应,所以这便导致了recv这个函数一直被阻塞,插上网线以后不能重新像服务器进行tcp连接,理想状态下是recv函数应该也像正常tcp断开那样给我返回一个信号,那样我就知道tcp中断了,就去循环重新获取TCP连接,可是并没有,我们设备安装在现场难免会有网线被拔出的情况,一拔出再插入tcp就连接不上了说不过去,所以只能自己想办法解决这个问题。
2.解决思路
一开始的解决思路就是在tcp的recv下面加一个检测网线是否被插入的判断,如果网线被拔出的话,也break,跳出当前while去上一级while里面进行tcp连接,可是忽略了recv函数阻塞的问题,网线被拔出recv没有数据根本不往下执行,如果是netconn不阻塞的那种倒是可以,所以这个方案否了。
后来琢磨recv不是阻塞么,不如重新创建一个任务检测网口的网线插入状态,把这个tcp任务重新启动呢,最开始想到了挂起再恢复,后来发现恢复以后任务还会继续在阻塞里面,解决不了问题。想了想只能是拔出网线后删除tcp任务再重新创建了,为避免资源浪费,检测到网线拔出就删除tcp任务,检测到网线插入就创建tcp任务。
3.核心代码:
HAL库 LAN8720_ReadPHY(PHY_BSR) & PHY_LINKED_STATUS
标准库 ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)
如果网线是插入状态 代码的结果就是1
如果网线是拔出状态 代码的结果就是0
当tcp建立连接以后,就一直去判断网线有没有被拔出,如果被拔出了,就删除tcp任务。当tcp没有建立连接的时候,就一直去判断网线有没有被插入,插入的话就创建tcp任务,注意代码逻辑不要多次删除或者创建同一任务导致系统崩溃。
四、完整代码
1.监测网口插入拔出任务
cpp
u8 TCP_CONNECT_FLAG=0;//TCP连接状态 0是未连接 1是已连接 2是重新创建了任务待连接
//1.监测网口插入拔出任务
void key_task(void *pdata)
{
u8 res;
OS_ERR err;
while(1)
{
/**key = KEY_Scan(0);
if(key==KEY0_PRES) //发送数据
{
LED0 = !LED0;
}
**/
if(TCP_CONNECT_FLAG==1){
if(!(LAN8720_ReadPHY(PHY_BSR) & PHY_LINKED_STATUS)){
//删除tcp任务
TCP_CONNECT_FLAG=0;
tcp_deletetask();
}
}else if(TCP_CONNECT_FLAG==0){
if((LAN8720_ReadPHY(PHY_BSR) & PHY_LINKED_STATUS)){
//打开tcp任务
TCP_CONNECT_FLAG=2;
tcp_starttask();
}
}
OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&err); //延时2s
}
}
2.TCP任务
cpp
#define PORT 5001
#define RECV_DATA (1024)
#define SERV_IP_ADDR "192.168.0.222"
#define SERV_PORT 8088
unsigned char rec_buffers[1024]={"0X66,0x14,0x97,0x0F,0x1D,0xEA\n"};
unsigned char rec_buffers2[1024]={"\n"};
extern u8 TCP_CONNECT_FLAG;
int sock=-1;
void tcp_thread(void *arg)
{
OS_ERR err;
int block = 1;
struct sockaddr_in Serv_addr;
//char*recv_data;
int recv_data_len;
/*为recv_data申请内存空间 申请成功返回内存空间首地址 失败返回NULL*/
//recv_data=(char*)malloc(RECV_DATA);
//if(recv_data==NULL){
//printf("Mallo memory failed\r\n");
// }
while(1){
if(sock!=-1){
closesocket(sock);
sock=-1;
}
/* 为sockaddr_in结构体成员赋值,用于以下的connect绑定 参数protocol在TCP/TCP两种协议下均为0 */
/*套接字申请成功返回Socket描述符(int类型) 失败返回-1*/
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
// printf("Socket error\n");
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_HMSM_STRICT,&err);
continue;
}
//ioctlsocket(sock,FIONBIO,&block);
/*TCP/IP -- IPv4*/
Serv_addr.sin_family=AF_INET;
/*绑定远端服务器的端口*/
Serv_addr.sin_port=htons(SERV_PORT);
/*绑定远端服务器的ip*/
Serv_addr.sin_addr.s_addr=inet_addr(SERV_IP_ADDR);
/* 清空sockaddr_in结构体内存空间 sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
memset(&(Serv_addr.sin_zero), 0, sizeof(Serv_addr.sin_zero));
/* 连接远端服务器 */
if (connect(sock, (struct sockaddr *)&Serv_addr, sizeof(struct sockaddr)) == -1)
{
//printf("Connect failed!\n");
closesocket(sock);
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_HMSM_STRICT,&err);
continue;
}
TCP_CONNECT_FLAG=1;
//printf("Connect to tcp server successful!\n");
while(1)
{
/* 成功接收到数据,返回接收的数据长度 */
recv_data_len = recv(sock, rec_buffers2, RECV_DATA, 0);
if (recv_data_len <= 0){
break;
}else{
write(sock,rec_buffers,1024);
}
/* 串口打印接收的数据内容 */
//printf("recv:%s\n",recv_data);
/* 发送数据内容 */
OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_HMSM_STRICT,&err);
}
OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_HMSM_STRICT,&err);
}
}
3.创建tcp任务
cpp
void tcp_starttask(){
OS_ERR err;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();//进入临界区
OSTaskCreate((OS_TCB * )&TcpTaskTCB,
(CPU_CHAR * )"tcp task",
(OS_TASK_PTR )tcp_thread,
(void * )0,
(OS_PRIO )TCP_PRIO,
(CPU_STK * )&TCP_TASK_STK[0],
(CPU_STK_SIZE)TCP_STK_SIZE/10,
(CPU_STK_SIZE)TCP_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT(); //退出临界区
}
4.删除tcp任务
cpp
void tcp_deletetask(){
OS_ERR err;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();//进入临界区
OSTaskDel((OS_TCB * )&TcpTaskTCB,&err);
OS_CRITICAL_EXIT(); //退出临界区
}
五、总结
算是解决了网线拔出再插入以后tcp不能重新建立连接的问题,可能方法过于简单粗暴,如果大佬有更好的方法解决这个问题欢迎交流指导。