IOCP分段发送的数据中间夹杂错误数据的问题

Gary@Tokyo 2010-11-04 05:20:14
服务器端是完成端口+多线程(4个线程)处理客户端:

客户端请求某个文件夹的目录结构
然后发送
数据包格式:1字节长度表示序号+1字节表示包长度+1字节指令+具体消息内容(文件夹信息)。
由于文件夹的信息大于我设定64K,所以分三段发,结果发现客户端收到的信息,中间有一段数据读出来是错误的,位置比较固定。




typedef struct _FileInfo
{
TCHAR szFileName[MAX_PATH];
DWORD dwFileID;
TCHAR cFileFlag;
TCHAR cFileType;
FILETIME ftLastWriteTime;
DWORD dwFileSizeHigh;
DWORD dwFileSizeLow;
DWORD dwFileCrc;
}FileInfo,*PFileInfo;



DWORD WINAPI CIOCPServer::_WorkerThreadProc(LPVOID lpParam)
{
CIOCPServer *pThis = (CIOCPServer*)lpParam;
PIOCP_BUF pBuffer;
PIOCP_CONTEXT pContext;
DWORD dwTrans = 0;
LPOVERLAPPED lpol;
BOOL bOK;

while (TRUE)
{
// ……………………

bOK = GetQueuedCompletionStatus(pThis->m_hCompletion,&dwTrans,(LPDWORD)&pContext,&lpol,WSA_INFINITE);
// 错误处理

pThis->HandleIO( pContext, pBuffer, dwTrans, nError );
}
return 0;
}

void CIOCPServer::HandleIO(PIOCP_CONTEXT pConTextKey, PIOCP_BUF pBuffer, DWORD dwTrans, int nError)
{
PIOCP_CONTEXT pContext;
pContext=pConTextKey;

if(pContext != NULL && (unsigned int )pContext != 0xfeeefeee )
{
if(pBuffer->IoOper == IoRecv)
::InterlockedDecrement(&pContext->nOutstandingRecv);
else if(pBuffer->IoOper == IoWrite)
::InterlockedDecrement(&pContext->nOutstandingSend);
}

if ( pBuffer != NULL && (unsigned int)pBuffer != 0xfeeefeee )
{
switch(pBuffer->IoOper)
{
// 其他无关代码省略…………
case IoRecv:
pContext=pConTextKey;
if (pContext == NULL || pBuffer == NULL)
break;
EnterCriticalSection(&pContext->Lock);
SendFileStructEx(pContext,pBuffer,pContext->sCurOption, _T("0102"));
LeaveCriticalSection(&pContext->Lock);
break;

case IoWrite:
pContext=pConTextKey;
EnterCriticalSection(&pContext->Lock);
pContext->sIsPostSend=0;//发送包完成
pContext->nSendCount+=dwTrans;

if (pContext->nSendCount = pBuffer->nBufLen)
{
pContext->nSendCount=0;
}

if( _tcsnccmp(pBuffer->pBuf+4, _T("0102"),4)==0 )
{
INT64 i64t=pContext->i64TotalByte - pContext->i64HavedByte;
if (i64t != 0 )
{
if( i64t < pBuffer->nBufLen - 12)
{
pBuffer->nBufLen = (int)(i64t + 12);
}

memcpy(pBuffer->pBuf+12,pContext->pPoint+pContext->i64HavedByte,pBuffer->nBufLen-12);
pContext->i64HavedByte+=pBuffer->nBufLen-12;
*(int*)pBuffer->pBuf=pBuffer->nBufLen-4;

if (!PostSend(pContext,pBuffer))
{
LeaveCriticalSection(&pContext->Lock);
return;
}
}
else
{
if (pContext->pPoint != NULL && pContext->bFree == FALSE)
{
free(pContext->pPoint);
pContext->pPoint=NULL;
}
}
}

LeaveCriticalSection(&pContext->Lock);
break;
default :
;
}
return ;
}
}

void CIOCPServer::SendFileStructEx(PIOCP_CONTEXT pContext,PIOCP_BUF pBuffer,TCHAR *sBrowserDir,TCHAR *sCmd)
{
u_long nTotalSize = 0;
int nFileCount = 0;
int nFileInfoSize = 0;
if (m_pFile == NULL)
{
return;
}

nFileInfoSize=sizeof(FileInfo);
int nFileIndex = 1;
BrowserFolderEx(sBrowserDir,&nFileCount,m_pFile,&m_pFile, nFileIndex ); //遍历目录
nFileIndex = 0;
nTotalSize=nFileCount*nFileInfoSize;
pContext->pPoint=(TCHAR*)m_pFile; // FileInfo 之前申请了一块内存
pContext->i64TotalByte=nTotalSize;

if(nTotalSize > BUFFER_SIZE -12)
nTotalSize=(BUFFER_SIZE -12)/nFileInfoSize*nFileInfoSize;
pContext->i64HavedByte=nTotalSize;

memset(&pBuffer->ol,0,sizeof(WSAOVERLAPPED));
pBuffer->ol.OffsetHigh = 0;
*(int*)(pBuffer->pBuf)=8+nTotalSize;
memcpy(pBuffer->pBuf+4,sCmd,4);
memcpy(pBuffer->pBuf+8,&nFileCount,4);
memcpy(pBuffer->pBuf+12,m_pFile,nTotalSize);
pBuffer->nBufLen=nTotalSize+12;

if (!PostSend(pContext,pBuffer))
{
LOG.LogF( _T("发送文件结构体信息失败 行号: %d 错误码: %d"), __LINE__,WSAGetLastError());
return;
}

PostRecv(pContext,pBuffer);
}


我测试时候,一个目录有485个文件,客户端接收的信息显示第370-396个文件信息是错误的,这段之前以及之后的都正确。

现在我判断还是发送错误,客户端读取是按格式读的,单线程,应当不会有错误。
...全文
144 16 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
visualwind 2010-11-07
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 itsgoodtobebad 的回复:]
飞狐你说的这种方法是否有实际使用过的?这个对我是一个新的思路啊,我一直在这个地方没想到好的办法。
如果是这样的话,设置一个发送队列,在主程序里只发送队列头的包。

然后在work线程里来继续发送?就是在GetQueuedCompletionStatus后来继续投递WSASend吗?
你看看是不是这样实现的:
if(PerIOData->OperationType==WSA_SEND)
{
if(BytesTransferred<PerIOData->DataBuf.len)
{
// 继续发送
}
else
{
// 发送下一个包
}
[/Quote]

对的,就是这个意思
白虹李李 2010-11-07
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 visualwind 的回复:]
引用 12 楼 sky04 的回复:
我将缓冲区从64K改成32K后好像没发生混乱

请问这种大的数据包,一次send发送不了,分多次发送,该怎么发送好点?
您所说的继续发送上次剩余的,不大明白怎么实现,麻烦明示一下,最好能有些代码,谢谢!


不一定每次都混乱,比如网络环境良好的情况下概率就很小。但是作为良好的程序来说应该去处理各种可能的情况。你可以先把要发送的数据放入一个队列,然……
[/Quote]

飞狐你说的这种方法是否有实际使用过的?这个对我是一个新的思路啊,我一直在这个地方没想到好的办法。
如果是这样的话,设置一个发送队列,在主程序里只发送队列头的包。

然后在work线程里来继续发送?就是在GetQueuedCompletionStatus后来继续投递WSASend吗?
你看看是不是这样实现的:
if(PerIOData->OperationType==WSA_SEND)
{
if(BytesTransferred<PerIOData->DataBuf.len)
{
// 继续发送
}
else
{
// 发送下一个包
}
}
白虹李李 2010-11-05
  • 打赏
  • 举报
回复
你的程序处理沾包不?
就是说你第一次收到了1.5个包,然后你按照你的协议处理了前面部分。
下一次你收到半个包,你按照协议去处理,结果发现长度很大(因为你读的位置根本不是长度)。就出问题了。
你说的这个应该是个抓包工具吧,我也没用过。
Gary@Tokyo 2010-11-05
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 visualwind 的回复:]

引用 12 楼 sky04 的回复:
我将缓冲区从64K改成32K后好像没发生混乱

请问这种大的数据包,一次send发送不了,分多次发送,该怎么发送好点?
您所说的继续发送上次剩余的,不大明白怎么实现,麻烦明示一下,最好能有些代码,谢谢!


不一定每次都混乱,比如网络环境良好的情况下概率就很小。但是作为良好的程序来说应该去处理各种可能的情况。你可以先把要发送的数据放入一个队列,……
[/Quote]
谢谢,我看看怎么把代码改改,现在这代码好像不好改
visualwind 2010-11-05
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 sky04 的回复:]
我将缓冲区从64K改成32K后好像没发生混乱

请问这种大的数据包,一次send发送不了,分多次发送,该怎么发送好点?
您所说的继续发送上次剩余的,不大明白怎么实现,麻烦明示一下,最好能有些代码,谢谢!
[/Quote]

不一定每次都混乱,比如网络环境良好的情况下概率就很小。但是作为良好的程序来说应该去处理各种可能的情况。你可以先把要发送的数据放入一个队列,然后每次WSASend这个队列的第一个包,发送完成回调后判断是否发完了,如果发完了就把队列第一个包删掉,继续发第二个包。如果只发了一部分,就继续发第一个包剩余的部分。
Gary@Tokyo 2010-11-05
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 visualwind 的回复:]

引用 5 楼 sky04 的回复:

引用 2 楼 visualwind 的回复:

两次send操作,一前一后也会造成数据混乱吗?


会混乱。因为send只是把数据放入发送队列,并不是立刻发送。如果前一个send包未发完数量就回调,系统会跟着发第二个send包,这时第一个包的一部分数据就在第二个数据包后面了
[/Quote]
我将缓冲区从64K改成32K后好像没发生混乱

请问这种大的数据包,一次send发送不了,分多次发送,该怎么发送好点?
您所说的继续发送上次剩余的,不大明白怎么实现,麻烦明示一下,最好能有些代码,谢谢!
Gary@Tokyo 2010-11-05
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 hmm7e 的回复:]

代码没细看,应当没什么大问题.

1:把缓冲区改成4K,弄一个文件较少的(或者正好到临界点上).
2:把四个线程,改成一个线程,输出一下调试信息.
[/Quote]
分别改了试了试
将缓冲区改成32K就没出错,线程是4还是1没有影响
visualwind 2010-11-05
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 sky04 的回复:]

引用 2 楼 visualwind 的回复:

两次send操作,一前一后也会造成数据混乱吗?
[/Quote]

会混乱。因为send只是把数据放入发送队列,并不是立刻发送。如果前一个send包未发完数量就回调,系统会跟着发第二个send包,这时第一个包的一部分数据就在第二个数据包后面了
野男孩 2010-11-05
  • 打赏
  • 举报
回复
太长了,只能帮顶了。

网络的问题,用抓包工具帮着定位吧。WireShark或者Ethereal啥的,先定位看是客户端发送错了,还是服务端接收有问题。
xili 2010-11-04
  • 打赏
  • 举报
回复
奇怪的数据包格式,
数据包格式:1字节长度表示序号+1字节表示包长度+1字节指令+具体消息内容(文件夹信息)。

从代码中没有看到这个所谓的数据包格式.
如果一个数据包表示一个FileInfo,那程序就问题. 你的FileInfo大小超出了 "1字节表示包长度"的 表达能力.
「已注销」 2010-11-04
  • 打赏
  • 举报
回复
代码没细看,应当没什么大问题.

1:把缓冲区改成4K,弄一个文件较少的(或者正好到临界点上).
2:把四个线程,改成一个线程,输出一下调试信息.

Gary@Tokyo 2010-11-04
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 visualwind 的回复:]

用完成端口发送的时候,应该等一次发送完成后才能发下一包,在case IoWrite的时候要判断transferbytes是否等于请求发送的长度,如果小于的话还得继续发上次一次剩下的。
你这里case IoRecv的时候在SendFileStructEx,case IoWrite的时候也在PostSend,虽然加了临界区但是也还会造成投递了两个send操作,有可能导致发送出去的数据混乱。
[/Quote]
两次send操作,一前一后也会造成数据混乱吗?
Gary@Tokyo 2010-11-04
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 itsgoodtobebad 的回复:]

重叠模型啊
如果只能发完一次再发下一次,就不叫重叠模型了,何必呢

其实我最开始也在这里疑惑,如果有没发完的部分怎么办。但如果发完一个再发下一个,那么重叠模型就没任何意义了啊。
我想如果出现了没发完的,估计要上层处理了。

我觉得问题在于没有处理沾包,造成你读到的长度不对。就是说你在错误的位置去读长度了。

不过你说是一个字节的长度,那么怎么会有4038啊,不是最大255吗?
[/Quote]
不知道,我不知道wireshark那个是啥意思,还不会用
白虹李李 2010-11-04
  • 打赏
  • 举报
回复
重叠模型啊
如果只能发完一次再发下一次,就不叫重叠模型了,何必呢

其实我最开始也在这里疑惑,如果有没发完的部分怎么办。但如果发完一个再发下一个,那么重叠模型就没任何意义了啊。
我想如果出现了没发完的,估计要上层处理了。

我觉得问题在于没有处理沾包,造成你读到的长度不对。就是说你在错误的位置去读长度了。

不过你说是一个字节的长度,那么怎么会有4038啊,不是最大255吗?
visualwind 2010-11-04
  • 打赏
  • 举报
回复
用完成端口发送的时候,应该等一次发送完成后才能发下一包,在case IoWrite的时候要判断transferbytes是否等于请求发送的长度,如果小于的话还得继续发上次一次剩下的。
你这里case IoRecv的时候在SendFileStructEx,case IoWrite的时候也在PostSend,虽然加了临界区但是也还会造成投递了两个send操作,有可能导致发送出去的数据混乱。
Gary@Tokyo 2010-11-04
  • 打赏
  • 举报
回复
调试了好多遍,没有发现哪里有问题,用wireshark抓了一下包,发现第二段数据有个突然变大的迹象

之前都是4038一段一段的,发了64K的样子,然后突然有段3万多长度的数据,然后又是4038

18,363

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧