《Linux网络开发必学教程》17_深入 UDP 数据收发 (下)

问题: UDP 是否还有其他一对多的数据发送方式?
UDP 通讯中的多播
多播是向特定组中的所有主机传输数据的方法,多播也称之为组播
多播数据传输的特点:
  • 多播发送者针对特定的多播组,只发送 1 次数据,组内主机均可接收到数据
  • 主机加入特定组,即可接收该组中的多播数据
  • 多播组可在 IP 地址范围内任意添加
关键问题:如何收发多播数据
多播组是一个 D 类地址 (224.0.0.0 - 239.255.255.255)
"加入多播组"可理解为 UDP 网络程序进行的申请
  • 如:申请接收发往 239.234.111.222 的多播数据
  • 即:设置属性 (IPPROTO_IP, IP_ADD_MEMBERSHP
发送多播数据的方式,与发送普通 UDP 数据的方式相同
  • 预备操作:设置属性,如:(IPPROTO_IP, IP_MULTICAST_TTL
注意事项
加入同一个多播组的主机不一定在同一个网络中
因此,必须设置多播数据的最多转发次数(TTL)
  • TTL(即:Time To Live) 是决定数据传输距离的主要因素
  • TTL 用整数表示,并且每经过 1 个路由就减少 1
  • 当 TTL 变为 0 时,数据无法继续传递,只能销毁
多播程序设计:发送端
IP_MULTCAST_TTL: 用于设置多播数据的”最远传输距离“,默认 1
IP_MULTICAST_IF: 用于设置多播数据从哪一个网络接口(网卡)发送出去,默认:0.0.0.0
默认 0.0.0.0 情况下操作系统会自主选择使用哪个网络接口,但在[多网卡主机]的[实际工程应用]中,最好手工指定!!

IP_MULTCAST_LOOP: 用于设置多播数据是否发送回本机,默认1 (1,发送回本机)
remote.sin_family = AF_INET; remote.sin_addr.s_addr = inet_addr("224.1.1.168"); remote.sin_port = htons(8888); setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); do { len = sizeof(remote); r = sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len); sleep(1); }while(r > 0);

多播程序设计:接收端
IP_ADD_MEMBRESHIP: 用于申请加入多播组,参数为:多播组和本机地址
struct ip_mreq { // group address struct in_addr inmr_multiaddr; // local host address struct in_addr imr_interface }


struct ip_mreq group = {0}; group.imr_multiaddr.s_addr = inet_addr("224.1.1.168"); group.imr_interface.s_addr = htonl(INADDR_ANY); // 监听所有网卡setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)); do { len = sizeof(remote); r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len); buf[r] = 0; printf("r = %d\n", r); printf("msg = %s\n", buf); }while (r > 0)

退出多播组 【《Linux网络开发必学教程》17_深入 UDP 数据收发 (下)】quit = setsockopt(sock, IP_PROTO_IP, IP_DROP_MEMBERSHIP, &group, sizeof(group));
if (quit == 0) { break; }

编程实验:UDP 多播程序设计
mul_tx.c
#include #include #include #include #include #include #include int main() { int sock = 0; struct sockaddr_in remote = {0}; socklen_t len = 0; char buf[128] = "D.T.Software"; struct in_addr addr = {0}; int ttl = 0; int loop = 0; sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock == -1) { printf("socket error\n"); return -1; }remote.sin_family = AF_INET; remote.sin_addr.s_addr = inet_addr("224.1.1.168"); remote.sin_port = htons(8888); //------------------------- ttl = 0; len = sizeof(ttl); getsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, &len); // 可以不设置,ttl 默认为 1 printf("default ttl = %d\n", ttl); ttl = 32; len = sizeof(ttl); setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, len); printf("current ttl = %d\n", ttl); loop = 0; len = sizeof(loop); getsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &len); // 可以不设置,ttl 默认为 1 ,即本机也会收到 printf("default loop = %d\n", loop); // addr.s_addr = inet_addr("192.168.3.221"); // 具体指定使用哪一块网卡进行数据广播 addr.s_addr = htonl(INADDR_ANY); // 交由操作系统进行选择 len = sizeof(addr); setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, len); // 可以不设置,默认为 INADDR_ANY 即 "0.0.0.0", 交由操作系统选择使用哪个网卡 printf("current if = %s\n", inet_ntoa(addr)); // 当主机有多个网卡,在实际工程使用时,最好手工指定使用那块网卡广播数据 //-------------------------while (1) { len = sizeof(remote); sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len); sleep(1); }close(sock); return 0; }

mul_rx.c
#include #include #include #include #include #include #include int main() { int server = 0; struct sockaddr_in saddr = {0}; struct sockaddr_in remote = {0}; struct ip_mreq group = {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"); 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 sucess\n"); group.imr_multiaddr.s_addr = inet_addr("224.1.1.168"); group.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)); while (1) { len = sizeof(remote); r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len); if (r > 0) { buf[r] = 0; printf("Recvive: %s\n", buf); } else { break; } }close(server); return 0; }

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

    推荐阅读