CAtlHttpClient的一个严重bug

     我写的一个程序要从http服务器下载xml文件,就用了CAtlHttpClient这个http客户端类。在xml文件比较小的时候一切都顺利,
但当xml文件超过1M后,问题就时不时出现:不能下载xml文件了!
    什么原因呢?
    只能在本机调试了。为了尽快重现bug,我把xml文件增大到了3M多,下载的周期也由原来的2分钟缩短到30秒...经过20多个周期
问题又重现了:不能下载xml文件,陷在了下载中....
    在vc2003的工具栏中点击"全部中断",打开"线程"窗口就呈现出所有的线程来。我们只关心用户线程。
    是不是死锁了?
    我挨个挨个线程的打开,然后观查调用栈,没有发现死锁,所有打开的线程都可以按F10运行....但却发现下载xml文件的线程
一直在ZEvtSyncSocket::Read()函数中(CAtlHttpClient实际是typedef CAtlHttpClientT<ZEvtSyncSocket> CAtlHttpClient),跳
不出来。难道在Read里发生死锁了?试着按F10却是可以调试运行的,看来不是死锁。那是什么原因让它一直陷在Read中?
    先大概说一下调用栈:
    我的DownloadHttp("http地址")函数调用CAtlHttpClientT<TSocketClass>::Navigate()->......->CAtlHttpClientT<TSocketClass>::ReadBody...
    CAtlHttpClientT<TSocketClass>::ReadBody中有这样一个循环,写过tcp通信的朋友都很熟悉(//pgp是我加我注释):

   // pgp begin
    
//
循环的功能:不断的从网络底层缓存区中读数据,直到完成指定数量才跳出循环
    
//
nContentLen:xml文件的长度,从http头中获得
    
//
nCurrentBodyLen:已读完的xml文件长度,即当前读的进度.
    
//当nCurrentBodyLen >= nContentLen时,跳出循环,即读完了xml文件的内容

    while (nCurrentBodyLen <  nContentLen)
    
{
    dwRead 
= dwReadBuffSize;//
pgp:缓冲区大小
    
//pgp:从ZEvtSyncSocket::Read()中读数据

    if (!Read(readbuff, &dwRead))
        
return false
;
    
// notify user

    if (m_pNavData)
    
{
       
if (m_pNavData->
pfnReadStatusCallback)
        
if (!m_pNavData->pfnReadStatusCallback(dwRead, m_pNavData->
m_lParamRead))
            
return false
;
    }

    
//pgp:累加进度
    nCurrentBodyLen += dwRead;
    
//pgp:把一次读出的数据保存起来

    if (!m_current.Append((LPCSTR)(BYTE*)readbuff, dwRead))
    
{
        ATLASSERT(
0
);
        
return false// error!

    }

    m_pEnd 
= ((BYTE*)(LPCSTR)m_current) + m_current.GetLength();
    }

    线程就是陷在这个循环中,一直出不来。原因是,循环读了几次后,Read(readbuff, &dwRead)的输出参数dwRead每次都等于0,导致
nCurrentBodyLen += dwRead的当前进度值一直不变,始终不满足nCurrentBodyLen >= nContentLen这个结束循环的条件。
    那为什么Read返回0字节呢?返回0字节又表示什么意思呢?
    我们继续看ZEvtSyncSocket::Read()里的函数,我省略了一些与本问题无关的代码:

   // pgp
    
//这是重叠模型读数据

inline bool ZEvtSyncSocket::Read(const unsigned char *pBuff, DWORD * pdwSize) 
{
        ...........
        
    
bool bRet = true
;
    WSABUF buff;
    buff.buf 
= (char*
)pBuff;
    buff.len 
= *
pdwSize;
    
*pdwSize = 0
;
    DWORD dwFlags 
= 0
;
    WSAOVERLAPPED o;
    ZeroMemory(
&o, sizeof
(o));

    
// protect against re-entrency

    m_csRead.Lock();
    o.hEvent 
=
 m_hEventRead;
    WSAResetEvent(o.hEvent);
    
//pgp:先投递一个读请求

    if (WSARecv(m_socket, &buff, 1, pdwSize, &dwFlags, &o, 0))
    
{
        DWORD dwLastError 
=
 WSAGetLastError();
        
if (dwLastError !=
 WSA_IO_PENDING)
        
{
            m_dwLastError 
=
 dwLastError;
            bRet 
= false
;
        }

    }


    
// wait for the read to complete
    if (bRet)
    
{
        
//pgp:等待Read事件的发生,等待超时为m_dwSocketTimeout,默认是10秒

        if (WAIT_OBJECT_0 == WaitForSingleObject((HANDLE)o.hEvent, m_dwSocketTimeout))
        
{
            dwFlags 
= 0
;
            
//
pgp:Read事件发生,调用WSAGetOverlappedResult返回一次读的字节数
            
//
问题就在这里!!在这个函数被调用了N次后,pdwSize指向的整数值为0
            
//
当调用WSAGetOverlappedResult后,第三个参数,即pdwSize返回0意味着什么?
            
//
意味着远程服务器已断开连接!!可MS却当成正常读数据处理了,或说没处理第三个参数返回0的情况
            
//当远程服务器断开这个连接后,我们这边也应该关闭连接了(还有Event等)

            if (WSAGetOverlappedResult(m_socket, &o, pdwSize, FALSE, &dwFlags))
                bRet 
= true
;
            
else

            
{
                m_dwLastError 
=
 ::GetLastError();
                bRet 
= false
;
            }

        }

        
else
            bRet 
= false;
    }


    m_csRead.Unlock();
    
return bRet;
}
    

看代码里的解释,我们明白了出错的地方,那就很easy了。改下代码,处理下调用WSAGetOverlappedResult后,第三个参数返回0的情况:

    inline bool ZEvtSyncSocket::Read(const unsigned char *pBuff, DWORD * pdwSize)
    
{
        .............
        
if (WAIT_OBJECT_0 ==
 WaitForSingleObject((HANDLE)o.hEvent, m_dwSocketTimeout))
    
{
        dwFlags 
= 0
;
        
if (WSAGetOverlappedResult(m_socket, &o, pdwSize, FALSE, &
dwFlags))
        
{
            bRet 
= true
;
            
//
pgp:如果第三个参数返回0,则bRet=false表示,这次下载失败了
            
//函数栈中有函数自会清扫战场

            if(0 == *pdwSize)
                bRet 
= false
;
        }

        
else
        
{
            m_dwLastError 
=
 ::GetLastError();
            bRet 
= false
;
        }

    }

    
else
        bRet 
= false;
    ..............
    }

ok了,问题并不难。解决了,给老大有一个交代了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值