Java中的UDP通信(附带实例)
用户数据报协议(UDP)是网络信息传输的另一种形式。基于 UDP 的通信和基于 TCP 的通信不同,基于 UDP 的信息传递更快,但不提供可靠性保证。
使用 UDP 传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。虽然 UDP 是一种不可靠的协议,但如果需要较快地传输信息,并能容忍小的错误,可以考虑使用 UDP。
基于 UDP 通信的基本模式如下:
发送数据包的步骤如下:
接收数据包的步骤如下:
注意,DatagramSocket 类的 receive() 方法接收数据时,如果还没有可以接收的数据,那么在正常情况下 receive() 方法将阻塞,一直等到网络上有数据传来,receive()方 法接收该数据并返回。如果网络上没有数据发送过来,receive() 方法也没有阻塞,那么肯定是程序有问题,大多数情况下是因为使用了一个被其他程序占用的端口号。
第一种构造方法在创建 DatagramPacket 对象时,指定了数据包的内存空间和大小。第二种构造方法不仅指定了数据包的内存空间和大小,还指定了数据包的目标地址和端口。在发送数据时,必须指定接收方的 Socket 地址和端口号,因此使用第二种构造方法可创建发送数据的 DatagramPacket 对象。
如果接收数据时必须指定一个端口号,不允许系统随机产生,此时可以使用第二种构造方法。比如有个朋友要你给他写信,那他的地址就必须确定,不确定是不行的。在发送数据时通常使用第一种构造方法,不指定端口号,而是由系统为我们分配一个端口号,就像寄信不需要到指定的邮局去寄一样。
广播数据报是一项较新的技术,其原理类似于广播电台。广播电台需要在指定的波段和频率上广播信息,收听者也要将收音机调到指定的波段、频率,才可以收听广播内容。
【实例】创建 UDP 协议广播电台程序。主机不断地重复播出节目预报,使加入同一组内的主机随时可接收到广播信息。接收者将正在接收的信息放在一个文本域中,并将已接收到的所有信息存储在另一个文本域中。
1) 广播主机程序不断地向外播出信息,代码如下:
2) 接收广播程序。单击“开始接收”按钮,系统开始接收主机播出的信息;单击“停止接收”按钮,系统停止接收广播主机播出的信息。代码如下:

图 1 接收广播的运行结果
发出广播和接收广播的主机地址必须位于同一个组内,地址范围为 224.0.0.0~224.255.255.255,该地址并不代表某个特定主机的位置。加入同一个组的主机可以在某个端口上广播信息,也可以在某个端口上接收信息。
使用 UDP 传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。虽然 UDP 是一种不可靠的协议,但如果需要较快地传输信息,并能容忍小的错误,可以考虑使用 UDP。
基于 UDP 通信的基本模式如下:
- 将数据进行打包(被称为数据包),然后将数据包发往目的地;
- 接收别人发来的数据包,然后查看数据包。
发送数据包的步骤如下:
- 使用 DatagramSocket() 创建一个数据包套接字;
- 使用 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 创建要发送的数据包;
- 使用 DatagramSocket 类的 send() 方法发送数据包。
接收数据包的步骤如下:
- 使用 DatagramSocket(int port) 创建数据包套接字,绑定到指定的端口上;
- 使用 DatagramPacket(byte[] buf, int length) 创建字节数组来接收数据包;
- 使用 DatagramPacket类的receive() 方法接收 UDP 包。
注意,DatagramSocket 类的 receive() 方法接收数据时,如果还没有可以接收的数据,那么在正常情况下 receive() 方法将阻塞,一直等到网络上有数据传来,receive()方 法接收该数据并返回。如果网络上没有数据发送过来,receive() 方法也没有阻塞,那么肯定是程序有问题,大多数情况下是因为使用了一个被其他程序占用的端口号。
Java DatagramPacket类
java.net 包的 DatagramPacket 类用来表示数据包。DatagramPacket 类的构造方法如下:- DatagramPacket(byte[] buf, int length);
- DatagramPacket(byte[] buf, int length, InetAddress address, int port)。
第一种构造方法在创建 DatagramPacket 对象时,指定了数据包的内存空间和大小。第二种构造方法不仅指定了数据包的内存空间和大小,还指定了数据包的目标地址和端口。在发送数据时,必须指定接收方的 Socket 地址和端口号,因此使用第二种构造方法可创建发送数据的 DatagramPacket 对象。
Java DatagramSocket类
java.net 包中的 DatagramSocket 类用于表示发送和接收数据包的套接字。该类的构造方法如下:DatagramSocket() DatagramSocket(int port) DatagramSocket(int port, InetAddress addr)
- 第一种构造方法创建 DatagramSocket 对象,构造数据报套接字,并将其绑定到本地主机任何可用的端口上;
- 第二种构造方法创建 DatagramSocket 对象,创建数据报套接字,并将其绑定到本地主机的指定端口上;
- 第三种构造方法创建 DatagramSocket 对象,创建数据报套接字,并将其绑定到指定的端口和指定的本地地址上,这种构造方法适用于有多块网卡和多个 IP 地址的情况。
如果接收数据时必须指定一个端口号,不允许系统随机产生,此时可以使用第二种构造方法。比如有个朋友要你给他写信,那他的地址就必须确定,不确定是不行的。在发送数据时通常使用第一种构造方法,不指定端口号,而是由系统为我们分配一个端口号,就像寄信不需要到指定的邮局去寄一样。
UDP网络程序设计
根据前面所讲的网络编程的基本知识以及 UDP 网络编程的特点,下面创建一个广播数据报程序。广播数据报是一项较新的技术,其原理类似于广播电台。广播电台需要在指定的波段和频率上广播信息,收听者也要将收音机调到指定的波段、频率,才可以收听广播内容。
【实例】创建 UDP 协议广播电台程序。主机不断地重复播出节目预报,使加入同一组内的主机随时可接收到广播信息。接收者将正在接收的信息放在一个文本域中,并将已接收到的所有信息存储在另一个文本域中。
1) 广播主机程序不断地向外播出信息,代码如下:
import java.io.IOException; import java.net.*; public class Notification extends Thread { String weather = "节目预报:8点有大型晚会,请收听"; // 发送的消息 int port = 9898; // 端口 InetAddress address = null; // 广播组地址 MulticastSocket socket = null; // 多点广播套接字 Notification() { try { address = InetAddress.getByName("224.255.10.0"); // 广播组地址 socket = new MulticastSocket(port); // 实例化多点广播套接字 socket.setTimeToLive(1); // 指定发送范围是本地网络 socket.joinGroup(address); // 加入广播组 } catch (IOException e) { e.printStackTrace(); // 输出异常信息 } } public void run() { while (true) { DatagramPacket packet = null; // 数据包 byte data[] = weather.getBytes(); // 字符串消息的字节数组 packet = new DatagramPacket(data, data.length, address, port); // 将数据进行打包 System.out.println(weather); // 在控制台中输出消息 try { socket.send(packet); // 发送数据 Thread.sleep(3000); // 线程休眠 } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Notification w = new Notification(); // 创建线程 w.start(); // 启动线程 } }运行结果为:
节目预报:8点有大型晚会,请收听
节目预报:8点有大型晚会,请收听
节目预报:8点有大型晚会,请收听
节目预报:8点有大型晚会,请收听
2) 接收广播程序。单击“开始接收”按钮,系统开始接收主机播出的信息;单击“停止接收”按钮,系统停止接收广播主机播出的信息。代码如下:
import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.net.*; import javax.swing.*; public class Receive extends JFrame implements Runnable, ActionListener { int port; InetAddress group = null; MulticastSocket socket = null; JButton inceBtn = new JButton("开始接收"); JButton stopBtn = new JButton("停止接收"); JTextArea inceAr = new JTextArea(10, 10); // 显示接收广播的文本域 JTextArea inced = new JTextArea(10, 10); Thread thread; boolean stop = false; // 停止接收信息的状态 public Receive() { setTitle("广播数据报"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); thread = new Thread(this); inceBtn.addActionListener(this); stopBtn.addActionListener(this); inceAr.setForeground(Color.blue); JPanel north = new JPanel(); // 将按钮添加到面板 north 上 north.add(inceBtn); north.add(stopBtn); add(north, BorderLayout.NORTH); // 将 north 放置在窗体的上部 JPanel center = new JPanel(); // 创建面板对象 center center.setLayout(new GridLayout(1, 2)); // 设置面板布局 center.add(inceAr); center.add(inced); add(center, BorderLayout.CENTER); // 设置面板布局 validate(); // 刷新 port = 9898; // 设置端口号 try { group = InetAddress.getByName("224.255.10.0"); socket = new MulticastSocket(port); socket.joinGroup(group); } catch (IOException e) { e.printStackTrace(); // 输出异常信息 } setBounds(100, 50, 360, 380); // 设置布局 setVisible(true); // 将窗体设置为显示状态 } public void run() { // run()方法 while (!stop) { byte data[] = new byte[1024]; DatagramPacket packet = null; packet = new DatagramPacket(data, data.length, group, port); // 待接收的数据包 try { socket.receive(packet); // 接收数据包 // 获取数据包中的内容 String message = new String(packet.getData(), 0, packet.getLength()); inceAr.setText("正在接收的内容:\n" + message); // 将接收内容显示在文本域中 inced.append(message + "\n"); // 每条信息为一行 } catch (IOException e) { e.printStackTrace(); // 输出异常信息 } } } public void actionPerformed(ActionEvent e) { // 单击 ince 按钮触发的事件 if (e.getSource() == inceBtn) { inceBtn.setBackground(Color.red); // 设置按钮颜色 stopBtn.setBackground(Color.yellow); // 设置按钮颜色 if (!(thread.isAlive())) { // 如线程不处于“新建状态” thread = new Thread(this); // 实例化 Thread 对象 } thread.start(); // 启动线程 stop = false; // 开始接收信息 } if (e.getSource() == stopBtn) { inceBtn.setBackground(Color.yellow); // 设置按钮颜色 stopBtn.setBackground(Color.red); // 设置按钮颜色 stop = true; // 停止接收信息 } } public static void main(String[] args) { Receive rec = new Receive(); rec.setSize(460, 200); } }运行结果为:

图 1 接收广播的运行结果
发出广播和接收广播的主机地址必须位于同一个组内,地址范围为 224.0.0.0~224.255.255.255,该地址并不代表某个特定主机的位置。加入同一个组的主机可以在某个端口上广播信息,也可以在某个端口上接收信息。