Широковещательная рассылка

Последнее обновление: 08.11.2022

Протокол UDP позволяет рассылать сообщения с помощью широковещательной групповой рассылки. При такой рассылке клиенту достаточно отправить одно сообщение, и его получат все остальные клиенты, которые подключены к группе. И нет надобности отправлять одно сообщение каждому отдельному клиенту.

При использовании широковещательной передачи надо учитывать, что передача не идет дальше локальных сетей, так как маршрутизаторы подобные рассылки не пропускают.

Присоединение к группе. JoinMulticastGroup и DropMulticastGroup

Широковещательная групповая рассылка доступна только для группы. Чтобы добавить клиент UdpClient в группу, у него вызывается метод JoinMulticastGroup(). Одна из его версий:

public void JoinMulticastGroup (System.Net.IPAddress multicastAddr);
public void JoinMulticastGroup (System.Net.IPAddress multicastAddr, int timeToLive);

В качестве параметра в метод передается адрес группы. В качестве адреса может использоваться один из адресов в диапазоне от 224.0.0.0 до 239.255.255.255. Если будет передан другой адрес, либо если роутер не поддерживает групповую рассылку, то приложение сгенерирует исключение SocketException.

Второй параметр - timeToLive представляет число проходов через маршрутизаторы. То есть пока сообщение достигнет получателя, оно может пройти через некоторое число маршрутизаторов, которые направят его по нужному пути. Например, число 50 означает, что чтобы пройти от компьютера-отправителя к компьютеру-получателю, дейтаграмма может миновать 50 маршрутизаторов.

После вызова этого метода неявно используемый объект Socket посылает роутеру пакет данных в формате IGMP (Internet Group Management Protocol) для присоединения к группе. И далее объект UdpClient сможет получать дейтаграммы, которые предназначаются всей группы объектов UdpClient.

Стоит отметить, что для присоединения к группе UdpClient должен быть привязан к определенному порту, по которому его сокет будет прослушивать входящиее сообщения. Для этого в конструктор UdpClient можно передать номер порта. Кроме того, присоединение к группе необходимо только для получения сообщений, которые предназначаются группе. А для отправки сообщений в группу необязательно принадлежать группе.

Для удаления из группы у UdpClient вызывается метод DropMulticastGroup():

public void DropMulticastGroup (System.Net.IPAddress multicastAddr);

В метод передается тот же адрес группы, который передавался в JoinMulticastGroup:

using var udpClient = new UdpClient();
var brodcastAddress = IPAddress.Parse("235.5.5.11"); ; // хост для отправки данных

udpClient.JoinMulticastGroup(brodcastAddress);
// .... рассылка и получение сообщений
udpClient.DropMulticastGroup(brodcastAddress);

Рассмотрим на небольшом примере. Сначала определим Udp-клиент, который будет получать сообщения по широковещательной рассылке:

using System.Net.Sockets;
using System.Net;
using System.Text;

using var udpClient = new UdpClient(8001);
var brodcastAddress = IPAddress.Parse("235.5.5.11"); ; // хост для отправки данных 
// присоединяемся к группе
udpClient.JoinMulticastGroup(brodcastAddress);
Console.WriteLine("Начало прослушивания сообщений");
while (true)
{
    var result = await udpClient.ReceiveAsync();
    string message = Encoding.UTF8.GetString(result.Buffer);
    if (message == "END") break;
    Console.WriteLine(message);
}
// отсоединяемся от группы
udpClient.DropMulticastGroup(brodcastAddress);
Console.WriteLine("Udp-клиент завершил свою работу");

Здесь клиент прослушивает подключения на порту 8001. Кроме того, он подключен к группе по адресу "235.5.5.11".

В цикле клиент получает сообщения, которые посылаются в группу. И если прислана команда "END", то выходит из цикла и завершает работу.

Таким образом, данный клиент будет получать сообщения из группы "235.5.5.11:8001". Для теста определим программу-отправитель, которая будет отправлять данные в эту группу:

using System.Net;
using System.Net.Sockets;
using System.Text;

var messages = new string[] { "Hello World!", "Hello METANIT.COM", "Hello work", "END" };
var brodcastAddress = IPAddress.Parse("235.5.5.11"); ; // хост для отправки данных 
using var udpSender = new UdpClient();
Console.WriteLine("Начало отправки сообщений...");
// отправляем сообщения
foreach(var message in messages)
{
    Console.WriteLine($"Отправляется сообщение: {message}");
    byte[] data = Encoding.UTF8.GetBytes(message);
    await udpSender.SendAsync(data, new IPEndPoint(brodcastAddress, 8001));
    await Task.Delay(1000);
}

Здесь обычный клиент UdpClient отправляет все сообщения из массива messages. При этом UdpClient не добавлен ни в какую группу. Но обратите внимание на адрес отправки - 235.5.5.11:8001 - тот же адрес, по которому выше определенный клиент получает сообщения из группы.

Запустим сначала программу Udp-клиента, который получает сообщения из группы. А затем запустим программу-отправитель. Консоль отправителя отобразит процесс отправки сообщений:

Начало отправки сообщений...
Отправляется сообщение: Hello World!
Отправляется сообщение: Hello METANIT.COM
Отправляется сообщение: Hello work
Отправляется сообщение: END

а консоль программы клиента-получателя отобразит процесс получения данных:

Начало прослушивания сообщений
Hello World!
Hello METANIT.COM
Hello work
Udp-клиент завершил свою работу

В данном случае для простоты можно запустить оба приложения на одном и тот же компьютере. Но даже если мы запустим на разных компьютерах локальной сети программу-получатель сообщений, все они одновременно получат сообщения, так как добавлены в одну и ту же группу.

Получение и отправка сообщений в одном приложении

Одно и то же приложение, которое входит в группу, может посылать и получать сообщения. Но здесь есть некоторые особенности. Так, для теста создадим консольный чат с применением широковещательной рассылки:

using System.Net;
using System.Net.Sockets;
using System.Text;

int localPort = 8001;
IPAddress brodcastAddress = IPAddress.Parse("235.5.5.11");
Console.Write("Введите свое имя: ");
string? username = Console.ReadLine();

Task.Run(ReceiveMessageAsync);
await SendMessageAsync();

// отправка сообщений в группу
async Task SendMessageAsync()
{
    using var sender = new UdpClient(); // создаем UdpClient для отправки
    // отправляем сообщения
    while (true)
    {
        string? message = Console.ReadLine(); // сообщение для отправки
        // если введена пустая строка, выходим из цикла и завершаем ввод сообщений
        if (string.IsNullOrWhiteSpace(message)) break;
        // иначе добавляем к сообщению имя пользователя
        message = $"{username}: {message}";
        byte[] data = Encoding.UTF8.GetBytes(message);
        // и отправляем в группу
        await sender.SendAsync(data, new IPEndPoint(brodcastAddress, localPort));
    }
}
// получение сообщений из группы
async Task ReceiveMessageAsync()
{
    using var receiver = new UdpClient(localPort); // UdpClient для получения данных
    receiver.JoinMulticastGroup(brodcastAddress);
    receiver.MulticastLoopback = false; // отключаем получение своих же сообщений
    while (true)
    {
        var result = await receiver.ReceiveAsync();
        string message = Encoding.UTF8.GetString(result.Buffer);
        Console.WriteLine(message);
    }
}

Теперь для групповой рассылки в локальной сети будет использоваться адрес "235.5.5.11", в качестве порта - 8001.

После ввода имени пользователя запускается новая задача, которая выполняет метод ReceiveMessageAsync() для получения данных. В этом методе создается UdpClient для получения данных. Он присоединяется к группе. И важный момент, чтобы сообщения этого же клиента не транслировались ему самому, устанавливаем false для свойства MulticastLoopback:

receiver.MulticastLoopback = false;

Далее в бесконечном цикле считываем входящие сообщения и выводим их на консоль.

Также в основном потоке запускается асинхронный метод SendMessageAsync(). В нем в бесконечном цикле считываем с консоли введенное сообщение и отправляем в группу, добавляя к сообщению имя пользователя. Если же пользователь ввел пустую строку, то выходим из цикла и тем самым завершаем выполнение метода и всего приложения.

После построения проекта мы можем запустить скомпилированное приложение на разных машинах в локальной сети.

Первый клиент:

Введите свое имя: Евгений
Олег: привет чат
привет Олег
Олег: тест

Второй клиент:

Введите свое имя: Олег
привет чат
Евгений: привет Олег
тест
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850