开源可靠UDP协议汇总分析

本文介绍了在一个项目中遇到的使用UDP协议传输高带宽视频数据的问题,包括数据乱序、不可达和分片等挑战。作者探讨了为何不直接使用TCP,并分享了在尝试自建可靠UDP协议失败后,最终选择ENet库作为解决方案的原因。ENet是一个轻量级的库,支持可靠和非可靠模式,适合在嵌入式和Windows平台间进行跨平台通信。

项目需求:使用UDP协议,将视频数据(约30MBytes/Sec) ,尽可能正确的传递给与设备直连的PC.错误的数据直接丢弃.

老大给出的信息:UDP不会乱序,因为没有人会特意打乱数据,既然给网卡送数据是顺序送的,当然应该是顺序的.
实测的结果 :UDP的数据包一定是乱的.
因为这就是UDP的特征之一. 哲学一点说,就是如果不维护某一个特征,则必然不会出现.
按照的解释, 对一个混乱的系统,不做规范和梳理,系统必然会朝着熵增加的趋势,也就是越来越混乱的方向发展的.

UDP另一个的让人崩溃的特点,除了乱序之外,是数据的不可到达性,即不保证所有的数据都会送达.
还有一个特点就是 : UDP的分片, 一般都是限制在1400(最大1476,但是为了保险起见,1400是一个经验值).
所以,对所有的数据,都需要手动分片;在接收端,就要对应的组装起来.

这个时候,就有一个疑问了,既然使用UDP,又需要做分片,又需要确保正确性,那么为何不直接使用TCP呢?
这是个好问题.
答案就是,项目一定要求使用UDP.
一个好处是,服务器在不知道客户端存在或者是否已经启动的情况下,就可以向某个地址一直发送数据.这点TCP是无法做到的.
顺便提一下, UDP的理论速度略大于TCP,但是这点提升,对于复杂的实现逻辑来说,我宁愿不要…

在尝试自己造轮子,做了半个月的尝试,使用UDP协议做一个可靠的传输协议之后,现实毒打了我.
那么问题来了,为什么不使用已经存在的类似的库来做呢?
这是因为:我需要在嵌入式端和PC的VS studio端分别做Server和Client.一般的库,在Windows下的C++可能没问题, 但是没有对应的嵌入式代码,或者一些Windows特有的h文件,导致无法移植.而且有些库的体积巨大,学习成本高昂,而且更致命的是,也不确定花了时间之后,这个库就能够正常运作. 项目时间不允许这么做.

不过最终还是老老实实的分析了几个知名的库,将优缺点分析清楚. 最终采用的是ENet.
下面将研究的结果分享出来.

名称地址主要语言轻量?特点/优点
RakNethttps://github.com/facebookarchive/RakNetC C++ HTML较重
UDThttps://sourceforge.net/projects/udt/C++较轻
asio2https://github.com/zhllxt/asio2C++ C较轻只有h文件 方便移植
KCPhttps://github.com/skywind3000/kcpC C++很轻只有4个主要文件,主要用来改善TCP和UDP的延迟问题
ENET 可靠UDPhttp://enet.bespin.org/index.htmlC++较轻可靠和非可靠可选.都是peer的概念,没有明确的Server和Client

你也可以从这里下载源代码
https://github.com/lsalzman/enet

这是一个在油管上非常好的例子,你可以跟着他的前2章来实现一个简单的可通信的Server和Client
Basic ENet Tutorial Series - Server/Client Setup (1/5)
他们的官网给出了代码
https://usergames.net/tutorials/enet/part1
也就是这样,可以直接复制来参考.


/* ./server/main.c */

#include <stdio.h>
#include <enet/enet.h>

int main (int argc, char argv)
{
if (enet_initialize () != 0)
{
fprintf (stderr, “An error occurred while initializing ENet.\n”);
return EXIT_FAILURE;
}
atexit (enet_deinitialize);

ENetEvent event;
ENetAddress address;
ENetHost* server;

/* Bind the server to the default localhost. /
/ A specific host address can be specified by /
/ enet_address_set_host (& address, “x.x.x.x”); /
address.host = ENET_HOST_ANY; // This allows
/ Bind the server to port 7777. */
address.port = 7777;

server = enet_host_create (&address /* the address to bind the server host to /,
32 / allow up to 32 clients and/or outgoing connections /,
1 / allow up to 1 channel to be used, 0. /,
0 / assume any amount of incoming bandwidth /,
0 / assume any amount of outgoing bandwidth */);

if (server == NULL)
{
printf(“An error occurred while trying to create an ENet server host.”);
return 1;
}

// gameloop
while(true)
{
ENetEvent event;
/* Wait up to 1000 milliseconds for an event. */
while (enet_host_service (server, & event, 1000) > 0)
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
printf (“A new client connected from %x:%u.\n”,
event.peer -> address.host,
event.peer -> address.port);
break;

		<span class="token keyword">case</span> ENET_EVENT_TYPE_RECEIVE<span class="token operator">:</span>
			<span class="token function">printf</span> <span class="token punctuation">(</span><span class="token string">"A packet of length %u containing %s was received from %s on channel %u.\n"</span><span class="token punctuation">,</span>
				event<span class="token punctuation">.</span>packet <span class="token operator">-&gt;</span> dataLength<span class="token punctuation">,</span>
				event<span class="token punctuation">.</span>packet <span class="token operator">-&gt;</span> data<span class="token punctuation">,</span>
				event<span class="token punctuation">.</span>peer <span class="token operator">-&gt;</span> data<span class="token punctuation">,</span>
				event<span class="token punctuation">.</span>channelID<span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token comment">/* Clean up the packet now that we're done using it. */</span>
				<span class="token function">enet_packet_destroy</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>packet<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token keyword">break</span><span class="token punctuation">;</span>

		<span class="token keyword">case</span> ENET_EVENT_TYPE_DISCONNECT<span class="token operator">:</span>
			<span class="token function">printf</span> <span class="token punctuation">(</span><span class="token string">"%s disconnected.\n"</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>peer <span class="token operator">-&gt;</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token comment">/* Reset the peer's client information. */</span>
			event<span class="token punctuation">.</span>peer <span class="token operator">-&gt;</span> data <span class="token operator">=</span> <span class="token constant">NULL</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

enet_host_destroy(server);

return 0;
}

一些参考:

何老师给我的例子
https://blog.youkuaiyun.com/yuanchunsi/article/details/70244338

https://usergames.net/tutorials/enet/part1
API
http://enet.bespin.org/enet_8h.html
教程
http://enet.bespin.org/Tutorial.html
ENet教程翻译
https://www.cnblogs.com/allen8807/archive/2010/12/10/1900473.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值