# 广播给多个收件人

> 原文： [https://docs.oracle.com/javase/tutorial/networking/datagrams/broadcasting.html](https://docs.oracle.com/javase/tutorial/networking/datagrams/broadcasting.html)

除了允许程序相互发送数据包的`DatagramSocket`之外，java.net 还包含一个名为`MulticastSocket`的类。这种套接字在客户端用于侦听服务器向多个客户端广播的数据包。

让我们重写引用服务器，以便它将`DatagramPacket`广播给多个收件人。新服务器现在需要定期广播引号，而不是向发出请求的特定客户端发送引号。客户端需要进行修改，以便被动地侦听引号，并在`MulticastSocket`上进行修改。

该实例由三个类组成，这三个类是来自前一个实例的三个类的修改： [`MulticastServer`](examples/MulticastServer.java) ， [`MulticastServerThread`](examples/MulticastServerThread.java) 和 [`MulticastClient`](examples/MulticastClient.java) 。本讨论重点介绍了这些类的有趣部分。

这是服务器主程序的新版本。此代码与先前版本`QuoteServer`之间的差异以粗体显示：

```
import java.io.*;

public class MulticastServer {
    public static void main(String[] args) throws IOException {
        new MulticastServerThread().start();
    }
}

```

基本上，服务器获得一个新名称并创建`MulticastServerThread`而不是`QuoteServerThread`。现在让我们看一下包含服务器核心的`MulticastServerThread`。这是它的类声明：

```
public class MulticastServerThread extends QuoteServerThread {
    // ...
}

```

我们已经使这个类成为`QuoteServerThread`的子类，因此它可以使用构造函数，并继承一些成员变量和`getNextQuote`方法。回想一下`QuoteServerThread`创建一个绑定到端口 4445 的`DatagramSocket`并打开引用文件。在这个例子中，`DatagramSocket`的端口号实际上并不重要，因为客户端从不向服务器发送任何内容。

在`MulticastServerThread`中明确实现的唯一方法是其`run`方法。 `run`方法与`QuoteServerThread`方法之间的差异以粗体显示：

```
public void run() {
    while (moreQuotes) {
        try {
            byte[] buf = new byte[256];
            // don't wait for request...just send a quote

            String dString = null;
            if (in == null)
                dString = new Date().toString();
            else
                dString = getNextQuote();
            buf = dString.getBytes();

            InetAddress group = InetAddress.getByName("203.0.113.0");
            DatagramPacket packet;
            packet = new DatagramPacket(buf, buf.length, group, 4446);
            socket.send(packet);

            try {
                sleep((long)Math.random() * FIVE_SECONDS);
            } 
            catch (InterruptedException e) { }
        }
        catch (IOException e) {
            e.printStackTrace();
            moreQuotes = false;
        }
    }
    socket.close();
}

```

有趣的变化是如何构造`DatagramPacket`，特别是用于构建`DatagramPacket`的`InetAddress`和端口。回想一下前一个例子从客户端发送到服务器的数据包中检索了`InetAddress`和端口号。这是因为服务器需要直接回复客户端。现在，服务器需要解决多个客户端。所以这次`InetAddress`和端口号都是硬编码的。

硬编码端口号为 4446（客户端必须有`MulticastSocket`绑定到此端口）。 `DatagramPacket`的硬编码`InetAddress`是“203.0.113.0”并且是组标识符（而不是运行单个客户端的计算机的 Internet 地址）。此特定地址是为保留此目的而从保留中任意选择的。

以这种方式创建，`DatagramPacket`将发往所有侦听端口号 4446 的客户端，该端口号是“203.0.113.0”组的成员。

要侦听端口号 4446，新客户端程序刚刚使用该端口号创建了`MulticastSocket`。要成为“203.0.113.0”组的成员，客户端使用标识该组的`InetAddress`调用`MulticastSocket`的`joinGroup`方法。现在，客户端设置为接收发往指定端口和组的`DatagramPacket`。这是来自新客户端程序的相关代码（也被重写为被动接收引号而不是主动请求它们）。粗体语句是与`MulticastSocket`交互的语句：

```
MulticastSocket socket = new MulticastSocket(4446);
InetAddress group = InetAddress.getByName("203.0.113.0");
socket.joinGroup(group);

DatagramPacket packet;
for (int i = 0; i < 5; i++) {
    byte[] buf = new byte[256];
    packet = new DatagramPacket(buf, buf.length);
    socket.receive(packet);

    String received = new String(packet.getData());
    System.out.println("Quote of the Moment: " + received);
}

socket.leaveGroup(group);
socket.close();

```

请注意，服务器使用`DatagramSocket`来广播客户端通过`MulticastSocket`接收的数据包。或者，它可以使用`MulticastSocket`。服务器用来发送`DatagramPacket`的套接字并不重要。广播数据包时重要的是`DatagramPacket`中包含的寻址信息，以及客户端用来监听它的套接字

* * *

**Try this:** 

运行`MulticastServer`和几个客户端。观察客户如何获得相同的报价。

* * *