如何在Linux c/c++ 进行多播(组播)编程

第一章: 前言

多播技术,也被称为“组播”,是一种网络通信机制,它允许一个节点(发送者)向一组特定的节点(接收者)发送信息。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能,同时减少网络带宽的使用。

在单播通信中,信息从一个节点发送到另一个节点,而在广播通信中,信息从一个节点发送到网络中的所有节点。多播则介于这两者之间,信息从一个节点发送到一组特定的节点。这种方式特别适合于需要向一组特定的主机发送信息的场景,例如在线视频会议或实时数据共享。

多播的实现依赖于D类IP地址,范围从224.0.0.0到239.255.255.255。这些地址被划分为局部链接多播地址、预留多播地址和管理权限多播地址。主机可以向路由器请求加入或退出某个多播组,然后路由器和交换机会有选择地复制并传输数据,只将数据传输给组内的主机。

多播技术的优点包括:

  1. 带宽效率:多播允许数据在同一分组的主机之间共享,节省了网络带宽。

  2. 服务器负载:由于多播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。

  3. 广域网支持:与广播不同,多播可以在广域网,如Internet上进行。

然而,多播技术也有一些缺点:

  1. 无纠错机制:多播与单播相比没有纠错机制,当发生错误的时候难以弥补,但是可以在应用层来实现此种功能。

  2. 网络支持:多播的网络支持存在缺陷,需要路由器及网络协议栈的支持。

总的来说,多播技术是一种强大的网络通信机制,它在许多场景中都非常有用,如网上视频、网上会议等。然而,要有效地使用多播,需要理解其工作原理,并确保网络设备和协议栈正确地支持多播。

第二章: 广域网的多播

多播是一种网络通信机制,它允许一个节点(发送者)向一组节点(接收者)发送信息。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能。

在IP网络中,多播地址被定义为D类IP地址,范围从224.0.0.0到239.255.255.255。这些地址被划分为三类:

  1. 局部链接多播地址:这个范围是从224.0.0.0到224.0.0.255。这些地址主要用于网络设备之间的本地通信,例如路由器之间的通信。这些地址的数据包不会被路由器转发到其他网络。

  2. 预留多播地址:这个范围是从224.0.1.0到238.255.255.255。这些地址可以在全球范围内使用,例如在Internet上。这意味着,如果你的应用程序需要向全球的多个节点发送信息,你可以使用这个范围内的地址。

  3. 管理权限多播地址:这个范围是从239.0.0.0到239.255.255.255。这些地址类似于私有IP地址,只能在特定的组织或企业内部使用。这些地址的数据包不能在Internet上路由,因此可以用来限制多播的范围。

在使用多播地址时,你需要注意一些事情。首先,你需要确保你的网络设备(如路由器和交换机)支持多播,并且已经正确配置。其次,你需要选择正确的多播地址,以确保你的信息能够正确地发送到目标节点。

希望这个解释能帮助你更深入地理解广域网的多播和多播地址。如果你还有其他问题,欢迎随时向我提问。

第三章: 多播的编程

多播是一种在网络中发送信息的方式,它允许一个节点向一组节点发送信息,而不是单独向每个节点发送。这种方式在网络编程中非常有用,因为它可以大大提高效率和性能。

在多播编程中,我们使用一些特殊的套接字选项来控制多播行为。这些选项通常在IP层设置,因为多播是在IP层实现的。

  1. IP_MULTICAST_TTL:这个选项用于设置多播数据包的生存时间(TTL)。TTL是一个在0到255之间的值,它决定了数据包可以通过多少个路由器。每当数据包通过一个路由器,其TTL就会减1,当TTL达到0时,数据包就会被丢弃。这个选项可以防止多播数据包在网络中无限制地传播。

  2. IP_MULTICAST_IF:这个选项用于设置默认的多播接口。多播数据包将从这个接口发送,其他接口将忽略这些数据包。

  3. IP_MULTICAST_LOOP:这个选项用于控制是否允许多播数据包在本地回环。如果设置为1,数据包将在本地回环;如果设置为0,数据包将不会在本地回环。

  4. IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP:这两个选项用于控制节点是否加入或离开一个多播组。当一个节点加入一个多播组后,它就可以接收到发送到该组的所有数据包。

在编写多播程序时,我们通常遵循以下步骤:

  1. 创建一个套接字:我们首先需要创建一个套接字来发送和接收数据。

  2. 设置多播参数:然后,我们需要设置多播的参数,如TTL和本地回环。

  3. 加入多播组:接下来,我们需要将节点加入到一个多播组中。

  4. 发送和接收数据:一旦节点加入了一个多播组,它就可以开始发送和接收数据了。

  5. 离开多播组:最后,当节点不再需要接收多播数据时,它可以从多播组中离开。

以下是一个使用C++编写的多播编程示例。这个示例中,我们将创建一个发送者和一个接收者,发送者将向一个多播组发送数据,接收者将接收这些数据。

首先,我们创建一个发送者:

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    
    
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
    
    
        std::cerr << "Error creating socket" << std::endl;
        return -1;
    }

    // 设置多播TTL参数
    unsigned char ttl = 1;
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
    
    
        std::cerr << "Error setting multicast TTL" << std::endl;
        return -1;
    }

    // 设置目标地址
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("239.0.0.1"); // 多播地址
    addr.sin_port = htons(12345); // 目标端口

    // 发送数据
    std::string msg = "Hello, Multicast!";
    if (sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    
    
        std::cerr << "Error sending data" << std::endl;
        return -1;
    }

    std::cout << "Data sent to multicast group" << std::endl;

    return 0;
}

然后,我们创建一个接收者:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    
    
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
    
    
        std::cerr << "Error creating socket" << std::endl;
        return -1;
    }

    // 绑定到本地地址和端口
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意本地地址
    addr.sin_port = htons(12345); // 目标端口

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    
    
        std::cerr << "Error binding socket" << std::endl;
        return -1;
    }

    // 加入多播组
    ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr("239.0.0.1"); // 多播地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 任意网络接口

    if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
    
    
        std::cerr << "Error joining multicast group" << std::endl;
        return -1;
    }

    // 接收数据
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    if (recv(sock, buf, sizeof(buf), 0) < 0) {
    
    
        std::cerr << "Error receiving data" << std::endl;
        return -1;
    }

    std::cout << "Received data: " << buf << std::endl;

    // 离开多播组
    if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
    
    
        std::cerr << "Error leaving multicast group" << std::endl;
        return -1;
    }

    return 0;
}

第四章:深入理解Linux内核中的多播

在Linux内核中,多播功能是通过一系列精心设计的数据结构和相应的操作来实现的。本章将深入探讨这些数据结构,包括struct ip_mc_sockliststruct ip_mreqnstruct ip_sf_socklist,以及如何通过IP_ADD_MEMBERSHIPIP_DROP_MEMBERSHIP选项来操作这些数据结构,从而实现多播功能。

4.1 多播的核心数据结构:ip_mc_socklist

在Linux内核中,多播是通过struct ip_mc_socklist数据结构来实现的。这个数据结构连接了多播的各个方面,包括多播的TTL(Time To Live)、是否启用多播回环、多播设备序号、多播地址以及多播群组。

4.2 加入和离开多播组:IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP

在Linux中,我们可以通过IP_ADD_MEMBERSHIP选项来将一个本地IP地址加入到一个多播组。这个过程主要包括将用户数据复制到内核、检查多播IP地址的合法性、查找对应的网络接口、检查多播列表中是否已存在该多播地址,以及将该多播地址加入到列表中。

相反,我们可以通过IP_DROP_MEMBERSHIP选项来将一个本地IP地址从一个多播组中移除。这个过程主要包括将用户数据复制到内核、查找对应的网络接口、检查多播列表中是否已存在该多播地址,以及将该多播地址从源地址和多播列表中移除。

4.3 源过滤:ip_sf_socklist

在Linux内核中,我们可以通过struct ip_sf_socklist数据结构来实现源过滤。这个数据结构包含了一个源地址列表,以及列表中源地址的数量和列表的最大容量。我们可以通过这个数据结构来控制哪些源的多播数据报被接收,哪些源的多播数据报被排除。

通过深入理解这些数据结构和操作,我们可以更好地理解Linux内核中的多播功能,以及如何在我们的应用中使用多播。在下一章中,我们将探讨如何在实际应用中使用Linux的多播功能。

一个多播服务器端的示例

下面是一个改进后的多播服务器端的示例。这个示例程序将持续向多播IP地址"224.0.0.88"的8888端口发送数据"BROADCAST TEST DATA",每发送一次间隔5s。

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

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88"
#define MCAST_DATA "BROADCAST TEST DATA"
#define MCAST_INTERVAL 5

int main(void)
{
    
    
    int sockfd;
    struct sockaddr_in mcast_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
    
    
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化多播地址
    memset(&mcast_addr, 0, sizeof(mcast_addr));
    mcast_addr.sin_family = AF_INET;
    mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);
    mcast_addr.sin_port = htons(MCAST_PORT);

    // 循环发送多播数据
    while (1)
    {
    
    
        int n = sendto(sockfd, MCAST_DATA, sizeof(MCAST_DATA), 0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr));
        if (n == -1)
        {
    
    
            perror("sendto");
            exit(EXIT_FAILURE);
        }

        // 等待一段时间
        sleep(MCAST_INTERVAL);
    }

    close(sockfd);

    return 0;
}

这个程序的流程如下:

在这里插入图片描述

  1. 创建套接字。
  2. 初始化多播地址。
  3. 设置协议族为AF。
  4. 设置多播IP地址。
  5. 设置多播端口。
  6. 开始循环,向多播地址发送数据。
  7. 等待一段时间。
  8. 回到步骤6。

这个程序的改进之处在于,它更加简洁,更加注重错误处理,确保在发生错误时能够及时退出并给出错误信息。同时,它也更加注重资源管理,确保在程序结束时关闭套接字。

一个多播客户端的示例

下面是一个改进后的多播客户端的示例。这个示例程序将加入多播组"224.0.0.88",监听端口8888,接收并打印出从多播组收到的数据。

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

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88"
#define MCAST_INTERVAL 5
#define BUFF_SIZE 256

int main(void)
{
    
    
    int sockfd;
    struct sockaddr_in local_addr;
    struct ip_mreq mreq;
    char buff[BUFF_SIZE];
    int n;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
    
    
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化本地地址
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(MCAST_PORT);

    // 绑定socket
    if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) == -1)
    {
    
    
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 设置回环许可
    int loop = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
    {
    
    
        perror("setsockopt: IP_MULTICAST_LOOP");
        exit(EXIT_FAILURE);
    }

    // 加入多播组
    mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    {
    
    
        perror("setsockopt: IP_ADD_MEMBERSHIP");
        exit(EXIT_FAILURE);
    }

    // 循环接收多播组的消息
    for (int times = 0; times < 5; times++)
    {
    
    
        socklen_t addr_len = sizeof(local_addr);
        memset(buff, 0, BUFF_SIZE);

        n = recvfrom(sockfd, buff, BUFF_SIZE, 0, (struct sockaddr*)&local_addr, &addr_len);
        if (n == -1)
        {
    
    
            perror("recvfrom");
            exit(EXIT_FAILURE);
        }

        // 打印信息
        printf("Recv %dst message from server: %s\n", times, buff);

        // 等待一段时间
        sleep(MCAST_INTERVAL);
    }

    // 退出多播组
    if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    {
    
    
        perror("setsockopt: IP_DROP_MEMBERSHIP");
        exit(EXIT_FAILURE);
    }

    close(sockfd);

    return 0;
}

这个程序的流程如下:

在这里插入图片描述

  1. 创建套接字。
  2. 初始化本地地址。
  3. 绑定socket。
  4. 设置回环许可。
  5. 加入多播组。
  6. 开始循环,接收数据。
  7. 打印信息。
  8. 等待一段时间。

猜你喜欢

转载自blog.csdn.net/qq_21438461/article/details/131021736
今日推荐