基于UDP协议的recvfrom函数无法获取一个完整的UDP包

ricciinhp 2014-07-02 01:57:39

void RZAgent::RecvPeerData(WAIT_MODE _eWaitMode /* = ENUM_SYN */)
{
pNetConn->WaitForPeerResponse(_eWaitMode);
pNetConn->RecvDataFromPeer();
}

利用这个函数接收对端的数据,首先等待对端响应,响应函数中主要是利用select等待套接字是否可读,如下

::select(maxFd+1, &readySet, NULL, NULL, ptv)

并且由于接收操作是放在一个单独的线程里的,所以使用的是阻塞模式等待数据到达,一旦数据到了,那么我就开始接收
RecvDataFromPeer();这个函数中封装了recvfrom函数,然后将收到的数据放到本地缓冲区中

现在的问题是:每次调用RecvDataFromPeer();也即recvfrom,用抓包工具看完整的一个UDP数据包如下

80 C8 00 06 40 A5 8C 19 D7 5E 18 68 52 2D 0E 56 ....@... .^.hR-.V
6A 9F AA 5F 00 00 00 01 00 00 00 26 81 CA 00 06 j.._.... ...&....
40 A5 8C 19 01 0E 51 54 53 53 31 34 30 34 32 37 @.....QT SS140427
39 32 37 32 00 00 00 00 81 CC 00 06 40 A5 8C 19 9272.... ....@...
71 74 73 69 00 00 00 00 00 00 00 02 61 74 00 04 qtsi.... ....at..
00 00 00 14

但是每次从recvfrom返回时我只能接收到80 C8这2个字节。
所以有没有这样一种可能,当前协议栈正在接收一个UDP数据包的时候,还没全部收完,然后进程切换,由于本地使用同步模式阻塞在这个套接字上,select检测到当前套接字上数据可读,然后直接返回,再调用recvfrom返回了整个包的一部分?

PS:对端服务器发送的是一个几十M的文件,每次切成一个块,然后使用UDP不停地发。
...全文
592 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
ricciinhp 2014-07-03
  • 打赏
  • 举报
回复
引用 11 楼 baichi4141 的回复:
[quote=引用 9 楼 ricciinhp 的回复:] 不知道为什么i总是打印出了0,但是rBuffer还是能够正常显示接收到的数据 我把printf放到recv函数调用处,结果能够正常返回接到的字节数,你能帮我看下问题出在哪吗,谢谢
if(a=b==c) 首先运行b==c,将判断结果赋给a,然后判断a的值 虽然我知道你是后括号写错了位置,但根本上我就不喜欢这种写法 建议多用几个临时变量保存每个运算的结果,别想着把多个运算写在一行里 特别是当你每次修改时都已经不记得这一行之前是用来干什么 [/quote] 再次感谢,我知道了,下次写清晰一点就是了
baichi4141 2014-07-03
  • 打赏
  • 举报
回复
引用 9 楼 ricciinhp 的回复:
不知道为什么i总是打印出了0,但是rBuffer还是能够正常显示接收到的数据 我把printf放到recv函数调用处,结果能够正常返回接到的字节数,你能帮我看下问题出在哪吗,谢谢
if(a=b==c) 首先运行b==c,将判断结果赋给a,然后判断a的值 虽然我知道你是后括号写错了位置,但根本上我就不喜欢这种写法 建议多用几个临时变量保存每个运算的结果,别想着把多个运算写在一行里 特别是当你每次修改时都已经不记得这一行之前是用来干什么
ricciinhp 2014-07-03
  • 打赏
  • 举报
回复
MSDN上有如下说明: If no error occurs, recvfrom returns the number of bytes received. If the connection has been gracefully closed, the return value is zero. 但是我接到了数据,它还是返回0
ricciinhp 2014-07-03
  • 打赏
  • 举报
回复
引用 7 楼 fishion 的回复:
你得把recvfrom的返回值也带上,这才是真正到的接收数据的长度
今天改了一下代码,又出问题,代码如下

	int i;
	::memset(rBuffer, 0, ulBufSize);
#ifdef WIN32
	if ((i = ::recvfrom(iSockFile, rBuffer, ulBufSize, 0,
									(sockaddr*)&lPeerAddr, &iSize) == SOCKET_ERROR))
		Log::ERR("Platform SDK \'recvfrom\' called failed.\tError Code: %d\n", WSAGetLastError());
#endif
	printf("The number of received bytes is %d, %s\n", i, rBuffer);
不知道为什么i总是打印出了0,但是rBuffer还是能够正常显示接收到的数据 我把printf放到recv函数调用处,结果能够正常返回接到的字节数,你能帮我看下问题出在哪吗,谢谢
ricciinhp 2014-07-02
  • 打赏
  • 举报
回复
引用 7 楼 fishion 的回复:
你得把recvfrom的返回值也带上,这才是真正到的接收数据的长度
嗯,这个自然 同时也谢谢5L的热心解答,结贴了
fishion 2014-07-02
  • 打赏
  • 举报
回复
你得把recvfrom的返回值也带上,这才是真正到的接收数据的长度
ricciinhp 2014-07-02
  • 打赏
  • 举报
回复
引用 3 楼 baichi4141 的回复:
我是真心不懂为啥都喜欢用字符串保存二进制数据 要说新手图简单吧,也有不少写这种代码的人水平比我更好 要说高手用得好吧,为啥又犯一些低级错误呢 std::string(rBuffer) std::string是字符串容器,其构造函数按C风格字符串解释输入指针指向的内存,最终调用的是strlen函数 你收到的数据包第三个字节就是0,它当然只解析前两个字节
谢谢指点!明白了
mujiok2003 2014-07-02
  • 打赏
  • 举报
回复
引用 2 楼 ricciinhp 的回复:
[quote=引用 1 楼 fishion 的回复:] recvfrom有没有调用错了,UDP方式的话,包不完整是会丢弃整包数据的,而且从你抓包的情况来年确实是整包数据都到了
谢谢回复,我贴下RecvDataFromPeer的代码:

std::string RZUdpConn::RecvDataFromPeer()
{
	if (!SocketValid())
		Log::ERR("Can not receive data, Please BuildConnection first.\n");

	::memset(rBuffer, 0, ulBufSize);
#ifdef WIN32
	struct sockaddr_in lPeerAddr;
	::memset(&lPeerAddr, 0, sizeof(sockaddr_in));
	int iSize = sizeof(lPeerAddr);
	if (::recvfrom(iSockFile, rBuffer, ulBufSize, 0,
							(sockaddr*)&lPeerAddr, &iSize) == SOCKET_ERROR)
		Log::ERR("Platform SDK \'recvfrom\' called failed.\tError Code: %d\n", WSAGetLastError());
#endif
	return std::string(rBuffer);
}
就是简单的对recvfrom的封装,没看出有什么问题啊 之前搜了许多资料,知道在收udp包时,如果物理层校验通过,发现数据传输未出错,那么会传给IP层,否则就是整包丢弃。所以recvfrom调用要么收不到,要么收到一个完整的,而上面这种情况根本不会出现。但打印调试信息,发现确实收不全,我也是无奈了。 本来要做一下解析的,因为收到的包含有8个字节的头部,如果少于8个字节说明包的格式不正确,就会直接丢弃,现在在每次调用返回2个字节,根本不做解析就直接丢弃,在解析模块里下了断点,完全不会陷进去。[/quote] buf不一定是字符串, 不能直接std::string(buf),试试std::vector<char>
mujiok2003 2014-07-02
  • 打赏
  • 举报
回复
怀疑recvfrom的参数有问题。
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
baichi4141 2014-07-02
  • 打赏
  • 举报
回复
我是真心不懂为啥都喜欢用字符串保存二进制数据 要说新手图简单吧,也有不少写这种代码的人水平比我更好 要说高手用得好吧,为啥又犯一些低级错误呢 std::string(rBuffer) std::string是字符串容器,其构造函数按C风格字符串解释输入指针指向的内存,最终调用的是strlen函数 你收到的数据包第三个字节就是0,它当然只解析前两个字节
ricciinhp 2014-07-02
  • 打赏
  • 举报
回复
引用 1 楼 fishion 的回复:
recvfrom有没有调用错了,UDP方式的话,包不完整是会丢弃整包数据的,而且从你抓包的情况来年确实是整包数据都到了
谢谢回复,我贴下RecvDataFromPeer的代码:

std::string RZUdpConn::RecvDataFromPeer()
{
	if (!SocketValid())
		Log::ERR("Can not receive data, Please BuildConnection first.\n");

	::memset(rBuffer, 0, ulBufSize);
#ifdef WIN32
	struct sockaddr_in lPeerAddr;
	::memset(&lPeerAddr, 0, sizeof(sockaddr_in));
	int iSize = sizeof(lPeerAddr);
	if (::recvfrom(iSockFile, rBuffer, ulBufSize, 0,
							(sockaddr*)&lPeerAddr, &iSize) == SOCKET_ERROR)
		Log::ERR("Platform SDK \'recvfrom\' called failed.\tError Code: %d\n", WSAGetLastError());
#endif
	return std::string(rBuffer);
}
就是简单的对recvfrom的封装,没看出有什么问题啊 之前搜了许多资料,知道在收udp包时,如果物理层校验通过,发现数据传输未出错,那么会传给IP层,否则就是整包丢弃。所以recvfrom调用要么收不到,要么收到一个完整的,而上面这种情况根本不会出现。但打印调试信息,发现确实收不全,我也是无奈了。 本来要做一下解析的,因为收到的包含有8个字节的头部,如果少于8个字节说明包的格式不正确,就会直接丢弃,现在在每次调用返回2个字节,根本不做解析就直接丢弃,在解析模块里下了断点,完全不会陷进去。
fishion 2014-07-02
  • 打赏
  • 举报
回复
recvfrom有没有调用错了,UDP方式的话,包不完整是会丢弃整包数据的,而且从你抓包的情况来年确实是整包数据都到了

64,281

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

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