UDP发包的延时问题

来源:互联网 发布:手机平面设计图纸软件 编辑:程序博客网 时间:2024/06/06 10:53

首先说明下问题描述,就是当我们在做数据转发时,一般的1对N发送数据,应为发送的时候,我们不可能确定得了这个数据是否是有效的,一般是通过心跳包来检测,但是心跳包不是实时的,这个时候如果在N个地址中出现了无效地址,就会造成延时….项目中好像不是这样的,客户端掉线后,没问题,但是客户端掉线后连接上服务器,就会出现卡顿的情况,目前还在查找中….但是想把自己这3天的学习与资料查找做一个总结.不足之处还望不吝批评指正。

测试demo的思路如下,客户端 - > 服务端 - > N个接收端. 然后N个服务端中出现部分掉线,如果客户端发送Msg的时间与最后接收端的时间相差超过10ms我们就认为造成了延时,并给出解决方案.
通过线程模拟接收端,我这里是开了30个+10个会掉线的线程
接收线程,

bool g_stop = false;Event g_event(false);void RecvThread(void* pData){    int nPort = *(int*)pData;    DatagramSocket recvSocket;    SocketAddress addr("172.16.11.228", nPort);    recvSocket.bind(addr);    recvSocket.setBlocking(false);    Timespan span(1000);    char szMsg[512] = { 0 };    while (!g_stop)    {        try        {            if (recvSocket.poll(span, Poco::Net::Socket::SELECT_READ))            {                SocketAddress addrfrom;                int nlen = recvSocket.receiveFrom(szMsg, 512, addrfrom);                if (nlen > 0)                {                    szMsg[nlen] = 0;                    DateTime dt;                    printf("ThreadID:%05d,RecvTime:%02d:%02d:%03d===Msg:%s\n", Thread::currentTid(), dt.minute(), dt.second(), dt.millisecond(), szMsg);                }            }        }        catch (Poco::Exception& e)        {            printf("Exception:%s\n", e.displayText());        }    }    recvSocket.close();}

发送线程

void SendThread(void* pdata){    DatagramSocket cSocket;    SocketAddress addr("172.16.11.228", 33333);    int nIndex = 0;    cSocket.setSendTimeout(Timespan(1000));    cSocket.setReceiveTimeout(Timespan(1000));    try    {        cSocket.connect(addr);    }    catch (Poco::Exception& e)    {        printf("err:%d,%s\n", Thread::currentTid(), e.displayText().c_str());        return;    }    char szMsg[512] = { 0 };    g_event.wait();    while (nIndex < 1000)    {        try        {            DateTime dt;            int nlen = sprintf_s(szMsg, "Recv ThreadID:%05d Time:%02d:%02d:%03d", Thread::currentTid(),dt.minute(),dt.second(),dt.millisecond() );            nlen = cSocket.sendTo(szMsg, 256, addr);            SocketAddress recvaddr;        }        catch (Poco::Exception& e)        {            printf("ThreadID:%05d,SendRecvErr:%s\n", Thread::currentTid(), e.displayText().c_str());            ++nIndex;            continue;        }        Sleep(4);        ++nIndex;    }    cSocket.close();}

main函数

int _tmain(int argc, _TCHAR* argv[]){    g_event.reset();    Thread sendthread;    sendthread.start(SendThread, NULL);    Thread arrThread[30];    for (int i = 0; i < 30;++i )    {        int nport = 60000 + i;        arrThread[i].start(RecvThread, &nport);        Sleep(10);    }    g_event.set();    getchar();    g_stop = true;    getchar();    return 0;}

上述代码实现了..发送线程间隔4ms发送一个数据包,另外有30个线程负责接收服务器回显的数据包
发送线程,记录发送时间,然后与30个线程接收的数据进行对比,上述为了简单点,使用了poco开发库
服务端
之前使用了BSD套接字,也就是poco 封装底层的实现,但是没办法解决UDP延时的问题,再windows下不能设置socket选项,必须使用WSAIOCTL来这是,就自己封装了…看代码

#include "stdafx.h"#include "Poco/Net/Net.h"#include "Poco/Net/DatagramSocket.h"#include "Poco/Foundation.h"#include "Poco/Timespan.h"#include "Poco/DateTime.h"#include "Poco/Thread.h"#include <WinSock2.h>#include <vector>#include <Windows.h>using Poco::Thread;using std::vector;bool g_stopThrad = false;#pragma comment(lib,"ws2_32.lib")#define DEFAULTPORT 33333vector<SOCKADDR_IN> vecaddr;#define IOC_VENDOR 0x18000000 #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) void WorkThread(void* pdata){    int nret = -1;    WSADATA wsadata;    SOCKET  serversocket = INVALID_SOCKET;    serversocket = socket(AF_INET, SOCK_DGRAM, 0);    SOCKADDR_IN addr;    addr.sin_family = AF_INET;    addr.sin_addr.S_un.S_addr = INADDR_ANY;    nret = bind(serversocket,(SOCKADDR*)&addr, sizeof(addr));    ULONG block = 1;    ioctlsocket(serversocket, FIONBIO, &block);    DWORD dwBytesReturned = 0;    BOOL bNewBehavior = FALSE;    DWORD dwstatus = 0;    ***dwstatus = WSAIoctl(serversocket, SIO_UDP_CONNRESET,        &bNewBehavior, sizeof(bNewBehavior),        NULL, 0, &dwBytesReturned,        NULL, NULL);***            fd_set fdread;    TIMEVAL tm;    FD_ZERO(&fdread);    FD_SET(serversocket, &fdread);    tm.tv_sec = 0;    tm.tv_usec = 500000;    char szMsg[2048] = { 0 };    while (!g_stopThrad)    {        nret = select(0,&fdread, NULL, NULL, &tm);        if (nret <= 0 )        {            printf("err:%d\n", WSAGetLastError());            FD_ZERO(&fdread);            FD_SET(serversocket, &fdread);            continue;        }        nret = recvfrom(serversocket, szMsg, 2048, 0, NULL, NULL);        if (nret > 0 )        {            szMsg[nret] = 0;            Poco::DateTime tm;            printf("Time:%d:%d:%d---info:%s\n", tm.minute(), tm.second(), tm.millisecond(), szMsg);            for each (SOCKADDR_IN addr in vecaddr)            {                sendto(serversocket, szMsg, nret,0, (SOCKADDR*)&addr, sizeof(SOCKADDR));            }        }    }        closesocket(serversocket);}

服务端main函数

列表内容====int _tmain(int argc, _TCHAR* argv[]){    for (int i = 0; i < 40; ++i)    {        int nPort = 60000 + i;        SOCKADDR_IN addr;        addr.sin_family = AF_INET;        addr.sin_port = htons(nPort);        addr.sin_addr.S_un.S_addr = inet_addr("172.16.11.228");        vecaddr.push_back(addr);    }    Thread workThread;    workThread.start(WorkThread);    getchar();    g_stopThrad = true;    getchar();    return 0;}

这是正确的解决方案,错误的解决方案是不使用上面加粗和斜线的那一句,原理是windows下当我们向一个不存在的地址sendto数据后,会有一个icmp包的回显,造成select时间为可读,进而会造成select阻塞或者超时,也就引起了…udp接收线程与发送线程的时间相差很大的问题…具体理论链接参考博客尾部
直接上结果…
不设置Socket的情况,如下图…
延时图片
我们可以看到发送的时间与接收到的时间相差了40ms左右,这个再实时音频传输中是不被允许的,因为标准的MP3音频是26ms…更别说其他的自定义的了..所以会有卡顿的现象出现.
设置socket的情况,如下图
这里写图片描述
基本是没有延时的,同样的测试步骤,我是这样测试的,其实接收线程就相当于远端服务器,我另外在开一个差不多类似的客户端代码,但是只是接收100条Msg就退出,相当于模拟了无效地址..测试了10几次,最大的延时是2ms内,在我们允许的范围内,但是第一张图,基本上2-3次就会出现大量的延时..根据网络上的资料,udp socket一次收发耗时在2us左右,40*2us = 800us 接近1ms了..所以基本上符合预期了.当然论证不足或者有疏漏出请大家批评指针,一起进步

//参考链接
网络参考链接,还是比较中肯

原创粉丝点击