C++服务端CLOSE

tech2024-11-24  14

C++服务端CLOSE_WAIT问题排查

问题描述问题排查总结

问题描述

     服务端出现大量的处于CLOSE_WAIT状态的fd:

问题排查

     一个socket的关闭,是需要四次握手来完成的: (1)主动关闭连接的一方,调用close(),发送FIN包 ; (2)被动关闭的一方收到FIN包后,回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方等待被动关闭一方的应用程序,调用close操作 ; (3)被动关闭的一方在完成所有数据发送后,调用close()操作,被关闭的一方发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态; (4)主动关闭的一方收到FIN包,回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态 ; (5)等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态 ;

     出现CLOSE_WATI的原因主要就是:客户端主动发起连接,服务端收到客户端的FIN时,但是服务端没有接着发送自己的FIN,此时,该连接对应的fd处于CLOSE_WAIT状态。也就是说当服务端调用read读取fd上的数据,返回为0时,没有主动关闭该fd。我们程序使用的muduo库,看一下read处的代码:      当read返回0时,调用了handClose函数,我们继续看:      这里首先禁用channel,不再关注该fd上的可读可写事件,然后再进行回调通知,并没有调用close(fd),看到这里本想怀疑是不是muduo库忘记了处理,突然看到上面的一句提示:      这句话说明fd关闭时放在当前连接类TcpConnection的析构中进行的,我们看一下它的析构:      TcpConnection的析构中也并没有进行close(fd)操作,我们再看看TcpConnection的成员变量,其中有一个智能指针:

std::unique_ptr<Socket> socket_;

     而Socket对象中就存放了文件描述符fd,在TcpConnection析构的同时,该智能指针也会进行析构(它是个unique_ptr,独占的):      到这里就明白了,当服务端read数据返回0时,是有主动调用close函数来关闭该fd的,前提是:保证TcpConnection能够正常析构,这也正印证了作者的提示:将fd的关闭放在析构函数中,同时可以检测我们的程序是否存在内存泄漏问题。很不幸,定位到这里,基本确定我们的服务端程序对TcpConnection指针的管理不当,导致了内存泄漏。

     通过检查我们的代码,发现在登录模块LoginConn中保存了TcpConnection,用于主动发送账号互踢消息:      而LoginConn又保存在了静态变量s_ClientMap中,当客户端主动关闭时,我们并没有删除s_ClientMap中对应用户的LoginConn*,导致LoginConn不能正常析构,TcpConnection也不能正常析构,所以才会出现服务端没有主动调用close(fd),最终出现CLOSE_WAIT的现象。

总结

1,muduo库中的智能指针用的很多,甚至在时间轮中也时通过指针指针来管理生命周期的,看起来使用很简单,但是当智能指针的生命周期和代码逻辑绑定在一起时,很有可能因为误判了指针的生命周期,而导致代码逻辑执行异常。 2,智能指针是一把双刃剑,须小心使用。 3,CLOSE_WAIT状态如果长期存在,说明我们的被动关闭的一方程序有问题,没有及时发送FIN包给主动关闭方,此时只需要检查被动关闭方的程序即可。 4,TIME_WAIT出现在主动关闭方,如果我们的服务器是主动关闭方,如果TIME_WAIT只是短暂存在,该现象是正常的,如果长期存在,则是被动关闭方的问题,没有主动发送FIN包,需要检查被关闭方(一般是客户端程序),确保被关闭方收到关闭通知时(read==0)能正常调用close(fd)

最新回复(0)