首页 > 编程笔记 > Java笔记 阅读:13

Java UDP通信详解(附带实例)

分别位于两台计算机上的两个程序的数据交换,必须通过一个双向的通信连接实现,这个双向通信连接的一端称为一个 Socket。

Socket 通常用来实现客户方和服务方的连接,它是 TCP/IP 协议的一个十分流行的编程界面。Java 使用 Socket 来代表通信实例双方的通信端口,并通过 Socket 产生 I/O 流来进行网络通信。一个 Socket 由一个 IP 地址和一个端口号唯一确定。

UDP 协议是英文 User Datagram Protocol 的缩写,即用户数据报文协议。UDP协议是一种不可靠的网络协议,它在通信实例双方各自创建一个 Socket,但是这两个 Socket 之间并没有虚拟链路,只是发送、接收数据报文的对象。

Java UDP是什么

UDP 在通信时没有创建虚拟链路,所以是一种面向无连接的协议。UDP 通信的过程就像是邮政公司在两个网点之间进行邮件配送一样,在网点发送和接收邮件时都需要使用快递车来装载货物。

在使用 UDP 通信过程中,发送和接收的数据也需要使用“快递车”进行统一装车运输,所以 Java 中提供了一个 DatagramPacket 类,这个类的实例对象就相当于一个快递车,用于封装 UDP 通信中发送或者接收的数据。然而运输邮件只有快递车是不够的,还需要有邮政网点用来承载。

为此,Java 提供了与之对应的 DatagramSocket 类,这个类的作用就相当于各个网点。使用这个类的实例对象就可以发送和接收 DatagramPacket 数据报,发送和接收数据的过程如下图所示。


图 1 UDP传输原理

Java DatagramPacket类

UDP 在发送数据的时候,需要先使用 java.net 包中的 DatagramPacket 类将数据封装成数据包,该类的构造方法如下表所示。

表:DatagramPacket类的构造方法
构造方法 方法描述
public DatagramPacket(byte[] buf, int length) 构造 DatagramPacket 对象,用于接收长度为 length 数据包
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造一个数据报包,用于将长度为 length 的数据包发送到指定主机上的指定端口号
public DatagramPacket(byte[] buf, int offset, int length) 构造 DatagramPacket 对象,用于接收长度为 length 且偏移量为 offset 的数据包
public DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 构造一个数据报包,用于将长度为 length 且偏移量为 offset 的数据包发送到指定主机上的指定端口号
public DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 构造一个数据报包,用于将长度为 length 且偏移量为 offset 的数据包发送到指定主机上的指定端口号
public DatagramPacket(byte[] buf, int length, SocketAddress address) 构造一个数据报包,用于将长度为 length 的数据包发送到指定主机上的指定端口号

DatagramPacket 还有一些常用方法,如下表所示:

表:DatagramPacket类的常用方法
方法 方法描述
InetAddress getAddress() 返回发送此数据报或接收数据报的计算机的 IP 地址
byte[] getData() 返回数据缓冲区
int getLength() 返回将要发送或接收到的数据报的长度
int getPort() 返回发送此数据报或接收数据报的远程主机上的端口号
SocketAddress getSocketAddress() 获取接收或发出此数据报的远程主机的 SocketAddress 对象(通常为 IP 地址+端口号)

Java DatagramSocket类

DatagramSocket 类是与 DatagramPacket 类关系密切的一个类,它位于 java.net 包中,是数据报文通信的 Socket,包含了源 IP 地址和目的 IP 地址以及源端口号和目的端口号,用于创建发送端和接收端对象,但是在创建发送端和接收端对象时,需要使用不同的构造方法。

DatagramSocket 类的构造方法如下表所示:

表:DatagramSocket类的构造方法
构造方法 方法描述
public DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口(1~65535)
protected DatagramSocket(DatagramSocketImpl impl) 使用指定的 DatagramSocketImpl 对象创建未绑定的数据报套接字
public DatagramSocket(int port) 构造一个数据报套接字并将其绑定到本地主机上的指定端口
public DatagramSocket(int port, InetAddress laddr) 构造数据报套接字,将其绑定到指定的本地地址
public DatagramSocket(SocketAddress bindaddr) 构造数据报套接字,将其绑定到指定的本地套接字地址

DatagramSocket 类还有一些常用方法,如下表所示:

表:DatagramSocket类的常用方法
方法 方法描述
int getPort() 返回此数据报文套接字连接的端口
boolean isConnected() 返回此数据报文套接字的连接状态
void receive(DatagramPacket p) 从此数据报文套接字接收数据报包
void send(DatagramPacket p) 从此数据报文套接字发送数据报包
void close() 关闭此数据报文套接字

Java UDP网络程序

前面讲解了 DatagramPacket 类和 DatagramSocket 类,接下来讨论它们的使用。

这里需要创建一个发送端程序,一个接收端程序,在运行程序时,必须首先运行接收端程序才能接收来自发送端程序发送的信息,接收端程序如下例所示。

【实例 1】Demo.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 创建DatagramSocket对象,并且为它创建端口号8888
        DatagramSocket socket = new DatagramSocket(8888);
        // 创建一个长度1024字节的数组,用来接收发送端发送的数据
        byte[] array = new byte[1024];
        // 创建一个DatagramPacket对象,用来存放数据
        DatagramPacket packet = new DatagramPacket(array, array.length);
        // 验证DatagramSocket是否做好接收数据的准备
        System.out.println("等待接收数据");
        // DatagramSocket对象等待接收发送端的数据,如果等不到数据,会发生阻塞,一直等下去
        socket.receive(packet);
        // 定义一个字符串,用来获取DatagramPacket对象接收到的源数据
        String s = new String(packet.getData(), 0, packet.getLength());
        // 打印发送端的IP地址、端口号以及发送过来的数据
        System.out.println("IP地址" + packet.getAddress().getHostAddress() +
                "里的端口号" + packet.getPort() + "告诉您:" + s);
        socket.close();
    }
}
程序的运行结果如下:

等待接收数据

实例中,先创建了 DatagramSocket 对象,并指定它的端口号为 8888。监听 8888 端口,然后创建大小为 1024 字节的数组用来接收数据,并创建 DatagramPacket 对象用于存放数据。最后调用 receive(DatagramPacket p) 方法等待接收数据,接收到数据以后,数据会填充到 DatagramPacket 对象中,并输出。

但是我们从运行结果可以看到,程序在接收数据时发生了阻塞,不再往下运行,这是因为我们只创建了接收端,并没有数据传输过来,端口会一直等待数据,直到数据传送过来为止。

接下来,我们创建一个发送端,用来发送数据,如下例所示。

【实例 2】Demo.java
import java.net.*;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 创建DatagramSocket对象,作为发送端端口,并指明其端口号为8090
        DatagramSocket socket = new DatagramSocket(8090);
        // 定义我们要发送的数据
        String str = "好好学习天天向上";
        /**
         * 创建DatagramPacket对象,用来向接收端发送数据
         * str.getBytes():因为我们使用的是字节流传输,所以要将字符串转换为字节
         * 指定接收端IP为本机,端口号为8888
         */
        DatagramPacket packet = new DatagramPacket(str.getBytes(), 0,
                str.getBytes().length, InetAddress.getByName("localhost"), 8888);
        System.out.println("发送消息");
        // 通过send()方法将数据添加到DatagramSocket对象中,发送出去
        socket.send(packet);
        // 关闭DatagramSocket对象,释放资源
        socket.close();
    }
}
程序的运行结果如下:

等待接收数据

程序中,先创建了 DatagramSocket 对象,并为它指定端口号为 8090,使用这个端口向接收端发送数据。然后将要发送的字符串转换为字节数组作为要发送的数据,因为我们这里是模拟,因此使用本机 IP 作为接收端的 IP 号,并且指定接收端端口号为 8888。

注意,我们在这里指定的端口号必须与我们创建的接收端监听的端口号一致。最后调用 send(DatagramPacket p) 方法发送数据。

在创建好发送端以后,先运行我们创建好的接收端程序,让其进入阻塞状态,再运行发送端程序,这时接收端程序就会结束阻塞状态,程序的运行结果如下:

等待接收数据
IP地址127.0.0.1里的端口号8090告诉您:好好学习天天向上

运行结果打印出了接收端接收到的数据信息,接收到字符串“好好学习天天向上”,它来自 IP 地址 127.0.0.1,也就是我们本机的 IP 号。端口号为 8090 的发送端,这里的 8090 就是在我们在程序中第 7 行代码指定的端口号。

另外,在创建自定义端口时,可能会出现异常。比如在运行实例 1 时,程序运行会报错,提示“Address already in use: Cannot bind”,中文含义为“端口已经被占用”,出现错误的原因在于,操作系统中的端口 8888 存在冲突。

要解决这个问题,如果这个程序不太重要,只需要把占用端口的程序关闭就可以了。首先我们要查询是哪个程序在占用端口,打开cmd命令行窗口,输入命令“netstat -ano | findstr 8888”查询当前设备所有用到端口号 8888 的进程,如下图所示。


图 2 终端查询结果

图 2 中的界面显示的是使用 cmd 命令行窗口找到的端口号和它对应的 PID,其中 8888 端口已经被占用,占用这个端口号的程序的 PID 为 5260。然后按 Ctrl+Alt+Delete 组合键,打开 Windows 任务管理器,选择“详细信息”选项卡,如下图所示。


图 3 Windows任务管理器

找到任务管理器后,单击 PID 选项,让其顺序排列,然后找到我们要找的进程,右击,在弹出的快捷菜单中选择“结束任务”命令即可,如下图所示。这时再运行例 1 就正常了,不会出现端口被占用的现象。


图 4 关闭进程

相关文章