1 多播

&emsp 多播(Multicast )方式的数据传输是基于UDP完成的。 因此,与UDP服务器端/客户端的实现方式非常接近。 区别在于, UDP数据传输以单一目标进行,而多播数据同时传递到加入(注册)特定组的大量主机。 换言之,采用多播方式时,可以同时向多个主机传递数据。

1.1 多播的数据传输方式及流量方面的优点

多播的数据传输特点如下:

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

  多播是D类IP地址(224.0.0.0~239.255.255.255),"加入多播组"可以理解为。
  多播是基于UDP完成的,也就是说,多播数据包的格式与UDP数据包相同。不同的是,向网络发送一个多播数据包时,路由器将复制该数据包并传递到多个主机。多播需要借助路由器完成,如下图所示:
九:多播和广播-LMLPHP

  不要觉得频繁复制同一数据包,会不利于网络流量,因为。相当于羊村的100之羊,从很远的地方定了一批相同的订单,使用TCP/UDP需要送100次,但是使用多播,只需要送一次到羊村门口,然后村里驿站复制100份然后给小羊就行。基于这种特性,多播主要用于“多媒体数据的实时传输”。
  注意:虽然理论上可以完成多播通信,但不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此为了在不支持多播的路由器中完成多播通信,也会使用隧道技术。

1.2 路由和TTL,以及加入组的方法

  为了传递多播数据包,必须设置TTL(Time to Live),这是决定“数据包传递距离”的主要因素。TTL用整数表示,并且每经过一个路由器就减1,TTL变为0时,该数据包将无法传递被销毁。因此,TTL值设置过大将影响网络流量,设置国小会无法传递到目标,注意。
  TTL设置方法是通过此节:套接字可选项完成的。与TTL相关的协议层为IPPROTO_IP,选项名为IP_MULTICAST_TTL。具体设置如下:

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

另外,加入多播组也通过设置套接字选项完成,协议层为IPPROTO_IP,选项为IP_ADD_MEMBERSHIP。具体代码如下:

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

ip_mreq的结构体定义如下:

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

1.3 实现多播Sender 和 Receiver

  多播中使用“发送者”和“接收者”替代服务端和客户端,此处Sender是多播数据的发送主体,Receiver是多播数据的发送主体,Receiver需要加入多播组接收数据。下面给出示例,示例背景如下:

  • Sender:向AAA组广播文件中保存的新闻消息
  • Receiver:接收传递到AAA组的新闻信息
    九:多播和广播-LMLPHP

news_sender.c

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

#define TTL 64
#define BUF_SIZE 30

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

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 <IP><PORT>\n", argv[0]);
        exit(1);
    }

    //多播通信基于UDP
    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]));

    //设置TTL信息
    setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
    if (fp = fopen("news.txt", "r") == NULL) {
        ErrorHandler("fopen() error");
    }

    //实际传输数据的区域。
    while (!feof(fp)) {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
        sleep(2); //纯纯添加时间间隔,没意义的
    }
    fclose(fp);
    close(send_sock);
    return 0;
}

news_receiver.c

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

#define BUF_SIZE 30

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

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("Usage : %s <GroupIP><PORT> \n", argv[0]);
        exit(1);
    }


    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in adr;
    struct ip_mreq join_adr;

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

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

    //初始化结构体ip_mreq
    join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); //初始化多播组地址
    join_adr.imr_interface.s_addr = htonl(INADDR_ANY);

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

    while (1) {
        //不知道传输数据的主机地址信息,所以传0
        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;
}

2 广播

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

2.1 广播的理解及实现方法

  广播是向同一网络中所有主机传输数据的方法。广播也是基于UDP完成的,根据传输数据时使用的IP地址的形式,广播分为如下2种。

  • 直接广播
  • 本地广播

  二者在代码上的差别主要在于IP地址。直接广播的IP地址中除了网络地址外,其余主机全部设置为1。例如:希望向网络地址192.12.34中的所有主机传输数据时,可以向192.12.34.255传输。换言之,可以采用直接广播的方式向特定区域内所有主机传输数据。
  反之,在本地广播中使用的IP地址限定为255.255.255.255。例如,192.32.24网络中的主机向255.255.255.255传输数据时,数据将传递到192.32.24网络中的所有主机。
  如何实现Sender和Receiver呢?实际上,如果不仔细观察广播示例中通信时使用的IP地址,则很难与UDP示例进行区分。也就是说,数据通信中使用的IP地址是与UDP示例的唯一区别。默认生成的套接字会阻止广播,因此只需要通过如下代码更改默认设置。

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));
....

2.2 实现广播数据的Sender 和 Receiver

九:多播和广播-LMLPHP
new_sender_brd.c

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

#define BUFF_SIZE 30

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

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("Usage: %s <Boardcast IP> <PORT>\n", argv[0]);
        exit(1);
    }

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

    int bcast = 1;
    setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast));
    FILE* fp;
    if((fp = fopen("news.txt", "r")) == NULL) {
        ErrorHandler("fopen() error");
    }

    char buf[BUFF_SIZE];
    while(!feof(fp)) {
        fgets(buf, BUFF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_addr, sizeof(broad_addr));
        sleep(2);
    }
    close(send_sock);
    return 0;
}

news_receiver_brd.c

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

#define BUFF_SIZE 30

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

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

    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) {
        ErrorHandler("bind error");
    }

    while (1) {
        str_len = recvfrom(recv_sock, buf, BUFF_SIZE-1, 0, NULL, 0);
        if (str_len < 0) {
            break;
        }
        buf[str_len] = 0;
        fputs(buf, stdout);
    }
    close(recv_sock);
    return 0;
}
```
03-09 00:48