广播、组播、单播区别与联系

1.引言

网络通信中使用最多的就是广播、组播、单播几种通信方式了,今天我们抛开具体的标准和知识,简单聊聊单播、组播、广播的区别与使用。

2.单播、组播、广播区别与联系

单播:在同一网络内,两个设备点对点的通信就是单播通信。

组播:在同一网络可达范围内,一个网络设备与关心其数据的部分设备进行通信就是组播。

广播:在同一网络可达范围内,一个网络设备向本网络内所有设备进行通信就是广播。

具体如下:

广播、组播、单播区别与联系

简单地说,单播->组播->广播,是通信数量不断增加的通信方式。当然,通信数量的增多,带来的是通信设备的资源消耗更大,整体网络环境的复杂度更高。

通常,我们使用组播、广播完成两件事:

1)将同一份数据交互到多个目的地。比如,视频会议、新闻分发,都需要将一份数据同时传输到多个设备上,供大家使用。

2)通过客户端请求或发现服务器。有时,我们并不知道服务器的具体信息(如IP地址),这时,我们可以采取“盲发”的方式去广播或组播信息,等待服务器收到消息盲发的消息后,返回数据,如此找到对应目标设备。

众所周知,TCP是可靠传输(先与另一个通信端点建立可靠连接,再传输数据),因此TCP一般只支持单播这种通信方式,而DUP通信不需要建立连接就可以发送数据,因此,通常我们说的广播、组播,都是在UDP下概念。

此外,广播又可以分为两类:本地广播、定向广播。

1)本地广播:广播地址为255.255.255.255.

2)定向广播:广播地址类似192.168.4.255.

这两种广播功能类似,但具体区别说来话长,有感兴趣的可以留言,我再出篇帖子来介绍一下。

3.编程与测试

广播、单播在实现方式,以及使用方式上的区别不大,仅仅是目标IP,以及Socket属性的细微差别,我们先来看两者的区别与使用。

具体可参考以下代码:(开发板仍然是便宜且好用的ESP32开发板,开发环境是release V4.2,实际上,任何平台,以下代码都是可以参考滴,客官慢用)

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "tcpip_adapter.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "addr_from_stdin.h"

#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif

#define PORT CONFIG_EXAMPLE_PORT

static const char *TAG = "example";
static const char *payload = "Message from ESP32 ";

static int udp_creat(uint16_t port, char* bind_ip)
{
    struct sockaddr_in6 unicast_dest_addr = {0};
    struct sockaddr_in *unicast_dest_addr_ip4 = (struct sockaddr_in *)&unicast_dest_addr;
    int ip_protocol = 0;
    const int on = 1;
    inet_aton(bind_ip, &unicast_dest_addr_ip4->sin_addr.s_addr);//bind sta ip
    unicast_dest_addr_ip4->sin_family = AF_INET;
    unicast_dest_addr_ip4->sin_port = htons(port);
    ip_protocol = IPPROTO_IP;

    int sock = socket(AF_INET, SOCK_DGRAM, ip_protocol);
    if (sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        return -1;
    }

    int err = bind(sock, (struct sockaddr *)&unicast_dest_addr, sizeof(unicast_dest_addr));
    if (err < 0) {
        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        shutdown(sock, 0);
        close(sock);
        return -1;
    }
    
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) {
        ESP_LOGE(TAG, "reuse addr fail");
        close(sock);
        return -1;
    }

    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) != 0) {
        ESP_LOGE(TAG, "broadcast enable fail");
        close(sock);
        return -1;
    }

    return sock;
}

static void udp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;
    tcpip_adapter_ip_info_t sta_info;
    while (1) {

#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in unicast_dest_addr;
        unicast_dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
        unicast_dest_addr.sin_family = AF_INET;
        unicast_dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
        struct sockaddr_in6 unicast_dest_addr = { 0 };
        inet6_aton(HOST_IP_ADDR, &unicast_dest_addr.sin6_addr);
        unicast_dest_addr.sin6_family = AF_INET6;
        unicast_dest_addr.sin6_port = htons(PORT);
        unicast_dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_in6 unicast_dest_addr = { 0 };
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &unicast_dest_addr));
#endif
        char sta_ip_str[32] = {0};
        esp_err_t ret = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &sta_info);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "get sta ip fail");
        }
        printf(IPSTR, IP2STR(&sta_info.ip));
        sprintf(sta_ip_str, IPSTR, IP2STR(&sta_info.ip));
        int sock = udp_creat(3333,sta_ip_str);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }

        ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT);
        struct sockaddr_in broadcast_dest_addr;//for broadcast
        char sendline[32] = {"Hello"};
        char temp_str[32] = "255.255.255.255";
        bzero(&broadcast_dest_addr, sizeof(broadcast_dest_addr));
        broadcast_dest_addr.sin_family = AF_INET;
        broadcast_dest_addr.sin_addr.s_addr = inet_addr(temp_str); //广播地址(192.168.43.255 can be work, too.)
        broadcast_dest_addr.sin_port = htons(3333);//target port

        while (1) {
            int err = sendto(sock, sendline, strlen(sendline), 0, (struct sockaddr*)&broadcast_dest_addr, sizeof(broadcast_dest_addr));//broadcast
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }
            ESP_LOGI(TAG, "Message sent");

            err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&unicast_dest_addr, sizeof(unicast_dest_addr));//unicast
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }
            ESP_LOGI(TAG, "Message sent");

            struct sockaddr_in source_addr; // Large enough for both IPv4 or IPv6
            socklen_t socklen = sizeof(source_addr);
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %s", rx_buffer);
                if (strncmp(rx_buffer, "OK: ", 4) == 0) {
                    ESP_LOGI(TAG, "Received expected message, reconnecting");
                    break;
                }
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}

上述代码,通过UDP,实现广播、单播一次,然后进入接收一次数据;依次循环的功能。

4.组播实现介绍

组播的实现就略微复杂了,要实现组播,至少要经过以下步骤:

1)建立socket_fd

2)socket_fd和指定本地端口绑定

3)加入一个组播组

4)通过sendto / recvfrom进行数据的收发

5)离开组播组

6)关闭socket

注意:服务器和客户端必须都要加入相同的组播地址才可以。涉及到的socket属性主要是以下三个:

广播、组播、单播区别与联系

感兴趣的小伙伴可以留言,我将再出一篇关于组播的博客,谢谢点赞或收藏。

 

 

 

 

上一篇:leave, ret, enter指令的等效


下一篇:极客巅峰2020 virus