IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程——多播与广播 -> 正文阅读

[网络协议]网络编程——多播与广播

参考

  1. 《TCP/IP网络编程》 尹圣雨

多播与广播

多播

多播(Multicast)方式的数据传输是基于UDP完成的。与UDP服务器端/客户端的实现方式区别在于,UDP数据传输以单一目标进行,而多播数据同时传递传递到加入(注册)特定组的大量主机

MBone(Multicast Backbone,多播主干网)

多播是基于MBone这个虚拟网络工作的。虚拟网络可以理解为通过网络中的特殊协议工作的软件概念上的网络。即MBone并非可以触及的物理网络。它是以物理网络为基础,通过软件方法实现的多播通信必备虚拟网络

多播的传输方式

多播的数据传输特点:

  1. 多播服务器端针对特定多播组,只发送1次数据
  2. 即使只发送1次数据,但该组内的所有客户端都会接收数据
  3. 多播组数可在IP地址范围内任意增加
  4. 加入特定组即可接收发往该多播组的数据

多播组是D类IP地址(224.0.0.0~239.255.255.255)

多播需要借助路由器完成,只需发送1次,由路由器负责复制文件并传递到主机。因此,多播主要用于多媒体数据的实时传输

虽然理论上可以完成多播通信,但不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道(Tunneling)技术

路由(Routing)和TTL(Time to Live)

为了传递多播数据包,必需设置TTL。TTL是决定数据包传递距离的主要因素。TTL用整数表示,并且每经过1个路由器就减1。TTL变为0时,该数据包无法再被传递,只能销毁。因此,TTL的值设置过大将影响网络流量;设置过小也会无法传递到目标

TTL的设置通过套接字可选项完成,与TTL相关的协议层为IPPROTO_IP,选项名为IP_MULTICAST_TTL。例如,将TTL设置为64:

int send_sock;
int time_live = 64;
...
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
...

加入多播组

加入多播组也通过设置套接字可选项完成,选项名为IP_ADD_MEMBERSHIP。可通过如下代码加入多播组:

int recv_sock;
struct ip_mreq join_adr;
...
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
...
join_adr.imr_multiaddr.s_addr = "多播组地址信息";
join_adr.imr_interface.s_addr = "加入多播组的主机地址信息";
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));
...

其中,ip_mreq结构体定义如下:

struct ip_mreq
{
	struct in_addr imr_multiaddr;
	struct in_addr imr_interface;
};

第一个成员imr_multiaddr中写入加入的组IP地址;第二个成员imr_interface是加入该组的套接字所属主机的IP地址,也可使用INADDR_ANY

实现多播Sender和Receiver

多播中的Sender和Receiver替代服务器端和客户端。Sender比Receiver简单,因为Receiver需要经过加入组的过程,而Sender只需创建UDP套接字,并向多播地址发送数据

(1)Sender示例

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

#define TTL 64
#define BUF_SIZE 30
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int send_sock;
    struct sockaddr_in mul_adr;
    int time_live = TTL;
    FILE* fp;
    char buf[BUF_SIZE];
    if (argc != 3)
    {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&mul_adr, 0, sizeof(mul_adr));
    mul_adr.sin_family = AF_INET;
    mul_adr.sin_addr.s_addr = inet_addr(argv[1]);
    mul_adr.sin_port = htons(atoi(argv[2]));

    setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)); // 指定TTL信息,这是Sender中的必要过程
    if ((fp = fopen("news.txt", "r")) == NULL)
    {
        error_handling("fopen() error");
    }

    while (!feof(fp))
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));  // 基于UDP套接字,所以利用sendto函数
        sleep(2);
    }
    fclose(fp);
    close(send_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2)Receiver示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in adr;
    struct ip_mreq join_adr;
    if (argc != 3)
    {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[2]));

    if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
    {
        error_handling("bind() error");
    }

    join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]);          // 初始化多播组地址
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY);           // 初始化待加入主机的IP地址

    setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));  // 加入多播组

    while (1)
    {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (str_len < 0)
        {
            break;
        }
        buf[str_len] = 0;
        fputs(buf, stdout);
    }
    close(recv_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

基于Windows的实现

与Linux下只有头文件的声明稍有区别

(1)Sender

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <ws2tcpip.h>

#define TTL 64
#define BUF_SIZE 30
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSendSock;
    SOCKADDR_IN mulAdr;
    int timeLive = TTL;
    FILE* fp;
    char buf[BUF_SIZE];

    if (argc != 3)
    {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        ErrorHandling("WSAStartup() error!");
    }

    hSendSock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&mulAdr, 0, sizeof(mulAdr));
    mulAdr.sin_family = AF_INET;
    mulAdr.sin_addr.s_addr = inet_addr(argv[1]);
    mulAdr.sin_port = htons(atoi(argv[2]));

    setsockopt(hSendSock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&timeLive, sizeof(timeLive));
    if ((fp = fopen("news.txt", "r")) == NULL)
    {
        ErrorHandling("fopen() error");
    }
    while (!feof(fp))
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(hSendSock, buf, strlen(buf), 0, (SOCKADDR*)&mulAdr, sizeof(mulAdr));
        Sleep(2000);
    }
    closesocket(hSendSock);
    WSACleanup();
    return 0;
}

void ErrorHandling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2)Receiver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <ws2tcpip.h>

#define BUF_SIZE 30
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hRecvSock;\
    SOCKADDR_IN adr;
    struct ip_mreq joinAdr;
    char buf[BUF_SIZE];
    int strLen;

    if (argc != 3)
    {
        printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
        exit(1);
    }
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        ErrorHandling("WSAStartup() error!");
    }

    hRecvSock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[1]));
    if (bind(hRecvSock, (SOCKADDR*)&adr, sizeof(adr)) == SOCKET_ERROR)
    {
        ErrorHandling("bind() error");
    }

    joinAdr.imr_multiaddr.s_addr = inet_addr(argv[1]);
    joinAdr.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(hRecvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&joinAdr, sizeof(joinAdr)) == SOCKET_ERROR)
    {
        ErrorHandling("setsock() error");
    }

    while (1)
    {
        strLen = recvfrom(hRecvSock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (strLen < 0)
        {
            break;
        }
        buf[strLen] = 0;
        fputs(buf, stdout);
    }
    closesocket(hRecvSock);
    WSACleanup();
    return 0;
}

void ErrorHandling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

广播

广播(Broadcast)在“一次性向多个主机发送数据”这一点上与多播类似,但传输数据的范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接收数据。相反,广播只能向同一网络中的主机传输数据

广播的实现方法

广播也是基于UDP完成的。根据传输数据时使用的IP地址的形式,广播分为直接广播(Directed Broadcast)和本地广播(Local Broadcast),二者实现上的差别主要在于IP地址

(1)直接广播
直接广播的IP地址除了网络地址外,其余主机地址全部设置为1。即可以采用直接广播的方式向特定区域内所有主机传输数据

(2)本地广播
本地广播中使用的IP地址限定为255.255.255.255

实际上,数据通信中使用的IP地址是与UDP示例的唯一区别。但默认生成的套接字会阻止广播,因此,只需调用setsockopt函数,将SO_BROADCAST选项设置为1:

int send_sock;
int bcast = 1;
...
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
...
setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast));
...

实现广播数据的Sender和Receiver

(1)Sender

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

#define BUF_SIZE 30
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int send_sock;\
    struct sockaddr_in broad_adr;
    FILE* fp;
    char buf[BUF_SIZE];
    int so_brd = 1;
    if (argc != 3)
    {
        printf("Usage : %s <Broadcast IP> <PORT>\n", argv[0]);
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&broad_adr, 0, sizeof(broad_adr));
    broad_adr.sin_family = AF_INET;
    broad_adr.sin_addr.s_addr = inet_addr(argv[1]);
    broad_adr.sin_port = htons(atoi(argv[2]));

    setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd));
    if ((fp = fopen("news.txt", "r")) == NULL)
    {
        error_handling("fopen() error");
    }
    
    while (!feof(fp))
    {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr));
        sleep(2);
    }
    close(send_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2)Receiver

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

#define BUF_SIZE 30
void error_handling(char* message);

int main(int argc, char* argv[])
{
    int recv_sock;
    struct sockaddr_in adr;
    int str_len;
    char buf[BUF_SIZE];
    if (argc != 2)
    {
        printf("Usage : %s <PORT>\n", argv[0]);
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[1]));

    if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
    {
        error_handling("bind() error");
    }
    while (1)
    {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
        if (str_len < 0)
        {
            break;
        }
        buf[str_len] = 0;
        fputs(buf, stdout);
    }
    close(recv_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:57:15  更:2021-11-12 19:58:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年7日历 -2024/7/3 8:52:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码