如何解决UDP发送时CPU占用率高的问题?

DDGG 2009-12-29 02:07:16
UDP发送时CPU占用率非常高(单核CPU 100%、双核50%、……),试了将socket设置为非阻塞或是在发送前select判可发都无济于事。

在网上搜了一把也没找到什么解决办法,不知道大家有何良策?

附上测试用的代码如下(4个字节“1234”连续不断发送):

#include "stdlib.h"

int main(int argc, char* argv[])
{
WSADATA wsadata;
WSAStartup(0x0202, &wsadata);

sockaddr_in addrTo;
struct hostent *he = gethostbyname("localhost");
ASSERT(he != NULL);
if (he)
{
addrTo.sin_family = AF_INET;
addrTo.sin_addr = *((struct in_addr *)he->h_addr);
addrTo.sin_port = htons(1001);
}

SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET)
{
return SOCKET_ERROR;
}

// 非阻塞不等于立即返回,UDP是立即模式,所以仍将等到所有数据被发送才返回。
// 故此处设置非阻塞似无意义。
u_long val = TRUE;
VERIFY(ioctlsocket(sock, FIONBIO, &val) != SOCKET_ERROR);

int nRet = 0;
while (nRet != SOCKET_ERROR)
{
nRet = sendto(sock, "1234", 4, 0, (sockaddr *)&addrTo, sizeof(addrTo));
}

system("pause");

return 0;
}
...全文
1947 28 打赏 收藏 转发到动态 举报
写回复
用AI写文章
28 条回复
切换为时间正序
请发表友善的回复…
发表回复
DDGG 2009-12-30
  • 打赏
  • 举报
回复
本来想的蛮好:把socket置成非阻塞,然后在发送之前用select去判断是否可以发送,如果还不能发这个线程就会被操作系统挂起,不占CPU的。等到了可以发送的时候,由于是非阻塞,调用sendto()只是把数据复制到网络缓存区就马上返回,由操作系统底层去发。

但是这个美好的愿望似乎只能在TCP上实现,UDP不行!
DDGG 2009-12-30
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 yschenwei 的回复:]
引用 3 楼 ddgg 的回复:
另外我的正式程序是一个UDP文件收发的程序,每次发出一包数据,收到接收方的确认后又继续发的,CPU占用率就是这样高。不希望加入强制延时降低发送效率!

首先抱歉#2楼的说法有问题,把以前的代码翻出来改了给跟楼主类似的,发现CPU同样也是非常高,只是以前没看任务管理器,而且没开大程序没感觉而已。
但是楼主的这个问题貌似有点不对,而且有点没有理解我1楼的意思。如果你真是收到确认之后继续发送数据包的话绝对不会出现这种情况。
因为即使是局域网,UDP丢包率也是很高的,而且确认包也有可能丢。所以绝对不会造成死循环导致CPU过高的。要是你的正式程序真的是用UDP按照 发包-接收-确认-接收到确认-发包 这个流程的话,那就是见鬼了。
由此我可不可以理解为 UDP的可靠传文件效率超高导致CPU忙不过来?
另:我试了下,用非确认的模式下,每次发送1K,的数据CPU还是同样100%。
遇到这种问题建议每次发送的数据包大些,发送1次或是几次之后稍微Sleep一下,(如果每次发4B sleep 1 的话效率当然不行了,但是可以设计为发送1M之后sleep1 就几乎上可以忽略了)
[/Quote]

你说得不错。

我正式的程序里是每次发1.5k数据的,而且发送后是等到接收方回送一个确认之后才继续发1.5k的,但CPU占用率就是这么高。

我是单独开了一个线程进行接收,不过收到数据之后没有什么放入队列,再到另一个线程中去读队列进行处理(因为我觉得那样线程太多了,我不想设计得太复杂),而是直接调用了数据就绪的回调函数,由回调函数解析数据。回调函数中可能会发送(就是sendto(),没有使用队列)。

不过用UDP一次发1M数据那是很不可靠的吧?而且发1M的数据sendto()的返回势必会变慢,后面即使加Sleep(1)也不管用了。而且人为地在发送中加入延时,或是定时去发送,都会大大降低传送效率的呀。
DDGG 2009-12-30
  • 打赏
  • 举报
回复
谢谢大家!结帖了。
cnzdgs 2009-12-30
  • 打赏
  • 举报
回复
[Quote=引用 24 楼 ddgg 的回复:]
对了,还有一个问题,如果每次发送65507字节是不是会容易丢包啊?
[/Quote]
UDP包越大就越容易丢,但如果包过小又浪费资源,最佳大小是让UDP封装为IP包后刚好等于网络的MTU,以太网的MTU是1500字节,所以UDP数据大约是1K多一些,具体数值可以自己计算一下或者在网上搜索,一般每次发1KB数据即可。如果在局域内传输,通常情况可以不考虑包的大小。
toobigsea 2009-12-30
  • 打赏
  • 举报
回复
学习一下。。。
james_hw 2009-12-30
  • 打赏
  • 举报
回复
[Quote=引用 24 楼 ddgg 的回复:]
对了,还有一个问题,如果每次发送65507字节是不是会容易丢包啊?
[/Quote]

如果网络MCU是1K字节,你把缓存设置的大些小些都不会影响丢包率,因为你发送的64K字节肯定会被拆开的。
DDGG 2009-12-30
  • 打赏
  • 举报
回复
对了,还有一个问题,如果每次发送65507字节是不是会容易丢包啊?
DDGG 2009-12-30
  • 打赏
  • 举报
回复
程序改成这样,也没怎么改啊,但好像和之前测试的情况有了区别:

#include <string>
using namespace std;

#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")


int main(int argc, char* argv[])
{
WSADATA wsadata;
WSAStartup(0x0202, &wsadata);

sockaddr_in addrTo;
struct hostent *he = gethostbyname("192.168.1.99"); // 局域网内另一台机器
if (he)
{
addrTo.sin_family = AF_INET;
addrTo.sin_addr = *((struct in_addr *)he->h_addr);
addrTo.sin_port = htons(1001);
}
else
{
return SOCKET_ERROR;
}

SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET)
{
return SOCKET_ERROR;
}

int nRet = 0;

// 设置非阻塞。好像又管用了,设置之后发送可能返回10035或10055。
// u_long val = TRUE;
// nRet = ioctlsocket(sock, FIONBIO, &val);
// if (nRet == SOCKET_ERROR)
// {
// return SOCKET_ERROR;
// }

// 设置发送缓存区的大小,只在非阻塞时有影响。
// int nSendBufSize = 1;
// nRet = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBufSize, sizeof(nSendBufSize));
// if (nRet == SOCKET_ERROR)
// {
// return SOCKET_ERROR;
// }

string strTemp;
strTemp.resize(1500); // 65507
while (nRet != SOCKET_ERROR)
{
nRet = sendto(sock, strTemp.data(), strTemp.size(), 0, (sockaddr *)&addrTo, sizeof(addrTo));
}

int nError = WSAGetLastError();
printf("WSAGetLastError() = %d", nError);

system("pause");

return 0;
}


CPU占用率在1-5%之间,当改成每次发65507字节,CPU就降到0-2%了。
在任务管理器查看百兆网卡的网络流量基本在97%以上,应该是充分利用带宽了,效率没有问题。

现在明白了几点:
1. 发送到本机和发送到网络上其他机器是不一样的,发送给本机根本没有经过网络设备,相当于在内存中复制数据。在任务管理器里查看网卡流量也是没有的。
2. 设置非阻塞对UDP端口有用,但是象1楼的代码里因为每次发送数据只有4个字节,网络设备足够快,不会报10035错而退出while循环,如果改大一点比如100个字节,就会了。
3. 设置发送缓存区只对非阻塞方式有用。

谢谢cnzdgs
ghost0088 2009-12-30
  • 打赏
  • 举报
回复
你要想效率高cpu当然得100%工作了,他只是不停的把数据拷贝到tcp的发送缓冲区,能不100%吗...
DDGG 2009-12-30
  • 打赏
  • 举报
回复
等一下……好像是我有问题
DDGG 2009-12-30
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 cnzdgs 的回复:]
前面已经说了,UDP包必须小于64KB,确切地说,一次最多只能发送65507字节。
[/Quote]

但是很奇怪,设置缓存区大小为1,然后发送63k的大小,仍然可以不报错,好像这个设置也对UDP没效果的说。
DDGG 2009-12-30
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 cnzdgs 的回复:]
UDP包头中用16位表示长度,所以UDP包必须小于64KB,sendto一次发1MB数据肯定失败。
[/Quote]

原来不能大于64k是这个原因啊!- -


因为你设置了非阻塞,当I/O操作需要等待时直接返回失败了,而你没有执行select,直接循环再次发送……
[/Quote]

这个应该不成立,按我1楼的代码,如果失败的话就应该要退出while循环了,而它能够一直发。

[Quote=引用 16 楼 cnzdgs 的回复:]
如果把这点都修改一下,即:目标改成其它主机,每次发送KB级的数据量,并且不设置非阻塞或者用select等待,如果此时CPU占用仍很高,那说明你的网络及相关硬件速度快……
[/Quote]

我试了,目标改成局域网内存在的另一主机,每次发送63k数据,不管设不设置非阻塞,此时CPU占用率仍然一直在25%左右,我的是4核的Intel Q8200。。。(双核就会是50%,单核就是100%)
cnzdgs 2009-12-30
  • 打赏
  • 举报
回复
前面已经说了,UDP包必须小于64KB,确切地说,一次最多只能发送65507字节。
DDGG 2009-12-30
  • 打赏
  • 举报
回复
又发现一个新问题:不能设置UDP的发送缓存区大小?

int nSendBuf = 100*1024;
int nRet = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBuf, sizeof(int)); // 设置虽然成功但是好像没有用,把这里的100k改成1,下面发63k大小反而可以成功。
ASSERT(nRet != SOCKET_ERROR);

string strTemp;
strTemp.resize(64*1024);
nRet = sendto(sock, strTemp.data(), strTemp.size(), 0, (sockaddr *)&addrTo, sizeof(addrTo));
if (nRet == SOCKET_ERROR)
{
int werr = WSAGetLastError(); // 10040 缓存区不足
printf("werr=%d", werr);
}
cnzdgs 2009-12-30
  • 打赏
  • 举报
回复
UDP包头中用16位表示长度,所以UDP包必须小于64KB,sendto一次发1MB数据肯定失败。

你在15楼提的想法是可行的,与TCP或UDP没有关系。

CPU占用高,说明数据很快就发出去了或者直接发送失败了,几乎没有等待。前面提了两点原因:一是数据发送到本地,实际上只是内存复制而没有I/O,所以没有等待;另一原因是你每次发送的数据量太小,I/O时间很短。另外还有一点原因,因为你设置了非阻塞,当I/O操作需要等待时直接返回失败了,而你没有执行select,直接循环再次发送,所以程序没有等待。如果把这点都修改一下,即:目标改成其它主机,每次发送KB级的数据量,并且不设置非阻塞或者用select等待,如果此时CPU占用仍很高,那说明你的网络及相关硬件速度快,程序本身没有问题,影响整体效率的瓶颈是CPU,这种情况下程序的传输效率已经很高了,如果还要提升传输效率,则要考虑代码优化和简化中间操作,或者更换更高性能的CPU。
yschenwei 2009-12-29
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 ddgg 的回复:]
另外我的正式程序是一个UDP文件收发的程序,每次发出一包数据,收到接收方的确认后又继续发的,CPU占用率就是这样高。不希望加入强制延时降低发送效率!
[/Quote]
首先抱歉#2楼的说法有问题,把以前的代码翻出来改了给跟楼主类似的,发现CPU同样也是非常高,只是以前没看任务管理器,而且没开大程序没感觉而已。
但是楼主的这个问题貌似有点不对,而且有点没有理解我1楼的意思。如果你真是收到确认之后继续发送数据包的话绝对不会出现这种情况。
因为即使是局域网,UDP丢包率也是很高的,而且确认包也有可能丢。所以绝对不会造成死循环导致CPU过高的。要是你的正式程序真的是用UDP按照 发包-接收-确认-接收到确认-发包 这个流程的话,那就是见鬼了。
由此我可不可以理解为 UDP的可靠传文件效率超高导致CPU忙不过来?
另:我试了下,用非确认的模式下,每次发送1K,的数据CPU还是同样100%。
遇到这种问题建议每次发送的数据包大些,发送1次或是几次之后稍微Sleep一下,(如果每次发4B sleep 1 的话效率当然不行了,但是可以设计为发送1M之后sleep1 就几乎上可以忽略了)
cdm2179 2009-12-29
  • 打赏
  • 举报
回复
学习
cnzdgs 2009-12-29
  • 打赏
  • 举报
回复
上面回复有误,更正一下,UDP不会合并多次发送的数据。不过问题原因还是数据太少,在整个过程中,执行I/O时间相对很少,大部分时间浪费在代码的执行上。把每次发送数据量改大一些(例如1KB),CPU占用率就会明显降低。
cnzdgs 2009-12-29
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 ddgg 的回复:]
引用 4 楼 cnzdgs 的回复:
你这段代码CPU占用高是正常的。因为数据是发送给本机,根本没有经过网络设备发送出去,相当于在内存中复制数据。


我把目标地址改成局域网里的另一台机器,也还是一样,好像和是不是发向本机没关系。

struct hostent *he = gethostbyname("192.168.1.20");
[/Quote]
还有一个问题,就是你每次发送的数据太少了。每次调用send函数的过程中要执行很多中间代码,需要占用一定的CPU资源。系统会先把数据先放入缓冲区中,等攒多了一起发,不会每次都进行I/O操作,所以多数时候都是没有等待而立即返回,因此CPU占用率会很高。
danscort2000 2009-12-29
  • 打赏
  • 举报
回复
UDP发送
不管你设置阻塞还是非阻塞
都是直接发送或者丢包
所以你这个等于死循环
CPU高很正常
通常的程序中是不会有这样的设计的
加载更多回复(8)

18,356

社区成员

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

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