由最开始进行Socket创建开始:
后面按照这个接口中的调用情况,查看到了接口函数alloc_socket,该函数的主要功能为为给定的Socket连接分配一个新的套接字。
在该函数里面有如下的一个逻辑:
可以看到,判断socket资源是否有人在使用时,除了判断socket->conn是否为空,还会判断select_waiting是否等于0。其中select_waiting标识该socket正在被多少个线程在使用。即要释放socket资源两个重要条件是,socket->conn必须为空,并且select_waiting要为0。
接下来看看lwip_close函数的实现,看为啥会导致资源释放不完全。
在lwip_close里面还未调用free_socket(sock, is_tcp);free_socket(sock, is_tcp);该函数功能为对数据空间进行释放和对socket->conn置空,该函数的调用在这个函数的最后一步:
但值接调用也会出现问题:
上述函数分析可知,lwip_close函数只能使socket->conn为空,并不能使select_waiting为0,所以其实只有lwip_close函数是不能使socket资源完全释放的。
从select_waiting名字中能比较容易的猜到,该变量跟select函数肯定是强相关的。全局搜索select_waiting,果然只有select函数有进行写操作。下面分析select函数:
参考上述代码分析,特别注意socket->select_waiting加1和减1的地方,可以看到,如果socket存在且的确需要监听事件,且并不是进来事件就已经产生或者已经超时,一定会加1;然后线程会有可能会进行休眠;正常情况下,休眠结束后,socket->select_waiting减1,离开该函数,socket->select_waiting恢复原值。但是,如果在线程休眠期间,恰巧在另外一个线程进行了lwip_close操作,事件就会有问题。
如果在休眠期间进行了lwip_close(socket),则通过tyr_socket(socket)获取不到socket结构体,则socket->select_waiting不会进行减1,后面执行一系列语句后,退出该函数,socket->select_waiting没有恢复原值,且比进来时大1。针对该函数,socket->select_waiting加1的次数是>=减1的次数,所以如果只要在函数退出时没有恢复原值,则socket->select_waiting永远不可能再减为0了,此时socket资源就出现了假占用,该socket再也不能被其他人使用了。
由此多了如下操作:接口LWIP_API_LOCK();的功能为在等待信号量时阻塞线程发出信号。如果“超时”参数非零,线程应该仅在指定时间内被阻止。主要达到让socket->select_waiting清零的效果,而接口LWIP_SET_CLOSE_FLAG();主要是设置标志位,让接口LWIP_API_UNLOCK();调用free_socket进行空间释放和socket->conn清零的效果。
最后的解决方案就是:将lwip_close和lwip_shutdown接口调换为close和shutdown。
扩展:1. close 把描述符的引用计数减一,仅在该计数变为0时才关闭套接字。而shutdown可以并以不管引用计数就激发TCP的正常连接终止序列
2. close终止读和写两个方向的数据传送,而shutdown可以指定哪个方向被关闭,读端还是写端还是两个都关闭