udp丢包问题

news/2024/5/18 15:13:47 标签: udp

 

一、主要丢包原因

1、接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。

2、发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。

3、发送的包较大,超过接受者缓存导致丢包:包超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。

int nRecvBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

巨型帧可以设置到9K缓存

4、发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决,但有时解决不了。所以在发送频率过快的时候还是考虑sleep一下吧。

5、局域网内不丢包,公网上丢包。这个问题我也是通过切割小包并sleep发送解决的。如果流量太大,这个办法也不灵了。总之udp丢包总是会有的,如果出现了用我的方法解决不了,还有这个几个方法: 要么减小流量,要么换tcp协议传输,要么做丢包重传的工作。

 

 

二、具体问题分析

1.发送频率过高导致丢包

很多人会不理解发送速度过快为什么会产生丢包,原因就是UDP的SendTo不会造成线程阻塞,也就是说,UDP的SentTo不会像TCP中的SendTo那样,直到数据完全发送才会return回调用函数,它不保证当执行下一条语句时数据是否被发送。(SendTo方法是异步的)这样,如果要发送的数据过多或者过大,那么在缓冲区满的那个瞬间要发送的报文就很有可能被丢失。至于对“过快”的解释,作者这样说:“A few packets a second are not an issue; hundreds or thousands may be an issue.”(一秒钟几个数据包不算什么,但是一秒钟成百上千的数据包就不好办了)。 要解决接收方丢包的问题很简单,首先要保证程序执行后马上开始监听(如果数据包不确定什么时候发过来的话),其次,要在收到一个数据包后最短的时间内重新回到监听状态,其间要尽量避免复杂的操作(比较好的解决办法是使用多线程回调机制)。

2.报文过大丢包

至于报文过大的问题,可以通过控制报文大小来解决,使得每个报文的长度小于MTU。以太网的MTU通常是1500 bytes,其他一些诸如拨号连接的网络MTU值为1280 bytes,如果使用speaking这样很难得到MTU的网络,那么最好将报文长度控制在1280 bytes以下。

3.发送方丢包

发送方丢包:内部缓冲区(internal buffers)已满,并且发送速度过快(即发送两个报文之间的间隔过短);  接收方丢包:Socket未开始监听;  虽然UDP的报文长度最大可以达到64 kb,但是当报文过大时,稳定性会大大减弱。这是因为当报文过大时会被分割,使得每个分割块(翻译可能有误差,原文是fragmentation)的长度小于MTU,然后分别发送,并在接收方重新组合(reassemble),但是如果其中一个报文丢失,那么其他已收到的报文都无法返回给程序,也就无法得到完整的数据了。 

引用:http://www.voidcn.com/article/p-agyzitmn-ot.html

开辟了一个新的线程负责将数据写入到文件,原来的线程只负责接收数据。当接收数据的线程收到一副图像的最后一个包时,马上置位相应变量,另一个线程不断检查该变量,条件满足时将数据写入到文件。其实这种轮询的方式也很耗费CPU资源,最好的办法是用事件(Event)来同步,不过这个版本的结果就很完美了,基本上没有丢失包,我就没有继续改进了。

#include // ......省略头文件

//......此处省略大量变量的声明和定义

bool IsNewImageReceived = false;

uint32_t GetImageNo(char data[PACKSIZ]);
uint16_t GetBlockNo(char data[PACKSIZ]);

// 和多线程相关的变量
static HANDLE g_hMutex = INVALID_HANDLE_VALUE;
HANDLE hThread = INVALID_HANDLE_VALUE;
DWORD WINAPI Thread_fcn(LPVOID lpParameter); // 线程处理函数

int main()
{
    // ......此处省略socket、bind的过程

    // 设置接受缓冲区的大小
    int nRecvBuf = 38400 * 1024;
    if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const char *)&nRecvBuf, sizeof(int)) == -1) {
        printf("Set receive buffer size failed\n");
        exit(EXIT_FAILURE);
    }

    // 建立新进程负责写入数据到文件
    hThread = CreateThread(NULL, 0, Thread_fcn, NULL, 0, NULL);
    // 互斥量保护数据
    g_hMutex = CreateMutex(NULL, FALSE, L"Mutex");

    // 主线程只负责接受数据
    for (;;) {
        if ((recvfrom(s, packetData, PACKSIZ, 0, (struct sockaddr *)&si_other, &slen)) == SOCKET_ERROR) {
            printf("recvfrom() failed with error code : %d", WSAGetLastError());
            exit(EXIT_FAILURE);
        }
        uint32_t imageno = GetImageNo(packetData); // 获取图像编号
        WaitForSingleObject(g_hMutex, INFINITE);                         // LOCK
        // 如果新的一副图像数据的一个包来了,立刻通知另外的线程保存数据
        if (imageno != curr_imageno) {
            prev_imageno = curr_imageno;
            curr_imageno = imageno;
            // 如果只使用一个数组,写入文件时新的数据可能会覆盖数组内容,因此使用两个数组,两个指针分别指向它们
            // 当写入条件满足时,用另外一个数组用于存放新的数据,此时交换指针即可
            swap(ptr_write, ptr_read);
            IsNewImageReceived = true; // 置位变量
        }
        pack_cnt++;
        ReleaseMutex(g_hMutex);                                         // UNLOCK
        // 把包拼凑起来
        uint16_t blockno = GetBlockNo(packetData);
        uint32_t offset = (blockno - 1) * BLKSIZ;
        memcpy(ptr_read+offset, packetData + 12, BLKSIZ);
    }
    closesocket(s);
    WSACleanup();
    return 0;
}

// 线程函数,负责写入数据到文件
DWORD WINAPI Thread_fcn(LPVOID lpParameter)
{
    for (;;) {
        WaitForSingleObject(g_hMutex, INFINITE);                             // LOCK
        // 不断检查条件是否满足
        if (IsNewImageReceived) {
            IsNewImageReceived = false; // 清变量
            pack_cnt = 0;
            // 文件名
            stringstream ss;
            ss << prev_imageno;
            string filename = path + string("image") + ss.str() + string(".raw");
            ReleaseMutex(g_hMutex);                                          // UNLOCK
            // 写入文件
            ofstream fout(filename.c_str(), ofstream::binary);
            if (!fout) {
                cerr << "Failed to open file!" << endl;
            }
            fout.write(ptr_write, FILESIZ);
            fout.close();
            // 将接受区清零
            memset(ptr_write, 0, FILESIZ);
        } else {
            ReleaseMutex(g_hMutex);
        }
    }
}

 


http://www.niftyadmin.cn/n/1115246.html

相关文章

node-sass 安装失败的原因及解决办法

2019独角兽企业重金招聘Python工程师标准>>> 在安装node-sass时&#xff0c;偶尔会遇到安装失败&#xff0c;由于node-sass底层是用python写的&#xff0c;所以先确保是否有python环境&#xff0c;如果没有请安装好 python。 继续往下。 node-sass 安装失败的原因 很…

linux-DNS域名正向解析、服务程序BIND安装与配置

文章目录一、DNS系统1、概念2、作用3、域名系统是树形的域名树4、工作原理5、DNS解析的三种方式二、DNS系统类型1、 缓存域名服务器2、主域名服务器3、 从域名服务器6、清理缓存命令二、DNS的安装和配置文件1、安装DNS服务软件&#xff1a;BIND软件1.1、相关的软件包2、bind的配…

将大数据变成可管理的数据

将大数据变成可管理的数据 大数据是无所不在的&#xff0c;因为它可以提供有价值的洞察力&#xff0c;如果没有它是不可用的。然而&#xff0c;分析大数据集可能会产生问题。首先&#xff0c;大数据是大规模的&#xff0c;有时太大&#xff0c;不能通过常用的分析工具有效地处理…

petalinux2018.3 error 记录

petalinux-config --get-hw-description. 报错 [INFO] sourcing bitbake ERROR: Failed to source bitbake ERROR: Failed to config project. ERROR: Get hw description Failed!. vi ./build/config.log Run devtool --help for further details. OpenEmbedded require…

Android 无标题 全屏设置

标题栏和状态栏  Android程序默认情况下是包含状态栏和标题栏的。 在Eclipse中新建一个Android程序&#xff0c;运行后显示如下&#xff1a; 图中标出了状态栏&#xff08;显示时间、电池电量、网络等&#xff09;和标题栏&#xff08;显示应用的名称&#xff0c;即activity的…

vc2008构建和使用libcurl静态库

1>下载CURL源代码curl-7.26.0.zip 2>用VC2008/2005打开工程curl-7.26.0\lib\libcurl.vcproj,转换下工程并构建,可以直接编译成功! 3>新建个控制台工程测试下刚才编译的静态库libcurl.lib,可以在libcurl\curl-7.26.0\docs\examples目录找个简单的使用curl的例子,在这个…

linxu DNS域名反向解析、缓存服务器、主从服务器和同步、分离解析

文章目录一、DNS域名解析-反向解析1、概念2、配置3、实操二、缓存服务器1、概念2、实操三、主从服务器和同步1.概念2、实操-搭建主从关系2.1、编辑备虚拟机2.2、编辑主虚拟机&#xff1a;2.3、验证3、实操-同步四、分离解析一、DNS域名解析-反向解析 1、概念 反向解析:根据IP…

centos7更改为启动桌面或命令行模式

进入cenos7的命令行模式 终端输入“init 3”回车进入命令行模式 登录成功后 # systemctl get-default //获取当前系统启动模式 查看配置文件 # cat /etc/inittab 通过以上显示&#xff0c;目前系统为命令行模式 更改模式命令&#xff1a;systemctl set-default graphical.targe…