您的位置:首页 > 理论基础 > 计算机网络

CAtlHttpClient的一个严重bug

2007-09-01 12:39 465 查看
我写的一个程序要从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了,问题并不难。解决了,给老大有一个交代了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: