嵌入式系统通过BOA建立了一个端口是9090的webserver,可以通过浏览器访问相关页面。又建立了websocket进行web页面和项目进程间的数据交互。
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;
}
更新代码再次测试
连接页面:
拔插网线,重新连接页面:
发现之前的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目录下,多次刷新页面拔插网线,并没有发现句柄有明显增加。
根据测试情况可以发现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