我写的一个程序要从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了,问题并不难。解决了,给老大有一个交代了