首先说明下问题描述,就是当我们在做数据转发时,一般的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
#include
#include
using Poco::Thread;
using std::vector;
bool g_stopThrad = false;
#pragma comment(lib,"ws2_32.lib")
#define DEFAULTPORT 33333
vector 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了..所以基本上符合预期了.当然论证不足或者有疏漏出请大家批评指针,一起进步
//参考链接
网络参考链接,还是比较中肯