1. 概述

在这篇文章中,我们将探讨使用Java进行网络通信,特别是在用户数据报协议(User Datagram Protocol,简称UDP)上。

UDP是一种通信协议,它通过网络传输独立的数据包,不保证它们一定能送达,也不保证接收顺序。

互联网上的大部分通信通常通过传输控制协议(Transmission Control Protocol,简称TCP)进行,但UDP也有其特定的应用场景,我们将在下一节中讨论。

2. 为什么使用UDP?

UDP与更常见的TCP有很大的不同。在考虑UDP表面级别的缺点之前,重要的是理解其较低的开销可以使其比TCP快得多。

除了速度,我们还需要记住,有些类型的通信并不需要TCP的可靠性,而是更重视低延迟。视频是一个可能从使用UDP而不是TCP中受益的应用的一个例子。

3. 构建UDP应用程序

构建UDP应用程序与构建TCP系统非常相似;唯一的区别在于客户端和服务器之间不需要建立点对点连接。

设置也非常直观。Java内置了支持UDP的网络功能,它位于java.net包中。因此,为了通过UDP执行网络操作,我们只需要导入java.net包中的类:java.net.DatagramSocketjava.net.DatagramPacket

在接下来的章节中,我们将学习如何设计通过UDP通信的应用程序;我们将使用流行的回声协议来构建这个应用。

首先,我们将创建一个回声服务器,它会将接收到的任何消息发送回去,然后是发送任意消息到服务器的回声客户端,最后我们将测试应用程序以确保一切正常工作。

4. 服务器

在UDP通信中,一个单独的消息被封装在一个DatagramPacket中,通过DatagramSocket发送。

让我们从创建一个简单的服务器开始:

public class EchoServer extends Thread {

    private DatagramSocket socket;
    private boolean running;
    private byte[] buf = new byte[256];

    public EchoServer() {
        socket = new DatagramSocket(4445);
    }

    public void run() {
        running = true;

        while (running) {
            DatagramPacket packet 
              = new DatagramPacket(buf, buf.length);
            socket.receive(packet);
            
            InetAddress address = packet.getAddress();
            int port = packet.getPort();
            packet = new DatagramPacket(buf, buf.length, address, port);
            String received 
              = new String(packet.getData(), 0, packet.getLength());
            
            if (received.equals("end")) {
                running = false;
                continue;
            }
            socket.send(packet);
        }
        socket.close();
    }
}

我们创建一个全局的DatagramSocket,在整个过程中用于发送数据包,一个字节数组来包裹我们的消息,以及一个名为running的状态变量。

为了简化,服务器继承自Thread,因此我们将所有代码都放在run方法中实现。

run方法中,我们创建一个循环,只要running没有因错误或客户端发送的终止消息而变为false,就一直运行。

在循环顶部,我们实例化一个DatagramPacket来接收传入的消息。

接下来,我们调用socket的receive方法。此方法会阻塞直到有消息到达,并将消息存储在传递给它的DatagramPacket的字节数组中。

接收消息后,我们获取客户端的地址和端口,因为我们将响应发送回客户端。

然后,我们为向客户端发送消息创建一个DatagramPacket。注意发送包的签名与接收包的不同。这个包也需要我们指定发送的目标客户端的地址和端口。

5. 客户端

现在让我们创建一个简单的客户端来匹配这个新服务器:

public class EchoClient {
    private DatagramSocket socket;
    private InetAddress address;

    private byte[] buf;

    public EchoClient() {
        socket = new DatagramSocket();
        address = InetAddress.getByName("localhost");
    }

    public String sendEcho(String msg) {
        buf = msg.getBytes();
        DatagramPacket packet 
          = new DatagramPacket(buf, buf.length, address, 4445);
        socket.send(packet);
        packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);
        String received = new String(
          packet.getData(), 0, packet.getLength());
        return received;
    }

    public void close() {
        socket.close();
    }
}

代码与服务器的大致相同。我们有全局的DatagramSocket和服务器的地址。我们在构造函数中初始化这些变量。

我们有一个单独的方法,用于向服务器发送消息并返回响应。

首先,我们将字符串消息转换为字节数组,然后为发送消息创建一个DatagramPacket

接着,我们发送消息。发送后,我们将DatagramPacket立即转换为接收模式。

当回声到达时,我们将字节转换回字符串并返回。

6. 测试

UDPTest.java类中,我们简单地创建一个测试来检查我们的两个应用程序的回声功能:

public class UDPTest {
    EchoClient client;

    @Before
    public void setup(){
        new EchoServer().start();
        client = new EchoClient();
    }

    @Test
    public void whenCanSendAndReceivePacket_thenCorrect() {
        String echo = client.sendEcho("hello server");
        assertEquals("hello server", echo);
        echo = client.sendEcho("server is working");
        assertFalse(echo.equals("hello server"));
    }

    @After
    public void tearDown() {
        client.sendEcho("end");
        client.close();
    }
}

setup方法中,我们启动服务器并创建客户端。而在tearDown方法中,我们向服务器发送一个终止消息,以便它关闭,同时我们也关闭客户端。

7. 总结

在这篇文章中,我们了解了用户数据报协议,并成功构建了自己的客户端-服务器应用程序,它们通过UDP进行通信。

要获取本文中使用的示例的完整源代码,您可以查看GitHub项目