基于TCP的网络通讯一段时间后出现断网问题

来源:互联网 发布:淘宝网页版电脑版卖家 编辑:程序博客网 时间:2024/05/21 07:09

问题现象:

基于TCP的网络通讯一段时间后出现断网。时间不定,有时四五个小时,有时二三天才出现。当问题出现时,和外界PING不通,PING本机能通,其他计算机也PING不通本机。有时还会造成和本机建立TCP连接的计算机也出现这一现象。

 

解决的过程:

在解决过程中,我们用PE(ProcessExplorer.exe)工具发现打开的句柄数不断飚升,检查代码后,看到在连接服务器不成功的情况下,新建的SOCKET未关闭。于是,修正这个BUG。再试,句柄数不再一路飚升了,可是,运行一段时间后断网问题还是没有解决。

这下子胸闷了,没方向了......

这时,距此问题被找出的时间点已经过去了一个多月,不时有人来问什么时候出正式版本。心中那个急啊,但急不是办法,只能暗自对自己说,这个问题搞不定就不用混下去了,以此来鼓励自己。

有人说,这个断网问题会不会和网卡有关?于是,我们换了张网卡,经三天的连续运行测试,还是断网了,问题依旧,证明了这和网卡无关。这时,我们又回到了原来的问题上来:是外部环境的问题还是新开发软件的问题?显然,这个问题还是出在了新开发的软件上!

结合测试工程师发现的一个现象——这个断网问题常出现在网络通讯的服务端,回头重新审查所有和网络通讯有关的代码,终于发现几处可疑的代码:

 

可疑代码之一:

BOOL CBlockingSocket::Accept(CBlockingSocket& sConnect, LPSOCKADDR psa)
{
 ASSERT(m_hSocket != INVALID_SOCKET);
 ASSERT(sConnect.m_hSocket == INVALID_SOCKET);
 int nLengthAddr = sizeof(SOCKADDR);
 sConnect.m_hSocket = accept(m_hSocket, psa, &nLengthAddr);
 if(sConnect == INVALID_SOCKET)
  return FALSE;
 m_RemoteSockAddr = *psa;  //保存远程信息(UDP时无法得到远程信息)

 return TRUE;
}

通读代码后,psa相关的代码本来的意思是想保存客户端的连接信息,方便以后使用。但事实上,这里的代码只在打开TCP侦听,并授受TCP连接时才会用到,和UDP无关。当在TCP建立连接后想得到客户端的连接信息时,也可以通过相关的函数来临时取得连接信息,用不着保存下来的m_RemoteSockAddr中的客户端连接信息。所以我们把代码改为:

BOOL CBlockingSocket::Accept(CBlockingSocket &sConnect)
{
 ASSERT(m_hSocket != INVALID_SOCKET);
 ASSERT(sConnect.m_hSocket == INVALID_SOCKET);
 sConnect.m_hSocket = accept(m_hSocket, NULL, NULL);    //这里有修改,去掉了第2、3个参数

 if(sConnect.m_hSocket == INVALID_SOCKET)
 {
  int iLastError = WSAGetLastError();
  ERRORLOG_FORMAT(("CBlockingSocket::Accept() accept() return INVALID_SOCKET, WSAGetLastError() return %d", iLastError));
  return FALSE;
 }

 return TRUE;
}


 

可疑代码之二:

同样还是授受TCP连接的代码。

  CSocketAgentPtr pNewSocket = pSocketServer->NewSocketAgent(pSocketServer);
  if (pSocketServer->Accept(pNewSocket))
  {
   pNewSocket->Init(pSocketServer->m_iSendBufLen, pSocketServer->m_iReceiveBufLen);
   //调用OnSocketConnect()之前,必须要将新建的SOCKET加进队列,否则会找不到
   SocketAgentQueue.UpdateSocket(pNewSocket);
   pSocketServer->OnSocketConnect(pNewSocket);
   if( pNewSocket.Get() && !pNewSocket.Get()->IsClosed() )
   {
    tagReadingSocket *pReadingSocket = new tagReadingSocket(pNewSocket);
    AfxBeginThread(fnReadingSocket, (LPVOID)pReadingSocket);
   }
  }
分析可能的原因是:当Accept函数调用出错时,新建的SOCKET没有关闭,也没有启动相应的线程为此SOCKET服务,于是这一新建的SOCKET一直僵死着,既不能传输数据,也无法关闭,最终导致资源耗尽而断网。根据这一思路,修改上述代码为

  CSocketAgentPtr pNewSocket = pSocketServer->NewSocketAgent(pSocketServer);
  if (!pSocketServer->Accept(pNewSocket))    //这里新增
   pNewSocket->Close(TRUE);    //这里新增
  else
  {
   pNewSocket->Init(pSocketServer->m_iSendBufLen, pSocketServer->m_iReceiveBufLen);
   //调用OnSocketConnect()之前,必须要将新建的SOCKET加进队列,否则会找不到
   if( pNewSocket.Get()==NULL || pNewSocket.Get()->IsClosed() )    //这里新增
    pNewSocket->Close(TRUE);    //这里新增
   else
   {
    SocketAgentQueue.UpdateSocket(pNewSocket);
    pSocketServer->OnSocketConnect(pNewSocket);
    tagReadingSocket *pReadingSocket = new tagReadingSocket(pNewSocket);
    AfxBeginThread(fnReadingSocket, (LPVOID)pReadingSocket);
   }
  }
编译新版本,经测试工程师连续一周的运行测试,已经突破了原来四天内必然断网的记录。这是个好消息!

尽管问题得到解决,但心中的郁闷没有解决。因为同一套代码用VC6+SP6编译后已在上千台计算机上运行了数年,没有发现这一问题。现在用VC2008编译后的软件有此问题,实在想不通。如哪位同行能解释一二,将感激不尽。

(成海2010-11-22撰于上海)

原创粉丝点击