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);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值