解决方案 »
- 关于截包内容的显示
- 难题!socket使用tcp连接超过1000个以后整个系统有问题了
- IID_NULL到底是个类GUID还是接口GUID,什么时候用得到它 ?
- 如何用VC做mp3播放器?
- 在CSlider控件拖动的时候动态显示其ToolTip内容
- 急!如何把.cpp文件转成.dll文件,在线等!
- 谁有读写INI文件的类(注意:不用CString类的).
- 为什么不需要注册啊?
- string型参数与“xxx”直接传值的区别在哪
- 300万个数据的排序问题
- 请大家推荐一本MFC编程的书
- MFC的单文档程序,自动创建了CTestView,CMainFrame等几个类,请问:CMainFrame是不是CTestView的基类?
服务器与客户端在同一台电脑运行,截获的数据有上述问题;服务器与客户端分别在两台电脑运行,截获的数据没有问题。采用 Release 模式编译:
无论服务器与客户端在同一台电脑运行还是分别在两台电脑运行,截获的数据都有上述问题。
我虽然是非阻塞模式,但是在程序中,如果 recv 取不到数据我会有 while 循环重新去调用 recv,直到超时或取够数据,因此也相当于是阻塞模式了。
再有服务端发送数据时要先判断发送缓冲区是否满,如果满就等待有空余空间再发送。
客户端接收数据时也要判断接收缓冲区是否有数据,如果有数据才接收。经过这样的逻辑可以保证数据可以正确接收。
{
int32 s32Timer = 0;
int32 s32Ret;
uint32 u32LeftLen = u32TotalLen;
int8 *p8Data = pRecvBuff;
CString s; do
{
s32Ret = recv(sockServer, p8Data, u32LeftLen, 0);
if (s32Ret > 0)
{
s32Timer = 0;
p8Data += s32Ret;
u32LeftLen -= s32Ret;
}
else
{
s32Timer ++;
Sleep(1000);
} if (s32Timer == 10)
{
AfxMessageBox("no more data !"); return FAILED_StreamPlayer;
}
} while (u32LeftLen > 0); pRecvBuff[u32TotalLen] = '\0';
return u32TotalLen;
}
正如我前面提到的,服务器代码肯定没有问题的,不用考虑。缓冲区也是足够大的,否则也不可能出现正确接收的情况。Release 并不是在所有情况下都正确,在本地和用虚拟机的情况下是有错误的。
异步模式是需要消息配合的,也就是说当消息通知有数据到达的时候,触发你的接收数据的函数OnReceive。
假设当前你刚接收完自定义数据包的长度字段,那么接收的时候就是接收这个长度数据,而在实际情况中,不是一次就能接收完你想要的数据的,这时候会发现实际接收到的数据比你自定义数据包长度小,所以你应该先把接收到的数据放到自己的一个缓冲区中以备拼成完整的数据包。待下次socket接收缓冲区中再次到达数据时,再进行接收的处理,直到把完整的数据包拼成后,才真正进入你自定义的数据包处理程序。
1、异步模式是需要消息配合的,也就是说当消息通知有数据到达的时候,触发你的接收数据的函数OnReceive。
——我是通过 WSAAsyncSelect IO模型实现的。主程序收到网络消息后,才会调用我的接收函数。2、我的 recv_packet_by_length 实现的是根据包长度接收数据的功能。while 循环的目的就是为了保证调用一次 recv_packet_by_length 就收到我想要的包长度。输入参数 u32TotalLen 是我想要接收的包长度,而不是整个接收缓冲的长度。你所说的拼包的操作在 while 循环中完成。
我觉得哈,你是因为没有等待客户端收到后就发第二包数据了,虽然用的是TCP协议,是流传输,不承在顺序问题,但问题是客户端,由于是用的是WSAAsyncSelect模型,他只会向WINDOWS窗口发送一个数据到达的消息,不会去真正执行接收过程,也对该消息是否能得到执行不感兴趣,而就算要执行某个FD_READ消息,如果有多包数据到达,也不能确定收哪一包,所以...
——窗口收到消息后,会调用我的接收代码。如下:BOOL CStreamPlayerDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if ((pMsg->message == WM_SOCKET) && (bSessionEndFlag == FALSE))
{
int32 s32Ret;
int32 s32Error = WSAGETSELECTERROR(pMsg->lParam);
int32 s32Event = WSAGETSELECTEVENT(pMsg->lParam); if (s32Event == FD_READ)
{
s32Ret = rtsp.recv_response();
if (s32Ret == FAILED_StreamPlayer)
{
bSessionEndFlag = TRUE;
end_session();
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}
你应该在每次recv数据前检查socket缓冲区是否有数据,如果没有数据,直接退出你的接收函数,并记录住当前接收了多少数据,并把已接收到的数据放到你自己的临时缓冲区中以备拼接。
等下次出发数据到达的消息时,你又可以继续接收未接收完的数据
我不知道你没有设置SOCKET接收缓冲区的大小,如果没有,SOCKET的缓冲区默认大小好像是4K(不敢肯定需要确认),但可以肯定地是不会超过64K,那么你的服务器会不停的发送数据到客户端的SOCKET缓冲内,好假如,你Recv了一次数据之后,开始Sleep(1000),那么在这1秒钟内服务器能够向你接收缓冲区发送多少数据呢? 本地的话我可以肯定地告诉肯定会超过64K,那么也就说在这1秒内服务器肯定会有发送数据失败的时候,失败原因就是客户端的SOCKET缓冲满了,SOCKET底层无法在继续帮你接收数据进行缓冲。 这个函数的修改方案:就是将Sleep(1000)删除掉,然后使用一个消息循环放入Sleep的位置
MSG msg;
while (::GetMessage(&msg, hNotifyWnd,0,0))
{
if (msg.message == WM_SOCKET_NOTIFY && (SOCKET)msg.wParam == sockServer)
{
if (WSAGETSELECTEVENT(msg.lParam) == FD_CLOSE)
{
// 关闭了这里要做处理的
break;
}
if (WSAGETSELECTEVENT(msg.lParam) == FD_READ)
{
break;
}
}else
{
::TranslateMessage(&msg);
}
}
放入这个消息循环不是怕丢(或是没有处理)Window的WM_SOCKET_NOTIFY消息,而是防止不停的Recv会造成CPU100%
当然,你可以将Sleep先直接去掉测试一下程序,然后再加入消息循环,我上面给出的不是很完善
完善的你可以参考CSocket 的Recv 函数调用的CSocket::PumpMessages(...)函数2. 处理不处理WM_SOCKET_NOTIFY 这个消息其实这个东西看程序流程而定,具体在那里处理掉,都可以因为这个东西肯定会发送到消息队列的
至于你用不用是另一会事,反正你处理不处理SOCKET缓冲中的数据是不会丢的,只有你调用recv的时候数据才能被取出来。
1。
我不知道你没有设置SOCKET接收缓冲区的大小,如果没有,SOCKET的缓冲区默认大小好像是4K(不敢肯定需要确认),但可以肯定地是不会超过 64K,那么你的服务器会不停的发送数据到客户端的SOCKET缓冲内,好假如,你Recv了一次数据之后,开始Sleep(1000),在半秒后,又开始由数据到达,那么在剩下的半秒内服务器能够向你接收缓冲区发送多少数据呢?本地的话我可以肯定地告诉肯定会超过64K,那么也就说在这不半秒内服务器肯定会有发送数据失败的时候,失败原因就是客户端的SOCKET缓冲满了,SOCKET底层无法在继续帮你接收数据进行缓冲。
2.数组越界。Release有问题是因为Release会有优化那一步而不是直接编译,所以一般类似数据越界啊什么的问题Release下会崩溃
我没有设置 SOCKET 的缓冲大小。如果按照你的说法,因为客户端处理不及时而造成 SOCKET 缓冲溢出,那么要根本杜绝这个问题应该怎么办呢?将 SOCKET 缓冲设大肯定不能根本解决问题,应该是客户端和服务器端在上层应用要做一些处理吧?
现在想请教大家的问题是:
1、上述第 3 点中的信息显示的确切意思是什么?百度了一下有人说是“多半是网络拥塞,导致顺序包抵达时间不同,延时太长,或者包丢失。”;2、上述第 4 点的信息显示是什么意思?3、我采用 Sleep 方式的代码,如果将写数据的文件指针定义为类的成员变量在客户端接收到的包是有丢包的,但如果将写数据的文件指针作为被调用函数的临时变量客户端接收到的包反而没有丢包,这是为什么?如果说丢包是因为我没有及时处理数据,那么后者所用的处理时间显然比前者更多,因为后者要频繁打开关闭文件,而前者只需要打开关闭一次;但是,我采用改写后的代码,两种文件指针定义方式都会丢包;4、也是最重要的一点:正如我在楼上提到的,如何杜绝因为客户端处理不及时而造成的丢包现象?谢谢大家!
1 TCP是可靠的协议,只要程序没问题就不会丢包,如果真的像你现在这样发生了丢包,应该就是我们程序的问题。
2 你上面的第一个问题应该是丢包造成的,tcp会通过重传来解决不会影响到应用层;第二个问题它只是wireshark自己生成的一个提示信息,不是错误。你可以参考http://www.wireshark.org/lists/wireshark-users/200806 /msg00047.html ;对于第三个问题我感觉应该跟文件指针是否是局部变量没关系,只要你在接收完之后把这个文件正常的关闭了或fllush了就没问题。我现在想看一下你调用int32 TCPSocket::recv_packet_by_length(int8 *pRecvBuff, uint32 u32TotalLen)
这个函数的上一层是怎么写的,我怀疑是别的地方的问题。
{
int32 s32Ret;
uint32 u32LeftLen = u32TotalLen;
int8 *p8Data = recvBuff.a8Buff;
do
{
s32Ret = recv(sockServer, p8Data, u32LeftLen, 0);
if (s32Ret > 0)
{
p8Data += s32Ret;
u32LeftLen -= s32Ret;
recvBuff.u32Offset += u32TotalLen;
}
else
{
return CONTINUE_StreamPlayer;
}
} while (u32LeftLen > 0); return SUCCEEDED_StreamPlayer;
}BOOL CStreamPlayerDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if (pMsg->message == WM_SOCKET)
{
int32 s32Ret;
int32 s32Error = WSAGETSELECTERROR(pMsg->lParam);
int32 s32Event = WSAGETSELECTEVENT(pMsg->lParam); if (s32Event == FD_READ)
{
s32Ret = rtsp.recv_data();
if (s32Ret == FAILED_StreamPlayer)
{
end_session();
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}int32 CRTSP::recv_data()
{
int32 s32Ret;
CStreamPlayerDlg *pMainDlg = (CStreamPlayerDlg *)AfxGetMainWnd();
if (u8RecvType == RECV_DATA_HEAD)
{
s32Ret = pMainDlg->sockTCP.recv_packet_by_length(recvBuff, 4 - recvBuff.u32Offset);
if (s32Ret != SUCCEEDED_StreamPlayer)
{
u8RecvType = RECV_DATA_HEAD; return s32Ret;
}
else
{
recvBuff.u32Offset = 0;
u8RecvType = RECV_DATA_BODY;
} recvBuff.u32Len = (recvBuff.p8Buff[2] << 8) | recvBuff.p8Buff[3];
} s32Ret = pMainDlg->sockTCP.recv_packet_by_length(recvBuff, recvBuff.u32Len - recvBuff.u32Offset);
if (s32Ret != SUCCEEDED_StreamPlayer)
{
u8RecvType = RECV_DATA_BODY; return s32Ret;
}
else
{
recvBuff.u32Len = recvBuff.u32Offset;
recvBuff.u32Offset = 0;
u8RecvType = RECV_DATA_HEAD;
} s32Ret = pMainDlg->codecVideo.get_NALU(recvBuff.p8Buff, recvBuff.u32Len);
if (s32Ret == FAILED_StreamPlayer)
{
ERROR_REPORT("get_NALU() failed !");
return FAILED_StreamPlayer;
} return SUCCEEDED_StreamPlayer;
}
对代码做下说明:
(1)、recvBuff.p8Buff 和 recvBuff.a8Buff 是指向同一个地址的,只是为了程序实现方便,前者定义为 unsigned char*,后者是 char 数组;
(2)、程序运行后,首先读 4 个字节的 head,这 4 个字节中包含后面跟的数据的长度。如果读不够 4 个字节,则立即返回,下次进来继续读;如果读够了 4 个字节,则计算得到后面跟的 body 的长度,然后根据长度读 body 数据,如果没读够需要的长度,则立即返回,下次进来继续读 body 数据;
(3)、recvBuff.u32Offset 用来实现数据拼接。变量 u8RecvType 是用来控制下次进来的读 head 还是读 body,其值交替变化,实现交替读取 data head 和 data body。如果任何一次没有从 SOCKET 读到需要的长度,则 u8RecvType 值保持不变,下次再进来就能实现数据拼接;如果每次能读到需要的长度,recvBuff.u32Offset 会置零,保证下一次读的数据放在接收缓冲(即 recvBuff.p8Buff)的起始处。谢谢大家!
(4)、get_NALU 函数用来实现将得到的数据写入文件。我在 32 楼叙述的两种文件指针定义方式,其一就是将文件指针做为 codecVideo 类的成员,其二就是作为 get_NALU 函数中的临时变量。PS:
要是这里能发附件就好了,我就可以把整个工程发上来。另外,我换了个名字,请大家不要奇怪。呵呵~~~
{
int32 s32Ret;
uint32 u32LeftLen = u32TotalLen;
int8 *p8Data = recvBuff.a8Buff;
do
{
s32Ret = recv(sockServer, p8Data, u32LeftLen, 0);
if (s32Ret > 0)
{
p8Data += s32Ret;
u32LeftLen -= s32Ret;
recvBuff.u32Offset += s32Ret;
}
else
{
return CONTINUE_StreamPlayer;
}
} while (u32LeftLen > 0); return SUCCEEDED_StreamPlayer;
}
int32 CRTSP::recv_data()
的逻辑好像没什么问题。
tcp协议的滑动窗口原理在发现接受端缓冲区满的情况下,会阻止发送端的socket的发送buffer继续发送数据,发送端调用send会失败,或返回值少于send函数里参数要求发送的数据长度,也就是没有发送完成。楼主在做发送端的程序时又检查send函数的返回值吗?你说的丢包可能是这个情况(举个例子):
1,假如发送端不停发送地址从0到10的数据。
2,接收端接收不过来,接收端socket接收缓存满
3,tcp滑动窗口协议限制发送端发送数据,这样程序通过send函数放入到发送端发送buffer的数据越积越多
4,发送端socket发送buffer满了,发送端send函数返回失败或返回值少于send函数里参数要求发送的数据长度,也就是没有发送完成。
5,我靠,这时候你的发送端程序没check send函数的返回值,继续发送下一个缓存。
6,这样中间的差值没有成功发送,你以为丢失了数据,其实更本没发出去!
非常感谢为我排查出一个安全隐患。TO 39 楼:
这么说来问题还是在于服务器端了?服务器端代码不是我写的,是用的 QuickTime 的开源代码 Darwin Streaming Server。如果真是服务器端的问题,我想,应该是我使用的方法不正确。现在先排查一下客户端有没有问题。下来我去看看服务器代码。
http://www.123people.ch/s/joerg+fetzlinked inhttp://www.xing.com/profile/Joerg_Fetzhttp://www.sensorsmag.com/sensors/humidity-moisture/smart-humidity-control-laundry-rooms-989http://www.shenzhenstuff.com/profile/JoergFetzhttp://blogs.myspace.com/index.cfm?fuseaction=blog.ListAll&friendId=501021546