如何解决UDP发送时CPU占用率高的问题? 本帖最后由 DDGG 于 2009-12-29 14:08:55 编辑 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 应该不会啊,以前这样写过,没出现这种问题啊。我的CPU还是 Intel p4 mobile的。 加入Sleep(1);的话效率就低了吧。另外我的正式程序是一个UDP文件收发的程序,每次发出一包数据,收到接收方的确认后又继续发的,CPU占用率就是这样高。不希望加入强制延时降低发送效率! 你这段代码CPU占用高是正常的。因为数据是发送给本机,根本没有经过网络设备发送出去,相当于在内存中复制数据。 gethostbyname("localhost"); 你每次多发送一些数据,比如几k,然后发送完sleep一下 while (nRet != SOCKET_ERROR) { nRet = sendto(sock, "1234", 4, 0, (sockaddr *)&addrTo, sizeof(addrTo)); }导致 跟UDP没关系 你就相当于死循环 当然CPU占用高实际处理处理时 上层可将数据入队列 发送线程定时去队列数据发送 或者和异步select模型也可 我把目标地址改成局域网里的另一台机器,也还是一样,好像和是不是发向本机没关系。struct hostent *he = gethostbyname("192.168.1.20"); UDP发送不管你设置阻塞还是非阻塞都是直接发送或者丢包所以你这个等于死循环CPU高很正常通常的程序中是不会有这样的设计的 还有一个问题,就是你每次发送的数据太少了。每次调用send函数的过程中要执行很多中间代码,需要占用一定的CPU资源。系统会先把数据先放入缓冲区中,等攒多了一起发,不会每次都进行I/O操作,所以多数时候都是没有等待而立即返回,因此CPU占用率会很高。 上面回复有误,更正一下,UDP不会合并多次发送的数据。不过问题原因还是数据太少,在整个过程中,执行I/O时间相对很少,大部分时间浪费在代码的执行上。把每次发送数据量改大一些(例如1KB),CPU占用率就会明显降低。 首先抱歉#2楼的说法有问题,把以前的代码翻出来改了给跟楼主类似的,发现CPU同样也是非常高,只是以前没看任务管理器,而且没开大程序没感觉而已。但是楼主的这个问题貌似有点不对,而且有点没有理解我1楼的意思。如果你真是收到确认之后继续发送数据包的话绝对不会出现这种情况。因为即使是局域网,UDP丢包率也是很高的,而且确认包也有可能丢。所以绝对不会造成死循环导致CPU过高的。要是你的正式程序真的是用UDP按照 发包-接收-确认-接收到确认-发包 这个流程的话,那就是见鬼了。由此我可不可以理解为 UDP的可靠传文件效率超高导致CPU忙不过来?另:我试了下,用非确认的模式下,每次发送1K,的数据CPU还是同样100%。遇到这种问题建议每次发送的数据包大些,发送1次或是几次之后稍微Sleep一下,(如果每次发4B sleep 1 的话效率当然不行了,但是可以设计为发送1M之后sleep1 就几乎上可以忽略了) 你说得不错。我正式的程序里是每次发1.5k数据的,而且发送后是等到接收方回送一个确认之后才继续发1.5k的,但CPU占用率就是这么高。我是单独开了一个线程进行接收,不过收到数据之后没有什么放入队列,再到另一个线程中去读队列进行处理(因为我觉得那样线程太多了,我不想设计得太复杂),而是直接调用了数据就绪的回调函数,由回调函数解析数据。回调函数中可能会发送(就是sendto(),没有使用队列)。不过用UDP一次发1M数据那是很不可靠的吧?而且发1M的数据sendto()的返回势必会变慢,后面即使加Sleep(1)也不管用了。而且人为地在发送中加入延时,或是定时去发送,都会大大降低传送效率的呀。 本来想的蛮好:把socket置成非阻塞,然后在发送之前用select去判断是否可以发送,如果还不能发这个线程就会被操作系统挂起,不占CPU的。等到了可以发送的时候,由于是非阻塞,调用sendto()只是把数据复制到网络缓存区就马上返回,由操作系统底层去发。但是这个美好的愿望似乎只能在TCP上实现,UDP不行! UDP包头中用16位表示长度,所以UDP包必须小于64KB,sendto一次发1MB数据肯定失败。你在15楼提的想法是可行的,与TCP或UDP没有关系。CPU占用高,说明数据很快就发出去了或者直接发送失败了,几乎没有等待。前面提了两点原因:一是数据发送到本地,实际上只是内存复制而没有I/O,所以没有等待;另一原因是你每次发送的数据量太小,I/O时间很短。另外还有一点原因,因为你设置了非阻塞,当I/O操作需要等待时直接返回失败了,而你没有执行select,直接循环再次发送,所以程序没有等待。如果把这点都修改一下,即:目标改成其它主机,每次发送KB级的数据量,并且不设置非阻塞或者用select等待,如果此时CPU占用仍很高,那说明你的网络及相关硬件速度快,程序本身没有问题,影响整体效率的瓶颈是CPU,这种情况下程序的传输效率已经很高了,如果还要提升传输效率,则要考虑代码优化和简化中间操作,或者更换更高性能的CPU。 又发现一个新问题:不能设置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);} 前面已经说了,UDP包必须小于64KB,确切地说,一次最多只能发送65507字节。 原来不能大于64k是这个原因啊!- -因为你设置了非阻塞,当I/O操作需要等待时直接返回失败了,而你没有执行select,直接循环再次发送……[/Quote]这个应该不成立,按我1楼的代码,如果失败的话就应该要退出while循环了,而它能够一直发。我试了,目标改成局域网内存在的另一主机,每次发送63k数据,不管设不设置非阻塞,此时CPU占用率仍然一直在25%左右,我的是4核的Intel Q8200(双核就会是50%,单核就是100%) 但是很奇怪,设置缓存区大小为1,然后发送63k的大小,仍然可以不报错,好像这个设置也对UDP没效果的说。 你要想效率高cpu当然得100%工作了,他只是不停的把数据拷贝到tcp的发送缓冲区,能不100%吗... 程序改成这样,也没怎么改啊,但好像和之前测试的情况有了区别:#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 如果网络MCU是1K字节,你把缓存设置的大些小些都不会影响丢包率,因为你发送的64K字节肯定会被拆开的。 UDP包越大就越容易丢,但如果包过小又浪费资源,最佳大小是让UDP封装为IP包后刚好等于网络的MTU,以太网的MTU是1500字节,所以UDP数据大约是1K多一些,具体数值可以自己计算一下或者在网上搜索,一般每次发1KB数据即可。如果在局域内传输,通常情况可以不考虑包的大小。 VC++服务器程序,怎样在两个线程中同时使用一个套接字?请教大家 什么问题 生成无法解析外部符号 解析ASP提示文件下载 安全警告 Idl中才能用BITMAPFILEHEADER等数据类型呢? 急,求助:关于VC++6中对文件的操作?。。。 我现在有个问题 利用CreateThread创建一个简单的端对端对话程序 如何从CBitmap中得到HBITMAP,谢谢 如何控制程序的下载速度,类似jetcar,那一位是金山毒霸开发组的 ms.net 的到来,com,com+还有明天吗? 处理数据库函数,怎么让参数传入字符串值 toolbar工具条不要图片怎么设置
我的CPU还是 Intel p4 mobile的。
加入Sleep(1);的话效率就低了吧。
另外我的正式程序是一个UDP文件收发的程序,每次发出一包数据,收到接收方的确认后又继续发的,CPU占用率就是这样高。不希望加入强制延时降低发送效率!
{
nRet = sendto(sock, "1234", 4, 0, (sockaddr *)&addrTo, sizeof(addrTo));
}
导致 跟UDP没关系 你就相当于死循环 当然CPU占用高
实际处理处理时 上层可将数据入队列 发送线程定时去队列数据发送 或者和异步select模型也可
我把目标地址改成局域网里的另一台机器,也还是一样,好像和是不是发向本机没关系。struct hostent *he = gethostbyname("192.168.1.20");
不管你设置阻塞还是非阻塞
都是直接发送或者丢包
所以你这个等于死循环
CPU高很正常
通常的程序中是不会有这样的设计的
但是楼主的这个问题貌似有点不对,而且有点没有理解我1楼的意思。如果你真是收到确认之后继续发送数据包的话绝对不会出现这种情况。
因为即使是局域网,UDP丢包率也是很高的,而且确认包也有可能丢。所以绝对不会造成死循环导致CPU过高的。要是你的正式程序真的是用UDP按照 发包-接收-确认-接收到确认-发包 这个流程的话,那就是见鬼了。
由此我可不可以理解为 UDP的可靠传文件效率超高导致CPU忙不过来?
另:我试了下,用非确认的模式下,每次发送1K,的数据CPU还是同样100%。
遇到这种问题建议每次发送的数据包大些,发送1次或是几次之后稍微Sleep一下,(如果每次发4B sleep 1 的话效率当然不行了,但是可以设计为发送1M之后sleep1 就几乎上可以忽略了)
你说得不错。我正式的程序里是每次发1.5k数据的,而且发送后是等到接收方回送一个确认之后才继续发1.5k的,但CPU占用率就是这么高。我是单独开了一个线程进行接收,不过收到数据之后没有什么放入队列,再到另一个线程中去读队列进行处理(因为我觉得那样线程太多了,我不想设计得太复杂),而是直接调用了数据就绪的回调函数,由回调函数解析数据。回调函数中可能会发送(就是sendto(),没有使用队列)。不过用UDP一次发1M数据那是很不可靠的吧?而且发1M的数据sendto()的返回势必会变慢,后面即使加Sleep(1)也不管用了。而且人为地在发送中加入延时,或是定时去发送,都会大大降低传送效率的呀。
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);
}
原来不能大于64k是这个原因啊!- -
因为你设置了非阻塞,当I/O操作需要等待时直接返回失败了,而你没有执行select,直接循环再次发送……
[/Quote]这个应该不成立,按我1楼的代码,如果失败的话就应该要退出while循环了,而它能够一直发。
我试了,目标改成局域网内存在的另一主机,每次发送63k数据,不管设不设置非阻塞,此时CPU占用率仍然一直在25%左右,我的是4核的Intel Q8200(双核就会是50%,单核就是100%)
但是很奇怪,设置缓存区大小为1,然后发送63k的大小,仍然可以不报错,好像这个设置也对UDP没效果的说。
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
如果网络MCU是1K字节,你把缓存设置的大些小些都不会影响丢包率,因为你发送的64K字节肯定会被拆开的。