//server代码:
#include<>
#include<>
#include<sys/>
#include<sys/>
#include<>
#include<arpa/>
#include<netinet/>
#include<>
//打印输入提示选项
static void* usage(const char* port)
{
printf("usage: %s [local_ip] [local_port]\n",port);
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}
//创建套接字
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
//将套接字与ip地址和端口号进行绑定
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(2);
}
char buf[1024];
struct sockaddr_in client;
socklen_t len = sizeof(client);
char* msg = "Have a goog day";
while(1)
{
//读取数据
int r = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
if(r<0)
{
perror("recvfrom");
exit(3);
}
else
{
buf[r] = 0;
printf("[%s : %d]# %s\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port),buf);
//回送数据
if(sendto(sock,msg,strlen(msg),0,(struct sockaddr*)&client,len)<0)
{
perror("sendto");
exit(4);
}
break;
}
}
return 0;
}
//client代码:
#include <>
#include <sys/>
#include <sys/>
#include <netinet/>
#include <>
#include <>
static void usage(const char* proc)
{
printf("Usage: %s [locaal_ip] [local_port]\n", proc);
exit(1);
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);//IPV4 SOCK_DGRAM 数据报套接字(UDP协议)
if(sock < 0)
{
perror("socket\n");
return 2;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port=htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(server_addr);
char buf[1024];
char* msg = "hello world";
while(1)
{
if (sendto(sock, msg, strlen(msg), 0, (struct sockaddr*)&server_addr, len) < 0 )
{
perror("send:");
exit(3);
}
struct sockaddr_in tmp;
len = sizeof(tmp);
int ret = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&tmp ,&len);
if(ret > 0)
{
buf[ret] = 0;
printf("server echo#:%s\n",buf);
break;
}
}
close(sock);
return 0;
}
运行结果:
UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
实现确认机制、重传机制、窗口确认机制。
如果你不利用Linux协议栈以及上层socket机制,自己通过抓包和发包的方式去实现可靠性传输,那么必须实现如下功能:
发送:包的分片、包确认、包的重发
接收:包的调序、包的序号确认
目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。
(1)RUDP
RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。
(2)RTP
实时传输协议(RTP)为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么 RTP 可以使用该组播表传输数据到多个目的地。
RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于底层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。
(3)UDT
基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
UDT原理分析:主要通过分析源码来弄清楚如何利用udp实现数据的可靠性传输,主要按照协议格式、关键数据结构等展开。
UDT应用层协议
UDT并不是在瓶劲带宽相对较小的和大量多元短文档流的情况下用来取代TCP的。
UDT是双工的,每个UDT实体有两个部分:发送和接收。
发送者根据流量控制和速率控制来发送(和重传)应用程式数据。
接收者接收数据包和控制包,并根据接收到的包发送控制包。发送和接收程式共享同一个UDP端口来发送和接收。 接收者也负责触发和处理任何的控制事件,包括拥塞控制和可靠性控制和他们的相对机制,例如RTT估计、带宽估计、应答和重传。
UDT总是试着将应用层数据打包成固定的大小,除非数据不够这么大。和TCP相似的是,这个固定的包大小叫做MSS(最大包大小)。由于期望UDT用来传输大块数据流,我们假定只有很小的一部分不规则的大小的包在UDT session中。MSS能够通过应用程式来安装,MTU是其最优值(包括任何包头)。
UDT拥塞控制算法将速率控制和窗口(流量控制)合并起来,前者调整包的发送周期,后者限制最大的位被应答的包。在速率控制中使用的参数通过带宽估计技术来更新,他继承来自基于接收的包方法。同时,速率控制周期是估计RTT的常量,流控制参数依赖于对方的数据到达速度,另外接收端释放的缓冲区的大小。
UDT有两种包:数据包和控制包。他们通过包头的第一位来区分(标志位)。如果是0,表示是数据包,1表示是控制包。
定时器:
UDT在接收端使用4个定时器来触发不同的周期事件,包括速率控制、应答、丢失报告(negative应答)和重传/连接维护。
UDT中的定时器使用系统时间作为源。UDT接收端主动查询系统时间来检查一个定时器是否过期。对于某个定时器T来说,其拥有周期TP,将定变量t用来记录最近T被设置或复位的时间。如果T在系统时间t0(t= t0)被复位,那么任何t1(t1-t>=TP)是T过期的条件。
四个定时器是:RC定时器、ACK定时器、NAK定时器、EXP定时器。他们的周期分别是:RCTP、ATP、NTP、ETP。
(1)RC定时器用来触发周期性的速率控制。
(2)ACK定时器用来触发周期性的有选择的应答(应答包)。RCTP和ATP是常量值,值为:RCTP=ATP=0.01秒。
(3)NAK被用来触发negative应答(NAK包)。重传定时器被用来触发一个数据包的重传和维护连接状态。他们周期依赖于对于RTT的估计。
(4)ETP值也依赖于连续EXP时间溢出的次数。推荐的RTT初始值是0.1秒,而NTP和ETP的初始值是:NTP=3*RTT,ETP=3*RTT+ATP。
在每次bounded
UDP接收操作(如果收到一个UDP包,一些额外的必须的数据处理时间)时查询系统时间来检查四个定时器是否已经过期。推荐的周期粒度是微秒。UDP接收时间溢出值是实现的一个选择,这依赖于循环查询的负担和事件周期精确度之间的权衡。
速率控制事件更新包发送周期,UDT发送端使用STP来安排数据包的发送。假定一个在时间t0被发送,那么下一次包发送时间是(t0+ STP)。换句话说,如果前面的包发送花费了t’时间,发送端将等待(STP-t’)来发送下一个数据包(如果STP-t’ <0,就不需要等待了)。这个等待间隔需要一个高精确度的实现,推荐使用CPU时钟周期粒度。