LINUX环境下websocket连接拔网线后socketfd未被释放的问题

websocket连接拔网线后socketfd未被释放

  • 问题说明
  • 问题解决
  • 补充问题
  • 参考

问题说明

嵌入式系统通过BOA建立了一个端口是9090的webserver,可以通过浏览器访问相关页面。又建立了websocket进行web页面和项目进程间的数据交互。
LINUX环境下websocket连接拔网线后socketfd未被释放的问题_第1张图片
websocket建立代码如下:

//客户端类型
enum em_ClientType{
    CLIENT_SENSOR_DATA = 0,
    CLIENT_CTRL_DEV,
    CLIENT_FAULT_DIAGNOSIS,
    CLIENT_OP_SENSOR,
    CLIENT_ROBOT_MAPPING,
    CLIENT_TYPE_SENSOR_SN,
    CLIENT_SN_MAPPING,
    CLIENT_ACPT_CHECK,
    CLIENT_WEB_MESSAGE,
    CLIENT_SINK_NODE,
    CLIENT_WHITE_LIST,
    CLIENT_BLACK_LIST,
    CLIENT_TOPO_LIST,
    CLIENT_SENSOR_LIST,
    CLIENT_YELLOW_LIST,
    CLIENT_CONFIG_DEVICE,
    CLIENT_SENSOR_CHECK,
    CLIENT_MAX
};

static pthread_t WebSocketThreadId;
int g_wsClientFds[CLIENT_MAX] = {-1};

/***************************************************************************
function: WSRS_Init
input:
output:
Description: 初始化websocket
****************************************************************************/
int WSRS_Init()
{
    int ret = -1;
    pthread_attr_t Thread_Attr;
    SSP_LOG(LOG_INFO, "Init ws start.\n");
    ESP_SetThreadAttr(&Thread_Attr, 20, SCHED_OTHER, 0x100000);
    ret = ESP_CreateThread(&WebSocketThreadId, &Thread_Attr, WSRS_MainThread, NULL);
    if(ret != 0)
    {
        SSP_LOG(LOG_ERROR, "Create network guard thread failed!\n");
    }
    SSP_LOG(LOG_INFO, "Init ws finished.\n");
    return ret;
}

/***************************************************************************
function: WSRS_MainThread
input:
output:
Description: websocket的主线程函数
****************************************************************************/
void *WSRS_MainThread(void *varg)
{
    struct ws_events evs;
    evs.onopen    = &WSRS_Onopen;
    evs.onclose   = &WSRS_Onclose;
    evs.onmessage = &WSRS_Onmessage;

    //ws_socket里面是一个while 1的循环监听网络连接
    ws_socket(&evs, 8001);
    return NULL;
}

/***************************************************************************
function: WSRS_Onopen
input:
output:
Description: 连接回调
****************************************************************************/
void WSRS_Onopen(int fd)
{
    char *cli;
    cli = ws_getaddress(fd);
    ESP_LOG(LOG_INFO, "Connection opened, client: %d | addr: %s\n", fd, cli);
    free(cli);
    return;
}

/***************************************************************************
function: WSRS_Onclose
input:
output:
Description: 客户端离开回调
****************************************************************************/
void WSRS_Onclose(int fd)
{
    char *cli;
    int  i = 0;
    
    cli = ws_getaddress(fd);
    ESP_LOG(LOG_INFO, "Connection closed, client: %d | addr: %s\n", fd, cli);
    free(cli);

    for(;i < CLIENT_MAX; i++)
    {
        if(g_wsClientFds[i] == fd)
        {
            g_wsClientFds[i] = -1;
            break;
        }
    }
    return;
}

/***************************************************************************
function: WSRS_Onmessage
input:
     fd-----File Descriptor belonging to the client
     msg----Received message can be a text or binary message
     size---Message size (in bytes)
     type---Message type
output:
Description: 收到消息的回调
****************************************************************************/
void WSRS_Onmessage(int fd, const unsigned char *msg, size_t size, int type)
{
    if(0 == memcmp(msg, "request_msg_data", strlen("request_msg_data")))
    {
        ESP_LOG(LOG_INFO, "rcv request to web msg:%s, size:%ld\n", msg, size);
        g_wsClientFds[CLIENT_WEB_MESSAGE] = fd;

        //数据处理

        return;
    }
}

代码运行,发现web页面打开过程中拔掉网线,该页面被分配的socketFd没有被释放。
因为websocket的端口是8001,使用命令netstat -anp | grep 8001查看socket连接情况。

页面连接前:
在这里插入图片描述
打开页面:
在这里插入图片描述
拔插网线,重新连接页面:
在这里插入图片描述
此时可以发现之前申请的socketfd端口63704并没有被释放。多次复现该问题,socketfd将耗尽,会导致web连接申请不到新的socketfd从而web页面登录失败。

问题解决

思考了下,决定在申请新的fd时判断之前的老fd是否存在,若是则先释放老的fd再申请新的fd。
修改接口WSRS_Onmessage,增加close老fd的代码。

if(0 == memcmp(msg, "request_msg_data", strlen("request_msg_data")))
{
	ESP_LOG(LOG_INFO, "rcv request to web msg:%s, size:%ld\n", msg, size);
	//释放老的FD
	if(0 != g_wsClientFds[CLIENT_WEB_MESSAGE] && -1 != g_wsClientFds[CLIENT_WEB_MESSAGE])
	{
		close(g_wsClientFds[CLIENT_WEB_MESSAGE]);
	}
	g_wsClientFds[CLIENT_WEB_MESSAGE] = fd;
    //数据处理

	return;
}

更新代码再次测试
连接页面:
在这里插入图片描述
拔插网线,重新连接页面:
LINUX环境下websocket连接拔网线后socketfd未被释放的问题_第2张图片
发现之前的61885仍然是ESTABLISHED状态,只是最后的31382/ody2000e没了。
继续查阅相关资料,发现int shutdown(int sockfd,int howto); 这个接口貌似可用,重新修改代码。

void WSRS_CloseOldFd(int fd)
{
    if(0 != fd && -1 != fd)
    {
        ESP_LOG(LOG_INFO, "close fd:%d\n", fd);
        shutdown(fd, SHUT_RDWR);
        close(fd);
    }
}

if(0 == memcmp(msg, "request_msg_data", strlen("request_msg_data")))
{
    ESP_LOG(LOG_INFO, "rcv request to web msg:%s, size:%ld\n", msg, size);
    WSRS_CloseOldFd(g_wsClientFds[CLIENT_WEB_MESSAGE]);

    g_wsClientFds[CLIENT_WEB_MESSAGE] = fd;
    //数据处理
    
    return;
}

再次更新程序进行测试
连接页面:
在这里插入图片描述
拔插网线,重新连接页面:
在这里插入图片描述
此时发现之前的连接59470被完美释放,问题解决。

补充问题

此时产生疑问,若void WSRS_CloseOldFd(int fd)接口中只对fd进行shutdown操作而不进行close,会有什么效果,试验下。

void WSRS_CloseOldFd(int fd)
{
    if(0 != fd && -1 != fd)
    {
        ESP_LOG(LOG_INFO, "close fd:%d\n", fd);
        shutdown(fd, SHUT_RDWR);
        //close(fd);
    }
}

if(0 == memcmp(msg, "request_msg_data", strlen("request_msg_data")))
{
    ESP_LOG(LOG_INFO, "rcv request to web msg:%s, size:%ld\n", msg, size);
    WSRS_CloseOldFd(g_wsClientFds[CLIENT_WEB_MESSAGE]);

    g_wsClientFds[CLIENT_WEB_MESSAGE] = fd;
    //数据处理
    
    return;
}

再再次更新程序进行测试
连接页面:
在这里插入图片描述
拔插网线,重新连接页面:
在这里插入图片描述
进入到进程的fd目录下,多次刷新页面拔插网线,并没有发现句柄有明显增加。
LINUX环境下websocket连接拔网线后socketfd未被释放的问题_第3张图片

根据测试情况可以发现close接口可以不再调用了。

close与shutdown的区别主要表现在:
close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的,特别是对于多进程并发服务器来说。
而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。

close()/closesocket()和shutdown()的区别
确切地说,close() / closesocket() 用来关闭套接字,将套接字描述符(或句柄)从内存清除,之后再也不能使用该套接字,与C语言中的 fclose() 类似。应用程序关闭套接字后,与该套接字相关的连接和缓存也失去了意义,TCP协议会自动触发关闭连接的操作。
shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。
调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了。
默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。

保险起见,还是再次调用close(fd);防止产生句柄耗尽的问题。

参考

https://blog.csdn.net/lgp88/article/details/7176509
https://blog.csdn.net/cs1462155255/article/details/77991006

你可能感兴趣的:(C语言,c语言,linux)