计算机网络:UDP套接字通信

来源:互联网 发布:office for mac 激活 编辑:程序博客网 时间:2024/05/17 08:26

上篇博客我们讲了,TCP套接字通信这篇博客我们主要讲解一下UDP套接字通信。

基本概念

用户数据报协议UDP(User Datagram Protocol)在传送数据之前不要要先建立连接,远地主机的运输层在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP却是一种最有效的工作方式。

UDP的主要特点

  1. UDP是无连接的。
  2. UDP使用尽最大努力交付。
  3. UDP是面向报文的。
  4. UDP没有拥塞控制
  5. UDP支持一对一、一对多、多对一和多对多的交互通信
  6. UDP的首部开销小,只有8个字节,比TCP的20字节的首部要短

UDP的首部格式

这里写图片描述
(1)源端口:源端口号,在需要对方回信时选用,不要要时可用全0。
(2)目的端口:目的端口号。在终点交付报文时必须要使用到。
(3)长度:UDP用户数据报的长度,其最小值是8。
(4)校验和:检测UDP用户数据报在传输中是否有错,有错就丢弃。
注意:
在计算校验和时,要在UDP用户数据报之前增加12个字节的伪首部。所谓“伪首部”是因为这种伪首部并不是UDP用户数据报的真正的首部。只是在计算校验和时,临时添加在UDP用户数据报前面,得到一个临时的UDP用户数据报。校验和就是按照这个临时的UDP用户数据报来计算的。伪首部既不向下传送也不向上递交,而仅仅为了计算校验和。

UDP通信的实现方法

UDP通信和上篇的TCP通信实现原理大同小异,但是他们还是有一些差异的:
1. 调用socket函数创建socket的时候,TCP是基于字节流传输的,而UDP是基于数据包传输的,所以socket函数的第二个参数是SOCK_DGRAM。
2. UDP服务器不需要通过三次握手来建立连接,所以UDP不需要listen去进行监听。
3. 在绑定之后可以直接进行读写,不过不同于TCP,而是自己特有的函数,recvfrom和sendto。

UDP读写函数

1.数据发送函数sendto
这里写图片描述
参数:
sockfd:表示当前的socket的fd。
buf:待发送数据的缓冲区。
size:缓冲区的长度。
falgs:调用方式标志位,一般位0,改变flags,将会改变Sendto发送的方式
addr:指向目的套接字的地址。
addrlen:所指地址的长度。
2.数据接收函数recvfrom
这里写图片描述
参数和上面类似,可以参考上面的。

套接字UDP通信

server.c:

#include<stdio.h>#include<unistd.h>#include<string.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>void Usage(const char* name){    printf("Usage is %s [IP] [port]\n",name);}int main(int argc,const char* argv[]){    if(argc != 3)    {        Usage(argv[0]);        return 1;    }    //创建套接字 套接字传输类型为数据报传输 SOCK_DGRM    int sock = socket(AF_INET,SOCK_DGRAM,0);    if(sock < 0)    {        perror("socket");        return 2;    }    //绑定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");        return 3;    }    //绑定后不需要设置为监听状态,因为udp是不可靠传输,不需要建立连接    char buf[1024];    while(1)    {        //udp客户端 - 服务器之间的通信采用的是 专门的函数通信        struct sockaddr_in client;        socklen_t len = sizeof(client);        //recvfrom从客户端接受数据        ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct ockaddr*)&client,&len);        if(s < 0)        {            perror("recvfrom");            return 4;        }        else if(s == 0)        {}        else        {            buf[s] = 0;            printf("%s,%d #: %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);            //调用sendto发送数据到客户端            sendto(sock,buf,strlen(buf),0,(struct sockadd*)&client,len);        }    }    close(sock);    return 0;}

client.c:

#include<stdio.h>#include<unistd.h>#include<string.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>void Usage(const char* name){    printf("Usage is %s [IP] [port]\n",name);}int main(int argc,const char* argv[]){    if(argc != 3)    {        Usage(argv[0]);        return 1;    }    //创建套接字 套接字传输类型为数据报传输 SOCK_DGRM    int sock = socket(AF_INET,SOCK_DGRAM,0);    if(sock < 0)    {        perror("socket");        return 2;    }    //创建成功    char buf[1024];    printf("Please Enter\n");    while(1)    {        printf("client#: ");        fflush(stdout);        //从标准输入中读数据        ssize_t s = read(0,buf,sizeof(buf)-1);        if(s < 0)        {            perror("read");            return 3;        }        buf[s-1] = 0;        //给定接受方的协议地址        struct sockaddr_in peer;        peer.sin_family = AF_INET;        peer.sin_port = htons(atoi(argv[2]));        peer.sin_addr.s_addr = inet_addr(argv[1]);        //向服务器发送数据        if(sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&peer,sizeof(peer)) < 0)        {            perror("sendto");            return 4;        }        struct sockaddr_in server;        socklen_t len = sizeof(server);        //接受服务器的数据        s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&server,&len);        if(s < 0)//接受失败        {            perror("recvfrom");            return 5;        }        else if(s == 0) //接受到文件结尾,表示服务器关闭        {            printf("server quit!\n");            break;          }        else //接受到的数据放到显示器上        {            buf[s] = 0;            printf("%s ,%d #:%s\n",inet_ntoa(server.sin_addr),ntohs(server.sin_port),buf);        }    }    close(sock);    return 0;}

UDP用户空间如何保证可靠性

UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
实现确认机制、重传机制、窗口确认机制。
如果你不利用Linux协议栈以及上层socket机制,自己通过抓包和发包的方式去实现可靠性传输,那么必须实现如下功能:
发送:包的分片、包确认、包的重发
接收:包的调序、包的序号确认
目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。
【RUDP】
RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。
【RPT】
实时传输协议(RTP)为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么 RTP 可以使用该组播表传输数据到多个目的地。
RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于底层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。、
【UDT】
基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
因项目中的需要,现在详细分析一下UDT是如何通过udp实现数据的可靠传输。通过阅读源码的方式。
UDT并不是在瓶劲带宽相对较小的和大量多元短文档流的情况下用来取代TCP的。
UDT主要作为TCP的朋友,和TCP并存,UDT分配的带宽不应该超过根据MAX-MIN规则的最大最小公平共享原则。(备注,最大最小规则允许UDT在高BDP连接下分配TCP不能使用的可用带宽)。
UDT是双工的,每个UDT实体有两个部分:发送和接收。
发送者根据流量控制和速率控制来发送(和重传)应用程式数据。
接收者接收数据包和控制包,并根据接收到的包发送控制包。发送和接收程式共享同一个UDP端口来发送和接收。
接收者也负责触发和处理任何的控制事件,包括拥塞控制和可靠性控制和他们的相对机制,例如RTT估计、带宽估计、应答和重传。
UDT总是试着将应用层数据打包成固定的大小,除非数据不够这么大。和TCP相似的是,这个固定的包大小叫做MSS(最大包大小)。由于期望UDT用来传输大块数据流,我们假定只有很小的一部分不规则的大小的包在UDT session中。MSS能够通过应用程式来安装,MTU是其最优值(包括任何包头)。
UDT拥塞控制算法将速率控制和窗口(流量控制)合并起来,前者调整包的发送周期,后者限制最大的位被应答的包。在速率控制中使用的参数通过带宽估计技术来更新,他继承来自基于接收的包方法。同时,速率控制周期是估计RTT的常量,流控制参数依赖于对方的数据到达速度,另外接收端释放的缓冲区的大小。