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

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

UDP 是一种无连接的网络通信机制,更像邮件或短信息通信方式。

和基于 TCP 协议的通信不同,基于 UDP 协议的信息传递更快,但不提供可靠性保证。也就是说,数据在传输时,用户无法知道数据能否正确到达目的地主机,也不能确定数据到达目的地的顺序是否和发送的顺序相同。

UDP 通信好比邮递信件。发信人不能确定所发的信件一定能够到达目的地,也不能确定信件到达的顺序是发出时的顺序,可能因为某种原因导致后发出的信件先到达。另外,也不能确定对方收到信就一定会回信。

尽管 UDP 是一种不可靠的通信协议,但由于其有较快的传输速度,在应用能容忍小错误的情况下,可以考虑使用 UDP 通信机制。例如在视频广播中,即使丢了几个信息帧,也不影响整体效果,并且速度够快。

Java 通过两个类实现 UDP 协议顶层的数据报:
采用 UDP 通信机制,在发送信息时,首先将数据打包,然后将打包好的数据(数据包)发往目的地。在接收信息时,首先接收发来的数据包,然后查看数据包中的内容。

Java DatagramPacket类

要发送或接收数据报,需要用 DatagramPacket 类将数据打包,即用 DatagramPacket 类创建一个对象,称为数据包。

1) DatagramPacket类的构造方法

DatagramPacket(byte[] buf,int length):构造数据包对象,用来接收长度为length的数据包。
DatagramPacket(byte[] buf,int length,InetAddress address,int port):构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf,int offset,int length):构造数据报包对象,用来接收长度为length的包,在缓冲区中指定了偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):构造数据报包,用来将长度为 length,偏移量为 offset 的包,发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address):构造数据报包,用来将长度为 length,偏移量为 offset 的包,发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf,int length,SocketAddress address):构造数据报包,用来将长度为 length的包,发送到指定主机上的指定端口号。
其中,buf 保存传入数据报的缓冲区,length 为要读取的字节数,address 为数据报要发送的目的套接字地址。port 为数据包的目标端口号。length 参数必须小于等于 buf.length。

2) DatagramPacket 类的常用方法

方法 描述
InetAddress getAddress() 返回某台机器的IP地址。
byte[] getData() 返回数据缓冲区。
int getLength() 返回将要发送或接收到的数据的长度。
int getOffset() 返回将要发送或接收到的数据的偏移量。
int getPort() 返回某台远程主机的端口号。
SocketAddress getSocketAddress() 获取要将此包发送到或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址+端口号)。
void setAddress(InetAddress iaddr) 设置要将此数据报发往的那台机器的IP地址。
void setData(byte[] buf) 为此包设置数据缓冲区。
void setData(byte[] buf, int offset, int length) 为此包设置数据缓冲区。
void setLength(int length) 为此包设置长度。
void setPort(int iport) 设置要将此数据报发往的远程主机上的端口号。
void setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址+端口号)。

Java DatagramSocket类

DatagramSocket 类是用来发送和接收数据报包的套接字,负责将打包的数据包发送到目的地,或从目的地接收数据包。

1) DatagramSocket 类的构造方法

DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port,InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr):创建数据报套接字,将其绑定到指定的本地套接字地址。

2) DatagramSocket类的常用方法

方法 描述
void bind(SocketAddress addr) 将此 DatagramSocket 绑定到特定的地址和端口。
void close() 关闭此数据报套接字。
void connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP 地址+端口号)。
void disconnect() 断开套接字的连接。
InetAddress getInetAddress() 返回此套接字连接的地址。
InetAddress getLocalAddress() 获取套接字绑定的本地地址。
int getLocalPort() 返回此套接字绑定的本地主机上的端口号。
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回null。
SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回null。
void receive(DatagramPacket p) 从此套接字接收数据报包。
void send(DatagramPacket p) 从此套接字发送数据包。

例如,将“你好”这两个汉字封装成数据包,发送到目的主机“192.168.0.107”,端口 2018 上,完成此任务的核心代码如下:
byte buff[] = "你好".getBytes();
InetAddress destAddress = InetAddress.getByName("192.168.0.107");
DatagramPacket dataPacket = new DatagramPacket(buff, buff.length, destAddress, 2018);
DatagramSocket sendSocket = new DatagramSocket();
sendSocket.send(dataPacket);

再如,接收发到本机 2016 号端口的数据包,完成此任务的核心代码如下:
byte buff[] = new byte[8192];
DatagramPacket receivePacket = new DatagramPacket(buff, buff.length);
DatagramSocket receiveSocket = new DatagramSocket(2016);
receiveSocket.receive(receivePacket);
int length = receivePacket.getLength();
String message = new String(receivePacket.getData(), 0, length);
System.out.println(message);

Java UDP通信实例

下面通过一个案例介绍 DatagramPacket 类和 DatagramSocket 类的用法。

【实例】设计点对点的快速通信系统,实现局域网内两台主机之间的通信和聊天功能。本系统属于互为服务器和客户机的网络应用系统,采用 UDP 数据报编程技术可以实现快速的点对点通信。聊天界面采用 Swing 组件来实现。以主机 1 和主机 2 表示两台主机。

1) 主机1的设计

主机 1 向对方 2012 端口发送信息,并在自己的 2016 号端口接收来自对方的信息。主机 1 的代码如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class UDPHostOne extends JFrame implements Runnable, ActionListener {
    JTextField sentMsg = new JTextField(20);
    JTextArea receivedMsg = new JTextArea();
    JButton send = new JButton("发送");

    /**
     * 构造方法,建立通信主界面
     */
    UDPHostOne() {
        setTitle("主机1"); // 设置窗口标题
        setSize(400, 500); // 设置窗口大小
        setVisible(true); // 显示主窗口
        Container container = this.getContentPane(); // 获取JFrame的内容面板
        container.setLayout(new BorderLayout()); // 设置布局方式为BorderLayout
        // 创建用于存放聊天记录的滚动面板
        JScrollPane centerPanel = new JScrollPane();
        receivedMsg = new JTextArea(); // 创建存放聊天内容的多行文本输入区对象
        centerPanel.setViewportView(receivedMsg); // 将文本区放入滚动面板
        container.add(centerPanel, BorderLayout.CENTER); // 将滚动面板放到窗口中央
        receivedMsg.setEditable(false); // 将文本区置为不可编辑状态
        // 创建底部面板对象,存放聊天标签、聊天栏、发送按钮
        JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        sentMsg = new JTextField(20); // 创建文本输入行对象,存放每次的聊天内容
        send = new JButton("发送"); // 创建按钮对象
        bottomPanel.add(new JLabel("信息")); // 将标签添加到底部面板
        bottomPanel.add(sentMsg); // 把聊天内容栏添加到底部面板
        bottomPanel.add(send); // 把“发送”按钮添加到底部面板
        container.add(bottomPanel, BorderLayout.SOUTH); // 将底部面板放在窗口底部
        send.addActionListener(this); // 注册“发送”按钮的动作事件
        sentMsg.addActionListener(this); // 注册聊天栏的动作事件
        Thread thread = new Thread(this);
        thread.start(); // 线程负责接收数据
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    /**
     * 单击按钮发送数据包,或者在文本输入框里按回车键发送文本
     */
    public void actionPerformed(ActionEvent event) {
        // 获取图形界面上文本框中输入的信息,将文本前后的空格过滤掉
        byte buffer[] = sentMsg.getText().trim().getBytes();
        try {
            // 设置信息发送的目的地地址为127.0.0.1
            InetAddress destAddress = InetAddress.getByName("127.0.0.0.1");
            // 将数据封装到数据报,指定发送目的地的端口号
            DatagramPacket dataPacket = new DatagramPacket(buffer, buffer.length, destAddress, 2012);
            // 创建数据报套接字
            DatagramSocket sendSocket = new DatagramSocket();
            receivedMsg.append("========== 本地消息 ==========\n");
            receivedMsg.append("数据报目标主机地址:" + dataPacket.getAddress() + "\n");
            receivedMsg.append("数据报目标端口是:" + dataPacket.getPort() + "\n");
            receivedMsg.append("数据报长度:" + dataPacket.getLength() + "\n");
            // 向异地发送数据报
            sendSocket.send(dataPacket);
            sentMsg.setText("");
        } catch (Exception e) {
        }
    }

    /**
     * 使用线程机制,接收来自异地的数据报
     */
    public void run() {
        DatagramSocket receiveSocket = null; // 定义数据报套接字
        DatagramPacket receivePacket = null; // 定义数据报对象
        byte buff[] = new byte[8192]; // 设置数据包最大字节数
        try {
            // 创建数据报对象,指定数据存储空间和数据长度
            receivePacket = new DatagramPacket(buff, buff.length);
            // 创建数据报套接字对象,接收信息端口号为2016
            receiveSocket = new DatagramSocket(2016);
        } catch (Exception e) {
        }
        while (true) {
            // 如果套接字为空,则跳出死循环
            if (receiveSocket == null)
                break;
            else {
                // 否则,接收来自异地的数据
                try {
                    receiveSocket.receive(receivePacket); // 接收数据
                    int length = receivePacket.getLength(); // 获取数据长度
                    // 获取异地主机的 IP 地址
                    InetAddress address = receivePacket.getAddress();
                    int port = receivePacket.getPort(); // 获取异地主机的端口号
                    // 将获取的异地发来的数据转为字符串
                    String message = new String(receivePacket.getData(), 0, length);
                    receivedMsg.append("========== 异地消息 ==========\n");
                    receivedMsg.append("收到数据长度:" + length + "\n");
                    receivedMsg.append("收到数据来自:" + address + "端口:" + port + "\n");
                    receivedMsg.append("收到数据是:" + message + "\n");
                } catch (Exception e) {
                }
            }
        }
    }

    public static void main(String args[]) {
        new UDPHostOne();
    }
}
程序运行结果如下图所示:


图 1 主机1的运行结果

2) 主机2的设计

主机 2 向对方 2016 端口发送信息,并在自己的 2012 号端口接收来自对方的信息。主机 2 的代码如下:
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class UDPHostTwo extends JFrame implements Runnable, ActionListener {
    private static final long serialVersionUID = 5719473920833381308L;
    JTextField sentMsg = new JTextField(20);
   JTextArea receivedMsg = new JTextArea();
   JButton send = new JButton("发送 ");

    /**
    * 构造方法,建立通信主界面
    */
    UDPHostTwo() {
        setTitle("主机 2"); // 设置窗口标题
        setSize(400, 300); // 设置窗口大小
        setVisible(true); // 显示主窗口
        Container container = this.getContentPane(); // 获取JFrame的内容面板
        container.setLayout(new BorderLayout()); // 设置布局方式为BorderLayout
        // 创建用于存放聊天记录的滚动面板
        JScrollPane centerPanel = new JScrollPane();
        receivedMsg = new JTextArea(); // 创建存放聊天内容的多行文本输入区对象
        centerPanel.setViewportView(receivedMsg); // 将文本区放入滚动面板
        container.add(centerPanel, BorderLayout.CENTER); // 将滚动面板放到窗口中央
        receivedMsg.setEditable(false); // 将文本区置为不可编辑状态
        // 创建底部面板对象,存放聊天标签、聊天栏、发送按钮
        JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        sentMsg = new JTextField(20); // 创建文本输入行对象,存放每次的聊天内容
        send = new JButton("发送"); // 创建按钮对象
        bottomPanel.add(new JLabel("信息")); // 将标签添加到底部面板
        bottomPanel.add(sentMsg); // 把聊天内容栏添加到底部面板
        bottomPanel.add(send); // 把“发送”按钮添加到底部面板
        container.add(bottomPanel, BorderLayout.SOUTH); // 将底部面板放在窗口底部
        send.addActionListener(this); // 注册“发送”按钮的动作事件
        sentMsg.addActionListener(this); // 注册聊天栏的动作事件
        Thread thread = new Thread(this);
        thread.start(); // 线程负责接收数据包
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    /**
     * 单击按钮发送数据包,或者在文本输入框里按回车键发送文本
     */
    public void actionPerformed(ActionEvent event) {
        // 获取图形界面上文本框中输入的信息,将文本前后的空格过滤掉
        byte buffer[] = sentMsg.getText().trim().getBytes();
        try {
            // 设置信息发送的目的地地址为127.0.0.1
            InetAddress destAddress = InetAddress.getByName("127.0.0.0.1");
            // 将数据封装到数据报,指定发送目的地的端口号
            DatagramPacket dataPacket = new DatagramPacket(buffer, buffer.length, destAddress, 2016);
            // 创建数据报套接字
            DatagramSocket sendSocket = new DatagramSocket();
            receivedMsg.append("========== 本地消息 ==========\n");
            receivedMsg.append("数据报目标主机地址:" + dataPacket.getAddress() + "\n");
            receivedMsg.append("数据报目标端口是:" + dataPacket.getPort() + "\n");
            receivedMsg.append("数据报长度:" + dataPacket.getLength() + "\n");
            // 向异地发送数据报
            sendSocket.send(dataPacket);
            sentMsg.setText("");
        } catch (Exception e) {
        }
    }

    /**
     * 使用线程机制,接收来自异地的数据报
     */
    public void run() {
        DatagramSocket receiveSocket = null; // 定义数据报套接字
        DatagramPacket receivePacket = null; // 定义数据报对象
        byte buff[] = new byte[8192]; // 设置数据包最大字节数
        try {
            // 创建数据报对象,指定数据存储空间和数据长度
            receivePacket = new DatagramPacket(buff, buff.length);
            // 创建数据报套接字对象,接收信息端口号为2012
            receiveSocket = new DatagramSocket(2012);
        } catch (Exception e) {
        }
        while (true) {
            // 如果套接字为空,则跳出死循环
            if (receiveSocket == null)
                break;
            else {
                // 否则,接收来自异地的数据
                try {
                    receiveSocket.receive(receivePacket); // 接收数据
                    int length = receivePacket.getLength(); // 获取数据长度
                    // 获取异地主机的 IP 地址
                    InetAddress address = receivePacket.getAddress();
                    int port = receivePacket.getPort(); // 获取异地主机的端口号
                    // 将获取的异地发来的数据转为字符串
                    String message = new String(receivePacket.getData(), 0, length);
                    receivedMsg.append("========== 异地消息 ==========\n");
                    receivedMsg.append("收到数据长度:" + length + "\n");
                    receivedMsg.append("收到数据来自:" + address + "端口:" + port + "\n");
                    receivedMsg.append("收到数据是:" + message + "\n");
                } catch (Exception e) {
                }
            }
        }
    }

    public static void main(String args[]) {
        new UDPHostTwo();
    }
}
程序运行结果如下图所示:


图 2 主机 2 的运行结果

相关文章