第四章:广播和组播

广播和组播

广播和多播就是发送方向多个接收方的主机发送消息,也就是一对多,广播是给所有的主机发送消息,只能用在局域网中;多播是给一个多播组中的所有主机发送消息,既可以用于广域网,也可以用于局域网;由于都是一对多,所以TCP的端对端的单播协议明显不适用,而只能用无连接不可靠的UDP协议

1. 广播

向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1。

a.只能在局域网中使用。

b.客户端需要绑定服务器广播使用的端口,才可以接收到广播消息。

第四章:广播和组播_第1张图片

// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
    - sockfd : 文件描述符
    - level : SOL_SOCKET
    - optname : SO_BROADCAST
    - optval : int类型的值,为1表示允许广播
    - optlen : optval的大小
代码(有一处不明白)
// server.cpp
#include 
#include 
using namespace std;
#include 
#include 

#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16

// 广播的IP地址
const char* Broadcast_IP = "127.255.255.255";

int main() {
    // 1.创建通信的socket套接字
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == socket_fd) {
        perror("socket");
        return -1;
    }

    // 开启广播设置
    int _optval = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &_optval, sizeof(_optval));

    // 2.绑定IP和端口,其实在这里我们不接受数据,帮不绑定其实无所谓
    struct sockaddr_in server_addr;
    // 地址族
    server_addr.sin_family = AF_INET;
    // IP
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
    // 端口
    server_addr.sin_port = htons(9999);

    int ret = bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (-1 == socket_fd) {
        perror("bind");
        return -1;
    }

    printf("server has initialized.\n");

    // 封装广播客户端的socket地址
    struct sockaddr_in All_Client_addr;
    All_Client_addr.sin_family = AF_INET;
    All_Client_addr.sin_port = htons(10000);
    inet_pton(AF_INET, Broadcast_IP, &All_Client_addr.sin_addr.s_addr);

    // 3.开始通信
    static int num = 0;
    char buf[MAX_BUF_SIZE] = {0};

    while (1) {
        // 服务端向所有的客户端广播数据
        bzero(buf, sizeof(buf));
        sprintf(buf, "hello , i am server , num = %d\n", num++);
        printf("send : %s", buf);

        sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&All_Client_addr, sizeof(All_Client_addr));
        sleep(1);
    }

    // 4.关闭套接字
    close(socket_fd);

    return 0;
}
// client.cpp
#include 
#include 
using namespace std;
#include 
#include 

#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16

int main() {
    // 1.创建通信的socket套接字
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == socket_fd) {
        perror("socket");
        return -1;
    }

    // 2.绑定端口信息,让发送方能够正确找到
    struct sockaddr_in client_addr;
    // 地址族
    client_addr.sin_family = AF_INET;
    // IP
    // inet_pton(AF_INET, "127.0.0.2", &client_addr.sin_addr.s_addr);  // 这行代码会出问题,但是我也不知道为什么
    client_addr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    client_addr.sin_port = htons(10000);

    int ret = bind(socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }

    char buf[MAX_BUF_SIZE] = {0};

    // 2.开始通信
    while (1) {
        // 读数据
        recvfrom(socket_fd, buf, sizeof(buf) - 1, 0, nullptr, nullptr);
        printf("recv : %s", buf);
    }

    // 4.关闭套接字
    close(socket_fd);

    return 0;
}

我们的代码需要做的功能是服务端启动后,即可开始向局域网内的所有主机广播信息,当有客户端连接进来的时候可以收到客户端的信息

我们先来解释bind()函数,为什么这里服务端和客户端都使用了bind()?

bind()函数可以给我们socket()创建出来的文件描述符绑定我们自己设定的IP和端口信息,比如这里我就给服务端绑定了"127.0.0.1"和9999的信息,客户端绑定了任意IP(局域网内)和10000端口,IP是次要的,bind()函数绑定socket的时候应该首先考虑到给优先接受数据的一方绑定,比如这里就是客户端,为什么呢?因为我发送方一定需要知道一个具体的端口号我才能发送,在UDP中IP倒不一定必须,因为有可能是广播或者组播,这就不是一个具体的IP了,但是端口号是标识不同主机的进程的,所以发送方一定是根据这个端口号找到你对应的进程的,然后如果我得客户端不绑定,就由系统给我自动分配,那就找不到了,所以这里其实服务端的绑定其实没有必要,但是为了习惯我还是加上了;在TCP中也是一样的,我客户端先向服务端发送数据,在这之前需要建立连接,我也是通过人为指定的端口连接服务端,所以服务端绑定了端口,也就调用了bind()

但是这里我不明白我给客户端指定IP为 127.0.0.2 收不到服务端广播的消息,必须是局域网内的任意IP,也就是INADDR_ANY才行,这里我不明白

另外还有一点就是广播的发送方要给socket()设置广播属性,就像这样

// 开启广播设置
int _optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &_optval, sizeof(_optval));
2. 组播(多播)

单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。 单播和广播是寻址方案的两个极端(要么单个要么全部),多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。

a.组播既可以用于局域网,也可以用于广域网

b.客户端需要加入多播组,才能接收到多播的数据

第四章:广播和组播_第2张图片

  • 组播地址

IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255 , 并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

第四章:广播和组播_第3张图片

  • 设置组播

第四章:广播和组播_第4张图片

多播的API用的比较少,需要用的时候来查询就可以了,但是要知道工作原理

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
// 服务器设置多播的信息,外出接口
    - level : IPPROTO_IP
    - optname : IP_MULTICAST_IF
    - optval : struct in_addr
// 客户端加入到多播组:
    - level : IPPROTO_IP
    - optname : IP_ADD_MEMBERSHIP
    - optval : struct ip_mreq
        
struct ip_mreq {
    /* IP multicast address of group. */
    struct in_addr imr_multiaddr; // 组播的IP地址
    /* Local IP address of interface. */
    struct in_addr imr_interface; // 本地的IP地址
};

typedef uint32_t in_addr_t;
struct in_addr {
    in_addr_t s_addr;
};
代码(和前面同样的问题)
// server.cpp
#include 
#include 
using namespace std;
#include 
#include 

#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16

// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";

int main() {
    // 1.创建通信的socket套接字
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == socket_fd) {
        perror("socket");
        return -1;
    }

    // 设置多播属性,设置外出接口
    struct in_addr _optval;
    // 初始化多播地址
    inet_pton(AF_INET, Multicast_IP, &_optval.s_addr);
    setsockopt(socket_fd, IPPROTO_IP, IP_MULTICAST_IF, &_optval, sizeof(_optval));

    // 发送方,这里我就不绑定端口了

    printf("server has initialized.\n");

    // 封装广播客户端的socket地址
    struct sockaddr_in All_Client_addr;
    All_Client_addr.sin_family = AF_INET;
    All_Client_addr.sin_port = htons(10000);
    inet_pton(AF_INET, Multicast_IP, &All_Client_addr.sin_addr.s_addr);

    // 3.开始通信
    static int num = 0;
    char buf[MAX_BUF_SIZE] = {0};

    while (1) {
        // 服务端向所有的客户端广播数据
        bzero(buf, sizeof(buf));
        sprintf(buf, "hello , i am server , num = %d\n", num++);
        printf("send : %s", buf);

        sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&All_Client_addr, sizeof(All_Client_addr));
        sleep(1);
    }

    // 4.关闭套接字
    close(socket_fd);

    return 0;
}
// client.cpp
#include 
#include 
using namespace std;
#include 
#include 

#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16

// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";

int main() {
    // 1.创建通信的socket套接字
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == socket_fd) {
        perror("socket");
        return -1;
    }

    // 加入多播组
    struct ip_mreq _optval;
    // 初始化
    _optval.imr_interface.s_addr = INADDR_ANY;
    inet_pton(AF_INET, Multicast_IP, &_optval.imr_multiaddr.s_addr);

    setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &_optval, sizeof(_optval));

    // 2.绑定端口信息,让发送方能够正确找到
    struct sockaddr_in client_addr;
    // 地址族
    client_addr.sin_family = AF_INET;
    // IP
    // inet_pton(AF_INET, "127.0.0.2", &client_addr.sin_addr.s_addr);  // 和之前一样的问题
    client_addr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    client_addr.sin_port = htons(10000);

    int ret = bind(socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }

    char buf[MAX_BUF_SIZE] = {0};

    // 2.开始通信
    while (1) {
        // 读数据
        recvfrom(socket_fd, buf, sizeof(buf) - 1, 0, nullptr, nullptr);
        printf("recv : %s", buf);
    }

    // 4.关闭套接字
    close(socket_fd);

    return 0;
}

同样的客户端也能收到服务端发送而来的数据

我们同样注意服务端和客户端对于设置多播和加入多播的设置方法

服务端

// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";    

// 设置多播属性,设置外出接口
struct in_addr _optval;
// 初始化多播地址
inet_pton(AF_INET, Multicast_IP, &_optval.s_addr);
setsockopt(socket_fd, IPPROTO_IP, IP_MULTICAST_IF, &_optval, sizeof(_optval));

客户端

// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";

// 加入多播组
struct ip_mreq _optval;
// 初始化
_optval.imr_interface.s_addr = INADDR_ANY;
inet_pton(AF_INET, Multicast_IP, &_optval.imr_multiaddr.s_addr);

setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &_optval, sizeof(_optval));

你可能感兴趣的:(牛客Linux,udp,网络协议,linux,c++)