Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发

15UDP 数据收发实战

① 再论UDP协议

  • UDP是无连接的(不可靠的,无应答消息,数据包无序号标识)
  • UDP是面向数据包的,对应用层数据既不合并也不拆分(保留数据包边界,不粘包)
  • UDP没有拥塞控制,网络出现的拥塞不会使源主机的发送速率降低
  • UDP支持一对一、一对多、多对一和多对多的交互通信
  • UDP消息头开销小,只有8个字节(TCP消息头共20个字节)
  • UDP相比较TCP更高效,牺牲效率

        

UDPIP的区别

  • UDP是建立于IP之上的数据传输协议
    • IP负责将UDP数据包从源主机传输到目标主机
    • UDP则将应用层数据投递到目标socket(端口号)socket和端口号通过bind函数绑定
  • UDP几乎完整“继承”了IP传输的特性(不能缺少传输层,UDP应运而生)
    • 通信两端无交互,无流控,无超时重发,不具备可靠性
    • ····应用层(应用数据) ==> 传输层(TCP(深度定制)、UDP(轻度定制)) ==> IP层(IP)

UDP数据收发

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第1张图片

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第2张图片

 UDP编程模式

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第3张图片

UDP数据收发

Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第4张图片

⑥ 编程实验:UDP数据收发

client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    struct sockaddr_in remote = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;

    sock = socket(PF_INET, SOCK_DGRAM, 0);

    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9999);

    if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("udp bind error\n");
        return -1;
    }

    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("127.0.0.1");
    remote.sin_port = htons(8888);

    while( 1 )
    {
        printf("Input: ");

        scanf("%s", input);

        len = sizeof(remote);

        sendto(sock, input, strlen(input), 0, (struct sockaddr*)&remote, len);

        r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        if( r > 0 )
        {
            buf[r] = 0;
            
            printf("Receive: %s\n", buf);
        }
        else
        {
            break;
        }
    }

    close(sock);

    return 0;
}

server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in remote = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_DGRAM, 0);

    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("udp server bind error\n");
        return -1;
    }

    printf("udp server start success\n");

    while( 1 )
    {
        len = sizeof(remote);

        r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        buf[r] = 0;

        printf("r = %d\n", r);
        printf("buf = %s\n", buf);
        printf("remote ip = %s\n", inet_ntoa(remote.sin_addr));
        printf("remote port = %d\n", ntohs(remote.sin_port));

        sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
    }
    
    close(server);

    return 0;
}

Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第5张图片

16深入UDP 数据收发(上)

① 问题:如何进行一对多的UDP数据发送?

UDP通信中的广播

  • 广播是向同一网络中的所有主机传输数据的方法
  • 广播类型
    • 直接广播:IP地址中除网络地址外,其余主机地址均设置为1eg192.168.1.255【网络地址  主机地址】
    • 本地广播:无需知道网络地址,使用255.255.255.255作为IP地址使用
  • 区别:
    • 本地广播数据不经过路由器寻址,直接发送到本地主机

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第6张图片

③ 本地广播应用案例:DHCP

  • DHCP(动态主机配置协议)是一个局域网的网络协议(基于UDP协议
    • 本地主机可自动获得服务器分配的IP地址和子网掩码
  • DHCP采用 客户端/服务器 模型, 地址的动态分配由网络主机驱动
  • 工作方式:
    • DHCP服务器接收到来自网络主机的地址申请时, 会向网络主机发送相关的地址配置信息,以实现网络主机地址信息的动态配置

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第7张图片

④ 预备工作 -> socket属性设置(option)

  • socket的本质是对本机网络资源的一种标识
  • socket本身有各种属性(不同的连接,属性可能不同)
  • 通过 setsockopt() /getsockopt() 可存取指定socket的属性值
  • socket属性的改变可造成socket数据收发行为的改变

⑤ TCP 编程中涉及的用法

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第8张图片

⑥ setsockopt() /getsockopt()属性存取函数

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第9张图片

  •         sock:将要被设置或者获取选项的套接字。
    •         level:选项所在的协议层。
      •         optname:需要访问的选项名。
        •         optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
          •         optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。

 ⑦ UDP 数据广播

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第10张图片

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第11张图片

⑦ 实验

Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第12张图片

client.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    struct sockaddr_in remote = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;

    sock = socket(PF_INET, SOCK_DGRAM, 0);

    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9000);

    if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("udp bind error\n");
        return -1;
    }

    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("127.0.0.1");
    remote.sin_port = htons(8888);

    while( 1 )
    {
        r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        if( r > 0 )
        {
            buf[r] = 0;

            printf("Receive: %s\n", buf);
        }
        else
        {
            break;
        }
    }

    close(sock);

    return 0;
}

server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in remote = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = "D.T.Software";
    int r = 0;
    int brd = 1;

    server = socket(PF_INET, SOCK_DGRAM, 0);

    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("udp server bind error\n");
        return -1;
    }

    printf("udp server start success\n");

    remote.sin_family = AF_INET;
    // remote.sin_addr.s_addr = inet_addr("192.168.3.255");
    remote.sin_addr.s_addr = 0xFFFFFFFF;
    remote.sin_port = htons(9000);

    // brd = 0;

    setsockopt(server, SOL_SOCKET, SO_BROADCAST, &brd, sizeof(brd));

    while( 1 )
    {
        len = sizeof(remote);

        r = strlen(buf);
        
        sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);

        sleep(1);
    }
    
    close(server);

    return 0;
}

17深入UDP 数据收发(下)

① 问题:UDP是否还有其他一对多的数据发送方式?

UDP通信中的多播

  • 多播是特定组中的所有主机传输数据的方法,多播也称之为组播
  • 多播数据传输的特点:
    • 多播发送者针对特定的多播组,只发送1次数据,组内主机均可收到数据
    • 主机加入特定组,即可接收该组中的多播数据
    • 多播组可在IP地址范围内任意增加

③ 关键问题:如何收发多播数据?

  • 多播组是一个D类地址224.0.0.0 - 239.255.255.255
  • 加入多播组可理解为UDP网络程序进行的申请
    • 如:申请接收发往239.234.111.222的多播数据
    • 即:设置属性(IP PROTO IP, IP_ADD_MEMBERSHIP
  • 发送多播数据的方式,与发送普通UDP数据的方式相同
    • 预备操作:设置属性, 如(IPPROTO_IP,IP_MULTICAST_TTL

④ 注意事项

  • 加入同一个多播组的主机不一定在同一个网络中(可能需要路由器转发)
  • 因此,必须设置多播数据的最多转发次数TTL)(避免资源浪费)
    • TTL (即:Time To Live)是决定数据传递距离的主要因素
    • TTL 用整数表示,并且每经过1个路由器就减少 1
    • TTL 变为0时,数据无法继续被传递,只能销毁

⑤ 多播程序设计:发送端

  • IP_MULTICAST_TTL
    • 用于设置多播数据的最远传播距离,默认:1
  • IP_MULTICAST_IF
    • 用于设置多播数据从哪一个网络接口(网卡)发送出去,默认:0.0.0.0(让操作系统决定用哪个网卡,仅限单网卡
  • IP_MULTICAST_LOOP
    • 用于设置多播数据是否发送回本机,默认:1

        Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第13张图片

⑥ 多播程序设计:接收端

IP_ADD_MEMBERSHIP

  • 用于申请加入多播组,参数为:多播组地止 和 本机地址

Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第14张图片

单网卡中可以使用INADDR_ANY(0.0.0.0),多网卡时,需要手动指定网卡。

⑦ 可以加入多播组,那么可以退出多播组吗?

Linux 网络开发必学课程(七)UDP 数据收发实战、深入UDP 数据收发_第15张图片

⑧ 实验

mul_rx.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    struct sockaddr_in remote = {0};
    struct ip_mreq group = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;

    sock = socket(PF_INET, SOCK_DGRAM, 0);

    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   // local host
    addr.sin_port = htons(9000);

    if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("udp bind error\n");
        return -1;
    }

    group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
    group.imr_interface.s_addr = htonl(INADDR_ANY);   // local host

    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

    while( 1 )
    {
        len = sizeof(remote);

        r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        if( r > 0 )
        {
            buf[r] = 0;

            printf("Receive: %s\n", buf);
        }
        else
        {
            break;
        }
    }

    close(sock);

    return 0;
}

仅限单网卡

mul_tx.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in remote = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = "D.T.Software";
    int r = 0;
    int ttl = 0;
    int loop = 0;
    struct in_addr addr = {0};

    server = socket(PF_INET, SOCK_DGRAM, 0);

    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);  // local host
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("udp server bind error\n");
        return -1;
    }

    printf("udp server start success\n");

    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("224.1.1.168");
    remote.sin_port = htons(9000);

    ttl = 0;

    len = sizeof(ttl);

    getsockopt(server, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, &len);

    printf("default ttl = %d\n", ttl);

    ttl = 32;

    len = sizeof(ttl);

    setsockopt(server, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, len);

    printf("current ttl = %d\n", ttl);

    loop = 0;

    len = sizeof(loop);

    getsockopt(server, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &len);

    printf("default loop = %d\n", loop);

    addr.s_addr = inet_addr("192.168.3.221");

    len = sizeof(addr);

    setsockopt(server, IPPROTO_IP, IP_MULTICAST_IF, &addr, len);

    printf("current if = %s\n", inet_ntoa(addr));

    while( 1 )
    {
        len = sizeof(remote);

        r = strlen(buf);
        
        sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);

        sleep(1);
    }
    
    close(server);

    return 0;
}

⑨ 小结

  • 单播:一对一数据发送,即:指定目标主机发送数据
  • 广播
    • 本地广播:本地局域网广播数据,所有主机均可接收数据
    • 直接广播:指定网络广播数据,目标网络中的主机均可接收数据
  • 多播(组播)
    • 向指定的多播地址发送数据,订阅该地址的主机均可接收数据

你可能感兴趣的:(Linux,网络开发必学课程,udp,网络,tcp/ip,UDP,数据收发实战)