1. 概述

在本教程中,我们将探索允许我们发送和接收 UDP 数据包的 DatagramChannel 类。

2. 数据报通道

在 Internet 支持的各种协议中, TCP 和 UDP是最常见的。

TCP 是面向连接的协议, 而 UDP 是面向数据报的协议,性能较高,但可靠性较低由于其不可靠的性质,UDP 通常用于发送广播或多播数据传输

Java NIO 模块DatagramChannel为面向数据报的套接字提供了一个可选择的通道 。换句话说,它允许创建数据报通道来发送和接收数据报(UDP 数据包)。

让我们使用 DatagramChannel 类创建一个通过本地 IP 地址发送数据报的客户端和一个接收数据报的服务器。

3. 打开并绑定

首先,让我们使用 openChannel 方法创建 DatagramChannelBuilder 类,该方法提供打开但未连接的数据报通道:

public class DatagramChannelBuilder {
    public static DatagramChannel openChannel() throws IOException {
        DatagramChannel datagramChannel = DatagramChannel.open();
        return datagramChannel;
    }
}

然后,我们需要将打开的通道绑定到本地地址以侦听入站 UDP 数据包。

因此,我们将添加将 DatagramChannel 绑定到提供的本地地址的 bindChannel 方法:

public static DatagramChannel bindChannel(SocketAddress local) throws IOException {
    return openChannel().bind(local); 
}

现在,我们可以使用 DatagramChannelBuilder 类创建在配置的套接字地址上发送/接收 UDP 数据包的客户端/服务器。

4. 客户端

首先,让我们使用 startClient 方法创建 DatagramClient 类,该方法使用已经讨论过的 DatagramChannelBuilder 类的 bindChannel 方法:

public class DatagramClient {
    public static DatagramChannel startClient() throws IOException {
        DatagramChannel client = DatagramChannelBuilder.bindChannel(null);
        return client;
    }
}

由于 客户端不需要侦听入站 UDP 数据包,因此我们在绑定通道时为地址提供了

然后,我们添加 sendMessage 方法来向服务器地址发送数据报:

public static void sendMessage(DatagramChannel client, String msg, SocketAddress serverAddress) throws IOException {
    ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
    client.send(buffer, serverAddress);
}

就是这样!现在我们准备使用客户端发送消息:

DatagramChannel client = startClient();
String msg = "Hello, this is a Baeldung's DatagramChannel based UDP client!";
InetSocketAddress serverAddress = new InetSocketAddress("localhost", 7001);

sendMessage(client, msg, serverAddress);

注意:由于我们已将消息发送到 localhost:7001 地址,因此我们必须使用相同的地址启动服务器。

5、服务端

同样,让我们使用 startServer 方法创建 DatagramServer 类,以在 localhost:7001 地址上启动服务器:

public class DatagramServer {
    public static DatagramChannel startServer() throws IOException {
        InetSocketAddress address = new InetSocketAddress("localhost", 7001);
        DatagramChannel server = DatagramChannelBuilder.bindChannel(address);
        System.out.println("Server started at #" + address);
        return server;
    }
}

然后,让我们添加 receiveMessage 方法,该方法从客户端接收数据报,提取消息并打印它:

public static void receiveMessage(DatagramChannel server) throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    SocketAddress remoteAdd = server.receive(buffer);
    String message = extractMessage(buffer);
    System.out.println("Client at #" + remoteAdd + "  sent: " + message);
}

另外,要从接收的缓冲区中提取客户端的消息,我们需要添加 extractMessage 方法:

private static String extractMessage(ByteBuffer buffer) {
    buffer.flip();
    byte[] bytes = new byte[buffer.remaining()];
    buffer.get(bytes);
    String msg = new String(bytes);

    return msg;
}

在这里,我们在 ByteBuffer 实例上使用了 Flip 方法,将其从从 I/O 读取翻转为写入 I/O。此外, flip 方法将限制设置为当前位置并将位置设置为零,以便我们从头开始读取。

现在,我们可以启动服务器并接收来自客户端的消息:

DatagramChannel server = startServer();
receiveMessage(server);

因此,服务器收到我们的消息时的打印输出将是:

Server started at #localhost/127.0.0.1:7001
Client at #/127.0.0.1:52580  sent: Hello, this is a Baeldung's DatagramChannel based UDP client!

6.DatagramChannelUnit测试

现在我们已经准备好了客户端和服务器,我们可以编写一个单元测试来验证端到端数据报(UDP 数据包)的传递:

@Test
public void whenClientSendsAndServerReceivesUDPPacket_thenCorrect() throws IOException {
    DatagramChannel server = DatagramServer.startServer();
    DatagramChannel client = DatagramClient.startClient();
    String msg1 = "Hello, this is a Baeldung's DatagramChannel based UDP client!";
    String msg2 = "Hi again!, Are you there!";
    InetSocketAddress serverAddress = new InetSocketAddress("localhost", 7001);

    DatagramClient.sendMessage(client, msg1, serverAddress);
    DatagramClient.sendMessage(client, msg2, serverAddress);

    assertEquals("Hello, this is a Baeldung's DatagramChannel based UDP client!", DatagramServer.receiveMessage(server));
    assertEquals("Hi again!, Are you there!", DatagramServer.receiveMessage(server));
}

首先,我们启动了绑定数据报通道的服务器来侦听 localhost:7001 上的入站消息。然后,我们启动客户端并发送两条消息。

最后,我们在服务器上接收入站消息,并将它们与我们通过客户端发送的消息进行比较。

7. 附加方法

到目前为止,我们已经使用了 DatagramChannel 类提供的 openbindsendreceive 等方法 。现在,让我们快速浏览一下其他方便的方法。

7.1. 配置阻塞

默认情况下,数据报通道是阻塞的。我们可以使用 configureBlocking 方法在传递 false 值时使通道非阻塞:

client.configureBlocking(false);

7.2. isConnected

isConnected 方法返回数据报通道的状态,即它是连接还是断开。

7.3. Socket

socket 方法返回与数据报通道关联的 DatagramSocket 类的对象。

7.4. close

此外,我们可以通过调用 DatagramChannel 类的 close 方法来关闭通道。

八、结论

在本快速教程中,我们探索了 Java NIO 的 DatagramChannel 类,该类允许创建数据报通道来发送/接收 UDP 数据包。

首先,我们检查了一些方法,例如 openbind ,它们同时允许数据报通道侦听入站 UDP 数据包。

然后,我们创建了一个客户端和一个服务器 ,以使用 DatagramChannel 类探索端到端 UDP 数据包传输

与往常一样,源代码可以在 GitHub 上获取。