项目需求:使用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.
下面将研究的结果分享出来.
名称 | 地址 | 主要语言 | 轻量? | 特点/优点 |
---|---|---|---|---|
RakNet | https://github.com/facebookarchive/RakNet | C C++ HTML | 较重 | |
UDT | https://sourceforge.net/projects/udt/ | C++ | 较轻 | |
asio2 | https://github.com/zhllxt/asio2 | C++ C | 较轻 | 只有h文件 方便移植 |
KCP | https://github.com/skywind3000/kcp | C C++ | 很轻 | 只有4个主要文件,主要用来改善TCP和UDP的延迟问题 |
ENET 可靠UDP | http://enet.bespin.org/index.html | C++ | 较轻 | 可靠和非可靠可选.都是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;
case ENET_EVENT_TYPE_RECEIVE:
printf ("A packet of length %u containing %s was received from %s on channel %u.\n",
event.packet -> dataLength,
event.packet -> data,
event.peer -> data,
event.channelID);
/* Clean up the packet now that we're done using it. */
enet_packet_destroy (event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
printf ("%s disconnected.\n", event.peer -> data);
/* Reset the peer's client information. */
event.peer -> data = NULL;
}
}
}
enet_host_destroy(server);
return 0;
}
一些参考:
何老师给我的例子
https://blog.csdn.net/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