多播和广播

单播用于两个主机之间的端对端通信,但平时开发中有这样的场景,要向一组N个主机发送相同的数据,如果基于TCP提供服务器,则需要维护N个套接字连接,即使使用UDP套接字提供服务器,也需要N次的数据发送。像这样,向大量客户端发送相同数据时,也会对服务器端和网络流量产生负面影响,可以使用多播和广播技术解决该问题。

前面几篇文章讲解了有关TCP套接字和UDP套接字通信代码及原理,今天在UDP套接字通信的基础上探讨下广播和多播相关。

一、多播

多播(Multicast)也叫组播,传输数据传输时基于UDP完成的,因此与UDP通信的服务器和客户端非常相近。区别在于UDP数据只能向单一目的地址传输,而多播数据可以同时传递到加入(注册)特定的多个主机

1. 多播传输数据特点:
  • 多播发送者针对特定的多播组织发送一次数据
  • 加入特定组的接收者都可以收到多播数据
2. 多播地址:

多播地址是D类IP地址:224.0.0.0~239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类。
局部链接多播地址:224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包
预留多播地址:224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议
管理权限多播地址:239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围

3. 多播传输原理:

多播是基于UDP套接字传输数据的基础完成,数据包格式与前面讲到的UDP数据包相同,以前的传输数据包的地址改成多播地址,向网络传递一个多播数据包时,路由器将复制该数据包并传递到多个主机,多播的传输需要借助路由器完成,正是由于这样的特性,大大节省了网络流量,减少了占用带宽,同时也减少了发送端的重复无用的工作,多播主要用于“多媒体数据的实时传输”。
要实现多播通信,要求介于多播源和接收者之间的路由器、集线器、交换机以及主机均需支持IP多播。目前,IP多播技术已得到硬件、软件厂商的广泛支持。

多播可以跨网传输,传输流程如下:

组播传输流程.jpg
4. 多播实现:

有关多播的实现需要设置UDP套接字的一些可选项,在前面的一片文章套接字(Socket)编程(三) 套接字可选项里面有提到设置方法和设置参数,有需要了解的可以点击链接查看。

IPPROTO_IP 选项名 说明 数据类型
IP_MULTICAST_TTL 生存时间(Time To Live),组播传送距离 int
IP_ADD_MEMBERSHIP 加入组播 struct ip_mreq
IP_DROP_MEMBERSHIP 离开组播 struct ip_mreq
IP_MULTICAST_IF 获取默认接口或设置接口 int
IP_MULTICAST_LOOP 组播数据回送,缺省默认回送 int

多播发送端
发送端为了实现多播的传递,必须设置TTL,TTL是Time to Live的简写,是解决“数据包传递距离”的主要因素,TTL用整数表示,并且没经过一个路由器就减一,TTL变为0时,该数据包无法再被传递,只能销毁,此值可以阻止将数据报转发到单个子网之外。
TTL的值设置过大将影响网络流量,当然设置过小也会导致数据无法传递到目的端,需要注意下。缺省情况下,发送 IP 多播数据报时其生存时间 (time-to-live, TTL) 值为 1。
使用套接字选项 IP_MULTICAST_TTL,可以将后续多播数据报的 TTL 设置为 0 到 255 之间的任何值。此功能用于控制多播的范围,这些阈值将针对具有以下初始 TTL 值的多播数据报强制实施相应约定
0 : 限定在同一主机
1 : 限定在同一子网
32 : 限定在同一站点
64 : 限定在同一地区
128 : 限定在同一洲
255 : 范围不受限制
站点和地区并未严格定义,站点可以根据实际情况再分为更小的管理单元

TTL和多播路由.jpg

接下来给出设置TTL的代码示例,重复多播固定字符串

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int createMulticastSender(char* ip, uint16_t port);

int main(int argc, const char * argv[]) {
    
    char *ip = "239.145.145.145";
    uint16_t port = 9190;
    
    if (createMulticastSender(ip, port) == 0) {
        printf("开启多播发送端失败\n");
    }
    return 0;
}

#pragma mark ---开启多播发送端
int createMulticastSender(char* ip, uint16_t port)
{
    int sock;
    struct sockaddr_in mAddr;
    
    mAddr.sin_len = sizeof(mAddr);
    mAddr.sin_family = AF_INET;
    mAddr.sin_port = htons(port);
    mAddr.sin_addr.s_addr = inet_addr(ip);
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    
    int opval = 64;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &opval, sizeof(opval)) == -1) {
        printf("设置多播的生命周期失败 code:%d description:%s\n",errno,strerror(errno));
        return 0;
    }
    /*
    //禁止组播回送
    int loop = 0;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1) {
        printf("禁止组播数据回送失败 code:%d description:%s\n",errno,strerror(errno));
    }
    */
    char *buffer = "Hello, World!";
    ssize_t buffer_len = strlen(buffer);
    while (1) {
        ssize_t sendLen = sendto(sock, buffer, buffer_len, 0, (struct sockaddr*)&mAddr, mAddr.sin_len);
        if (sendLen == buffer_len) {
            printf("成功多播 %zd 字节数据\n",sendLen);
        }else if (sendLen  == -1) {
            printf("多播失败  code:%d description:%s\n",errno,strerror(errno));
            break;
        }else {
            printf("多播数据不对 需要发送字节数为 %lu 字节,而实际发送 %zd 字节\n",sizeof(buffer),sendLen);
        }
        sleep(2);
    }
    
    printf("关闭多播发送端\n");
    close(sock);
    return 1;
}

多播接收端
将入多播也要通过设置UDP套接字的相关参数完后
下面是加入组播关闭时离开组播的代码示例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int createMulticastReceiver(char* ip, uint16_t port);

int main(int argc, const char * argv[]) {
    
    char *ip = "239.145.145.145";
    uint16_t port = 9190;
    
    if (createMulticastReceiver(ip, port) == 0) {
        printf("开启多播接收端失败\n");
    }
    return 0;
}

#pragma mark ---开启多播接收端
int createMulticastReceiver(char* ip, uint16_t port)
{
    int sock;
    struct sockaddr_in addr,peerAddr;
    memset(&peerAddr, 0, sizeof(peerAddr));
    memset(&addr, 0, sizeof(addr));
    struct ip_mreq join_adr;
    
    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("绑定多播地址失败 code:%d description:%s\n",errno,strerror(errno));
        return 0;
    }
    
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY);
    join_adr.imr_multiaddr.s_addr = inet_addr(ip);
    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &join_adr, sizeof(join_adr)) == -1) {
        printf("加入组播失败 code:%d description:%s\n",errno,strerror(errno));
        return 0;
    }
    
    printf("准备工作完成,开始接收组播\n");
    char buffer[64];
    while (1) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t recvLen = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&peerAddr, 0);
        if (recvLen > 0) {
            printf("peer IP:%s  peer Port:%d  buffer:%s\n",inet_ntoa(peerAddr.sin_addr),ntohs(peerAddr.sin_port),buffer);
            if (buffer[0] == 'C') break;
        }else {
            printf("接收多播错误 code:%d description:%s\n",errno,strerror(errno));
            break;
        }
    }
    
    printf("准备离开组播组\n");
    if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &join_adr, sizeof(join_adr)) == -1) {
        printf("离开组播失败 code:%d description:%s\n",errno,strerror(errno));
    }
    
    printf("关闭多播接收端\n");
    close(sock);
    return 1;
}

二、广播

广播的数据传输和多播相似,广播是一次性向网络内的所有主机发送数据,并且只能在局域网内传播,而不能跨网传播,广播也是基于UDP套接字传输数据实现。

1. 广播分类

根据广播的地址不同,分为直接广播(Directed Broadcast)和本地广播(Local Broadcast)。
直接广播:广播的IP地址除了网络好外,其余主机地合作位全部设置为1,例如希望向 192.168.1.32 所在网络中的所有主机发送广播数据时可以向 192.168.1.255 传输。换而言之,可以采用直接广播的方式向特定区域内的所有主机传输数据。
本地广播:本地广播的IP地址是 255.255.255.255,向该主机所在网络的所有主机发送广播数据。

2. 广播实现

有关广播的实现需要设置UDP套接字的一些可选项,在前面的一片文章套接字(Socket)编程(三) 套接字可选项里面有提到设置方法和设置参数,有需要了解的可以点击链接查看。

** SOL_SOCKET** 选项名 说明 数据类型
SO_BROADCAST 允许或禁止发送广播数据(1启用,0不启用) int

广播发送端
要实现广播的发送必须设置允许广播可选项,具体看下面示例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int createBroadcastSender(char* ip, uint16_t port);

int main(int argc, const char * argv[]) {
    
    char *ip = "255.255.255.255";
    uint16_t port = 9190;
    
    if (createBroadcastSender(ip, port) == 0) {
        printf("开启广播发送端失败\n");
    }
    return 0;
}

#pragma mark ---开启广播发送端
int createBroadcastSender(char* ip, uint16_t port)
{
    int sock;
    struct sockaddr_in bAddr;
    memset(&bAddr, 0, sizeof(bAddr));
    
    bAddr.sin_len = sizeof(bAddr);
    bAddr.sin_family = AF_INET;
    bAddr.sin_port = htons(port);
    bAddr.sin_addr.s_addr = inet_addr(ip);
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    
    int opval = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opval, sizeof(opval)) == -1) {
        printf("启用广播失败 code:%d description:%s\n",errno,strerror(errno));
        return 0;
    }
    
    char *buffer = "Hello, World!";
    ssize_t buffer_len = strlen(buffer);
    while (1) {
        ssize_t sendLen = sendto(sock, buffer, buffer_len, 0, (struct sockaddr*)&bAddr, sizeof(bAddr));
        if (sendLen == buffer_len) {
            printf("成功广播 %zd 字节数据\n",sendLen);
        }else if (sendLen  == -1) {
            printf("广播失败  code:%d description:%s\n",errno,strerror(errno));
            break;
        }else {
            printf("广播数据不对 需要发送字节数为 %lu 字节,而实际发送 %zd 字节\n",sizeof(buffer),sendLen);
        }
        sleep(5);
    }
    
    printf("关闭广播发送端\n");
    close(sock);
    return 1;
}

广播接收端

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

int createBroadcastReceiver(uint16_t port);

int main(int argc, const char * argv[]) {
    
    uint16_t port = 9190;
    if (createBroadcastReceiver(port) == 0) {
        printf("开启广播接收端失败\n");
    }
    return 0;
}

#pragma mark ---开启广播接收端
int createBroadcastReceiver(uint16_t port)
{
    int sock;
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));

    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("绑定广播地址失败 code:%d description:%s\n",errno,strerror(errno));
        return 0;
    }
    
    printf("准备工作完成,开始接收广播\n");
    char buffer[64];
    while (1) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t recvLen = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, 0);
        if (recvLen > 0) {
            printf("buffer: %s\n",buffer);
            if (buffer[0] == 'C') break;
        }else {
            printf("接收广播错误 code:%d description:%s\n",errno,strerror(errno));
            break;
        }
    }
    
    printf("关闭广播接收端\n");
    close(sock);
    return 1;
}

三、结语

关于广播和多播部分的实现很简单,都是基于UDP套接字。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容

  • 12.1 引言 在第1章中我们提到有三种IP地址:单播地址、广播地址和多播地址。本章将更详细地介绍广播和多播。 广...
    张芳涛阅读 771评论 0 4
  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 13,032评论 6 174
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,049评论 0 8
  • 引言 网络学习的核心内容就是网络协议的学习 网络协议:网络中进行数据交换而建立的规则、标准或者说是约定的集合因为不...
    _凉风_阅读 1,964评论 8 22
  • 问题描述 1. 启动问题 服务器经过一次重启,然后再次使用以下命令开启Mysql,出现错误。 2. Mysql错误...
    陈康stozen阅读 3,869评论 0 1