您的位置:首页 > 理论基础 > 计算机网络

解决UDP丢包问题的经验

2016-01-21 11:31 721 查看
月初参与了一个关于图像网络传输的小项目,图像采集端用的FPGA,每秒采集接近20MB数据,将大块的数据分割成一个个包,每个包加上图像编号和包的序号等信息,使用UDP协议发送出去。我的任务是写一个接收端(Windows下),将一个个包拼凑起来,并将二进制的图像数据转换成可视的RGB图像。

这是我第一次用UDP协议做有用的事,书本上阐述的UDP的丢包特性终于成了实实在在的问题摆在我面前。我写的第一个版本的程序,20M的数据丢失了近一半。在解决问题的路上,这篇文章UDP丢包原因给了我很大帮助。

我首先尝试增大接受缓冲区的大小,马上就有效果了,丢包率降低到10%以下。第二个改进是使用多线程。在我的单线程的版本中,当一整幅图像接收完毕后便将数据写入到文件中,这个操作很耗费时间,当有新的数据过来时,程序依然在执行写入操作,来不及接受新过来的数据,因此而造成丢包。我开辟了一个新的线程负责将数据写入到文件,原来的线程只负责接收数据。当接收数据的线程收到一副图像的最后一个包时,马上置位相应变量,另一个线程不断检查该变量,条件满足时将数据写入到文件。其实这种轮询的方式也很耗费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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  udp 网络