JavaSocket系列-UDP链接

2021/4/26 JavaSocket

# 简单介绍

基于 UDP协议 (opens new window)Java方式Socket的实现,它是 数据报套接字 (SOCK_DGRAM) 的一种传输类型,是 面向无连接 的服务类型。在选择UDP作为传输协议时必须要谨慎,在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。

相关参考

UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768 是UDP的正式规范。UDP在IP报文的协议号是17。

由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用的ICQ和QQ (opens new window)就是使用的UDP协议。

接下来一起来开始 JavaSocket UDP 的相关实战吧~

# UDP传输类型

UDP是通过数据报的方式来进行通信的,但是在发送和结束的有一下几种分类:

  1. 单播(点对点)
  2. 组播(一对多)
  3. 广播(全员广播,可以理解是一种特殊的组播)

需要注意

组播广播的方式只能用于局域网通信,并受限于以下的各种条件:

  1. 部分路由器或者网络设备可能有所限制,无法完成此项功能;
  2. 部分机器可能安装了虚拟机,可能需要关闭虚拟网卡后才可以正常使用;
  3. 部分系统可能有防火墙原因,广播可能发送或者接收异常,请关闭防护墙后试试;
  4. 其他情况的网络限制原因...

# 实现流程

  1. 创建一个UDPServer,监听指定端口的UDP数据;

  2. 创建一个UDPClient,发送不同UDP类型的数据;

  3. 服务端接受到数据处理(可以反馈客户端),服务端同样可以接受服务反馈的消息进行相应处理;

  4. 在数据交互结束后根据需要可以分别关闭相应的服务端或者客户端程序。

# 创建服务端代码

提示

由于广播是组播的一种特殊的实现,所以广播和组播的服务端公用一份代码实例。

  • 组播是有特定的类型的IP的
  • 广播可以是局域网全局广播或者指定网段广播,取决于当前网络环境的特定的广播地址

# 单播服务端代码

public class UdpSocketServer {
    private static final String TAG = UdpSocketServer.class.getSimpleName();
    private DatagramSocket mDatagramSocket;
    private int mPort;
    private volatile boolean isShutDown = true;
    private volatile boolean isRunning = false;
    /**
     * 创建一个UdpSocketServer对象,用于接收UDP客户端发送的消息.
     *
     * @param port 接收数据的端口号.
     * @throws Exception
     */
    public UdpSocketServer(int port) throws Exception {
        mPort = port;
        mDatagramSocket = new DatagramSocket(mPort);
        mDatagramSocket.setBroadcast(true); // 部分操作系统可能需要申请广播才可以使用
        isShutDown = false;
    }

    /**
     * 开始启动数据接收监听以及回复数据.
     */
    public void run() {
        if (!isRunning) {
            try {
                while (!isShutDown) {
                    byte[] buff = new byte[1024 * 8];
                    DatagramPacket dp = new DatagramPacket(buff, buff.length);
                    System.out.println(TAG + " run: Server receiving...");
                    mDatagramSocket.receive(dp);
                    String data = new String(dp.getData(), StandardCharsets.UTF_8).trim();
                    System.out.println(TAG + " run: server receiveMessage,  remoteAddress = " + dp.getAddress()
                            + ", port = " + dp.getPort() + ", data = " + data);

                    String repose = "[Server]: Client Msg is Received";
                    byte[] repBuf = repose.getBytes();
                    dp.setData(repBuf);
                    mDatagramSocket.send(dp);
                    System.out.println(TAG + " run:  Server has replied to the Message!");
                }
            } catch (Exception e) {
                isRunning = false;
                System.out.println(TAG + " run: exception = " + e.getMessage());
            }
        } else {
            System.out.println(TAG +" run: UdpSocketServer has running...");
        }
    }

    /**
     * 关闭Socket链接,如果还在执行接收或者发送的情况,可能会在相应位置出现异常,忽略即可.
     */
    public void shutDown() {
        isRunning = false;
        isShutDown = true;
        try {
            mDatagramSocket.close();
        } catch (Exception e) {
            System.out.println(TAG + " shutDown");
        }
        System.out.println(TAG + " shutDown");
    }

}

# 组播和广播服务端代码

/**
 * 关于组播地址的分类参考:
 * <p>224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
 * <p>224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
 * <p>224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址);
 * <p>239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
 */
public class UdpMulticastSocketServer {
    private static final String TAG = UdpMulticastSocketServer.class.getSimpleName();
    private MulticastSocket mMultiSocket;
    private InetAddress mMultiGroupInetAddress;
    private int mPort;
    private volatile boolean isShutDown = true;
    private volatile boolean isRunning = false;

    /**
     * 创建一个UdpMultiSocketServer对象,可以绑定到指定的组播地址以及端口上,监听并回复消息.
     *
     * @param groupAddress 多播的地址.
     * @param port         多播的端口号.
     * @throws Exception
     */
    public UdpMulticastSocketServer(NetworkInterface networkInterface,
                                    InetAddress groupAddress, int port) throws Exception {
        mMultiGroupInetAddress = groupAddress;
        mPort = port;
        mMultiSocket = new MulticastSocket(mPort);
        mMultiSocket.setNetworkInterface(networkInterface); // 设置指定的网卡设备
        mMultiSocket.joinGroup(mMultiGroupInetAddress); // 加入到指定的多播地址上
        isShutDown = false;
    }

    /**
     * 开始运行并接收多播地址和端口的消息.
     */
    public void run() {
        if (!isRunning) {
            isRunning = true;
            try {
                while (!isShutDown) {
                    byte[] buff = new byte[1024];
                    DatagramPacket dp = new DatagramPacket(buff, buff.length);
                    System.out.println(TAG + " run: Server receiving...");
                    mMultiSocket.receive(dp);
                    // 处理接收到的消息
                    String remoteAddress = dp.getAddress().getHostAddress();
                    int port = dp.getPort();
                    String data = new String(dp.getData(), CharsetUtil.UTF_8).trim();
                    System.out.println(TAG + " run: server receiveMessage, remoteAddress = " + remoteAddress
                            + ", port = " + port
                            + ", data = " + data);

                    // 回复Client消息
                    String repose = "[Server]: Client Msg is Received";
                    dp.setData(repose.getBytes(CharsetUtil.UTF_8));
                    mMultiSocket.send(dp);
                }
            } catch (Exception e) {
                isRunning = false;
                System.out.println(TAG + " run: exception = " + e.getMessage());
            }
        } else {
            System.out.println(TAG + " run: UdpMulticastSocketServer has running...");
        }
    }

    /**
     * 关闭Socket链接,如果还在执行接收或者发送的情况,可能会在相应位置出现异常,忽略即可.
     */
    public void shutDown() {
        isRunning = false;
        isShutDown = true;
        try {
            mMultiSocket.close();
        } catch (Exception e) {
            System.out.println(TAG + " shutDown");
        }
        System.out.println(TAG + " shutDown");
    }
}

# 创建客户端代码

由于广播是组播的一种特殊的实现,所以广播和组播的客户端也公用一份代码实例。

# 单播客户端代码

public class UdpSocketClient {
    private static final String TAG = UdpSocketClient.class.getSimpleName();
    private DatagramSocket mDatagramSocket;
    private DatagramPacket mDatagramPacket;
    private byte[] buff = new byte[1024 * 8];
    private InetAddress mAddress;
    private int mPort;
    private volatile boolean isShutDown = true;
    private volatile boolean isReceiving = false;

    /**
     * 创建UdpSocketClient对象.
     *
     * @param address 发送数据的服务的InetAddress.
     * @param port    发送数据的服务的端口号.
     * @throws Exception
     */
    public UdpSocketClient(InetAddress address, int port) throws Exception {
        mAddress = address;
        mPort = port;
        mDatagramSocket = new DatagramSocket();
        mDatagramPacket = new DatagramPacket(buff, buff.length, mAddress, mPort);
        isShutDown = false; // 标记是否关闭
    }

    /**
     * 发送数据给目标Server.
     *
     * @param msg 需要发送的字符串数据.
     * @throws Exception
     */
    public synchronized void sendMessage(String msg) throws Exception {
        if (!isShutDown && null != msg && msg.length() > 0) {
            mDatagramPacket.setData(msg.getBytes(StandardCharsets.UTF_8));
            mDatagramSocket.send(mDatagramPacket);
            System.out.println(TAG + " sendMessage: msg = " + msg);
        }
    }

    /**
     * 开始接收Server回复的数据消息.
     */
    public synchronized void receiveData() {
        if (!isReceiving) {
            isReceiving = true;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!isShutDown) {
                        try {
                            byte[] revBuff = new byte[1024 * 8];
                            DatagramPacket revDatagramPacket = new DatagramPacket(revBuff, revBuff.length);
                            mDatagramSocket.receive(revDatagramPacket);
                            String rData = new String(revDatagramPacket.getData(), 0,
                                    revDatagramPacket.getLength(), StandardCharsets.UTF_8);
                            System.out.println(TAG + " receiveData:  remoteAddress = " + revDatagramPacket.getAddress()
                                    + ", port = " + revDatagramPacket.getPort() + ", data = " + rData);
                        } catch (Exception e) {
                            isReceiving = false;
                            System.out.println(TAG + " receiveData: exception = " + e.getMessage());
                        }
                    }
                }
            }).start();
        } else {
            System.out.println(TAG + " receiveData: UdpSocketClient has receiving...");
        }
    }

    /**
     * 关闭Socket链接,如果还在执行接收或者发送的情况,可能会在相应位置出现异常,忽略即可.
     */
    public synchronized void shutDown() {
        isReceiving = false;
        isShutDown = true;
        try {
            mDatagramSocket.close();
        } catch (Exception e) {
            System.out.println(TAG + " shutDown");
        }
        System.out.println(TAG + " shutDown");
    }

}

# 组播和广播客户端代码

/**
 * 关于组播地址的分类参考:
 * <p>224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
 * <p>224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
 * <p>224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
 * <p>239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
 */
public class UdpMulticastSocketClient {
    private static final String TAG = UdpMulticastSocketClient.class.getSimpleName();
    private MulticastSocket mMultiSocket;
    private InetAddress mMultiInetAddress;
    private int mPort;
    private volatile boolean isShutDown = true;
    private volatile boolean isReceiving = false;

    /**
     * 创建一个UdpMultiSocketClient对象,可以发送一个指定的多播(组播)消息,并可以接收回复消息.
     *
     * @param groupAddress 多播的地址.
     * @param port         多播的端口号.
     * @throws Exception
     */
    public UdpMulticastSocketClient(InetAddress groupAddress, int port) throws Exception {
        mMultiInetAddress = groupAddress;
        mPort = port;
        mMultiSocket = new MulticastSocket();
        isShutDown = false;
    }

    /**
     * 可以向指定的多播组发送一个消息,所有在指定的组地址以及端口号的设备都可以接收到.
     *
     * @param msg 发送的字符串消息.
     * @throws Exception
     */
    public synchronized void sendMessage(String msg) throws Exception {
        if (!isShutDown && null != msg && msg.length() > 0) {
            byte[] buff = msg.getBytes(CharsetUtil.UTF_8);
            DatagramPacket dp = new DatagramPacket(buff, buff.length, mMultiInetAddress, mPort);
            mMultiSocket.send(dp);
            System.out.println(TAG + " sendMessage: msg = " + msg);
        } else {
            System.out.println(TAG + " sendMessage: msg is invalid!");
        }
    }

    /**
     * 接收回复的消息.
     */
    public synchronized void receiveData() {
        if (!isReceiving) {
            isReceiving = true;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (!isShutDown) {
                            byte[] buff = new byte[1024 * 8];
                            DatagramPacket dp = new DatagramPacket(buff, buff.length);
                            mMultiSocket.receive(dp);

                            InetAddress address = dp.getAddress();
                            int port = dp.getPort();
                            String data = new String(dp.getData(), CharsetUtil.UTF_8).trim();
                            System.out.println(TAG + " receiveData: remoteAddress = " + address.getHostAddress()
                                    + ", port = " + port
                                    + ", data = " + data);
                        }
                    } catch (Exception e) {
                        isReceiving = false;
                        System.out.println(TAG + " receiveData: exception = " + e.getMessage());
                    }
                }
            }).start();
        } else {
            System.out.println(TAG + " receiveData: UdpMulticastSocketClient has receiving...");
        }
    }

    /**
     * 关闭Socket链接,如果还在执行接收或者发送的情况,可能会在相应位置出现异常,忽略即可.
     */
    public synchronized void shutDown() {
        isReceiving = false;
        isShutDown = true;
        try {
            mMultiSocket.close();
        } catch (Exception e) {
            System.out.println(TAG + " shutDown");
        }
        System.out.println(TAG + " shutDown");
    }

}

# 网卡的接口相关工具类代码

/**
 * 网卡相关接口(NetworkInterface)的工具操作类对象
 */
public class NetworkInterfaceUtil {

    /**
     * 获取当前设备的所有网卡的NetworkInterface对象集合
     *
     * @return 设备的所有网卡的NetworkInterface对象集合
     * @throws Exception
     */
    public static Enumeration<NetworkInterface> getNetworkInterfaces() throws Exception {
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        return interfaces;
    }

    /**
     * 从当前设备中可用网卡中选择一个网卡NetworkInterface对象
     *
     * @return 选择的一个可用的网卡NetworkInterface对象
     * @throws Exception
     */
    public static NetworkInterface choseNetworkInterface() throws Exception {
        Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
        if (null != interfaces) {
            List<NetworkInterface> interfaceList = new ArrayList<>();
            while (interfaces.hasMoreElements()) {
                NetworkInterface networkInterface = interfaces.nextElement();
                // 当前正在运行的网卡
                if (networkInterface.isUp()) {
                    interfaceList.add(networkInterface);
                }
            }
            if (interfaceList.size() > 0) {
                // 打印每个NetworkInterface对象的相关消息
                System.out.println("**************** NetworkInterfaces ****************");
                for (int i = 0; i < interfaceList.size(); i++) {
                    NetworkInterface networkInterface = interfaceList.get(i);
                    byte[] bytes = networkInterface.getHardwareAddress();
                    StringBuffer sb = new StringBuffer();
                    if (null != bytes) {
                        for (int j = 0; j < bytes.length; j++) {
                            sb.append(String.format("%02x", bytes[j]));
                            if (j < (bytes.length - 1)) {
                                sb.append(":");
                            }
                        }
                    }
                    String hardwareAddress = sb.toString();

                    // 获取当前interface的所有Addresses
                    List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();

                    System.out.println("[" + (i + 1) + "]. networkInterface: " +
                            "\n\t > name = " + networkInterface.getDisplayName()
                            + ",\n\t > isLoopback = " + networkInterface.isLoopback()
                            + ",\n\t > isVirtual = " + networkInterface.isVirtual()
                            + ",\n\t > supportsMulticast = " + networkInterface.supportsMulticast()
                            + ",\n\t > MTU = " + networkInterface.getMTU()
                            + ",\n\t > hardwareAddress = " + (hardwareAddress.length() > 0 ? hardwareAddress : null)
                            + ",\n\t > interfaceAddress = " + interfaceAddresses);
                }
                System.out.println("[" + (interfaceList.size() + 1) + "]. exit");
                System.out.println("***************************************************\n");

                // 接收用户输入的功能序号选择对应的NetworkInterface或者退出
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    System.out.print("==> 请选择一个支持功能操作的网卡(NetworkInterface)序号:");
                    int num;
                    try {
                        num = scanner.nextInt();
                    } catch (Exception e) {
                        System.out.println("==> 您输入的序号错误,请重新输入!");
                        scanner = new Scanner(System.in);
                        continue;
                    }
                    if (num > 0 && num <= interfaceList.size()) {
                        // 返回选择项的NetworkInterface
                        return interfaceList.get(num - 1);
                    } else if (num == (interfaceList.size() + 1)) {
                        System.exit(0);
                    } else {
                        System.out.println("==> 您输入的序号错误,请重新输入!");
                    }
                }
            }
        }
        return null;
    }

    /**
     * 选择一个可用的局域网全局广播方式或者指定网段广播方式的地址
     *
     * @return 返回一个可用的广播地址InetAddress对象
     */
    public static InetAddress choseBroadcastAddress() {
        InetAddress broadcastAddress = null;
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println();
            System.out.println("******************* 请选择一个广播的方式 *******************");
            System.out.println("[1]. 局域网全网段广播(部分网路设备不支持或者有限制)");
            System.out.println("[2]. 指定网段局域网(选择一个指定网段的广播地址)");
            System.out.println("[3]. Exit");
            System.out.println("*********************************************************\n");
            System.out.print("==> 请选择广播的方式:");
            int num;
            try {
                // 广播方式选择
                num = scanner.nextInt();
                // 1. 局域网全网段广播,注意:部分路由器或者网络设备可能有所限制,无法完成广播
                if (num == 1) {
                    broadcastAddress = InetAddress.getByName("255.255.255.255");
                    break;
                } else if (num == 2) {
                    // 2. 局域网指定网段广播,根据当前的设备所处的网段的广播地址
                    NetworkInterface networkInterface = NetworkInterfaceUtil.choseNetworkInterface();
                    List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
                    List<InetAddress> broadcasts = new ArrayList<>();
                    // 3. 遍历获取当前网卡的所有可用的广播地址
                    if (interfaceAddresses.size() > 0) {
                        System.out.println("******************* 选择当前网卡的一个可用的广播 *******************");
                        for (int i = 0; i < interfaceAddresses.size(); i++) {
                            InetAddress broadcast = interfaceAddresses.get(i).getBroadcast();
                            if (null != broadcast) {
                                System.out.println("[" + i + "]. " + broadcast.getHostAddress());
                                broadcasts.add(broadcast);
                            }
                        }
                        System.out.println("****************************************************************\n");
                    } else {
                        System.out.println("==> 此interface broadcast 不可用,请重新选择!");
                        continue;
                    }
                    System.out.print("==> 请选择具体一个广播地址对象:");
                    // 4. 选择选中NetworkInterface的指定网段广播地址
                    num = scanner.nextInt();
                    broadcastAddress = broadcasts.get(num - 1);
                    break;
                } else if (num == 3) {
                    System.exit(0);
                } else {
                    System.out.println("==> 您输入的序号错误,请重新选择!");
                    continue;
                }
            } catch (Exception e) {
                System.out.println("==> 您输入的序号错误,请重新选择!");
                scanner = new Scanner(System.in);
                continue;
            }
        }
        // 5. 返回选中的广播地址对象
        return broadcastAddress;
    }

}

# 使用示例

/**
* Java Udp Socket Demo.
*
* @throws IOException
*/
public static void javaUdpSocketDemo() throws Exception {
    // 创建Server对象
    UdpSocketServer udpSocketServer = new UdpSocketServer(9999);
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 开启Server
            udpSocketServer.run();
        }
    }).start();

    // 本处ip是本地host,也可以是局域网下Server设备的实际ip
    InetAddress address = InetAddress.getByName("127.0.0.1");
    // 创建Client对象
    UdpSocketClient udpSocketClient = new UdpSocketClient(address, 9999);
    udpSocketClient.receiveData(); // 客户端接收回复消息

    Thread.sleep(500); // 多线程延迟,防止异步创建对象不同步问题

    // 接收输入的数据发送给Server
    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.println("\n==> [Client --> Server]请输入发送的消息(输入quit退出):");
        String s = scanner.nextLine();
        if ("quit".equals(s)) {
            System.out.println("==> javaUdpSocketDemo is quitting...");
            udpSocketServer.shutDown();
            udpSocketClient.shutDown();
            break;
        }
        // 发送输入的字符串数据
        udpSocketClient.sendMessage(s);
        Thread.sleep(500); // 数据输入发送间隔设置为500ms
    }

    System.out.println("------------------------- javaUdpSocketDemo finished -------------------------");
}

/**
* Java Multicast Udp Socket Demo.
* <p>
* 注意:<br>
* 1) 部分机器可能安装了虚拟机,可能需要关闭虚拟网卡后才可以正常使用.<br>
* 2) 部分系统可能有防火墙原因,广播可能发送或者接收异常,请关闭防护墙后试试.<br>
* 3) 部分路由器或者网络设备可能有所限制,无法完成此项功能.
*
* <p>
* 关于组播地址的分类:<br>
* 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;<br>
* 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;<br>
* 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;<br>
* 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。<br>
*
* @throws Exception
*/
public static void javaMulticastUdpSocketDemo() throws Exception {
    // 检查和获取可用的网卡设备,并选择一个网卡设置
    NetworkInterface networkInterface = NetworkInterfaceUtil.choseNetworkInterface();
    if (null == networkInterface) {
        System.out.println("nettyMulticastUdpSocketDemo: 网络异常,请检查网络设备是否可用!");
        return;
    }

    // 指定多播的地址和端口号
    InetAddress multiInetAddress = InetAddress.getByName("239.0.0.1");
    int port = 9999;

    // 创建多播Server对象
    UdpMulticastSocketServer multiSocketServer = new UdpMulticastSocketServer(networkInterface,
            multiInetAddress, port);
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 开启Server
            multiSocketServer.run();
        }
    }).start();

    // 创建多播Client对象
    UdpMulticastSocketClient multiSocketClient = new UdpMulticastSocketClient(multiInetAddress, 9999);
    multiSocketClient.receiveData();    // 客户端接收回复消息

    Thread.sleep(500); // 多线程延迟,防止异步创建对象不同步问题

    // 接收输入的数据发送给Server
    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.println("\n==> [Client --> Server]请输入发送的消息(输入quit退出):");
        String s = scanner.nextLine();
        if ("quit".equals(s)) {
            System.out.println("==> javaBroadcastUdpSocketDemo is quitting...");
            multiSocketClient.shutDown();
            multiSocketServer.shutDown();
            break;
        }
        // 发送输入的字符串数据
        multiSocketClient.sendMessage(s);
        Thread.sleep(500); // 数据输入发送间隔设置为500ms
    }
}

/**
* Java Broadcast Udp Socket Demo.
* <p>
* 注意:<br>
* 1) 部分机器可能安装了虚拟机,可能需要关闭虚拟网卡后才可以正常使用.<br>
* 2) 部分系统可能有防火墙原因,广播可能发送或者接收异常,请关闭防护墙后试试.
* 3) 部分路由器或者网络设备可能有所限制,无法完成此项功能.
*
* @throws Exception
*/
public static void javaBroadcastUdpSocketDemo() throws Exception {
    // 创建Server对象
    UdpSocketServer udpBroadcastSocketServer = new UdpSocketServer(9999);
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 开启Server
            udpBroadcastSocketServer.run();
        }
    }).start();

    Thread.sleep(500); // 多线程延迟,防止异步创建对象不同步问题
    // 广播方式选择: 局域网全局广播或者指定网段广播
    InetAddress broadcastAddress = NetworkInterfaceUtil.choseBroadcastAddress();
    System.out.println("==> 选择的broadcastAddress = " + broadcastAddress.getHostAddress());

    // 创建广播Client对象
    UdpSocketClient udpBroadcastSocketClient = new UdpSocketClient(broadcastAddress, 9999);
    udpBroadcastSocketClient.receiveData(); // 客户端接收回复消息

    Thread.sleep(500); // 多线程延迟,防止异步创建对象不同步问题

    // 接收输入的数据发送给Server
    Scanner scanner = new Scanner(System.in);
    while (true) {
        System.out.println("\n==> [Client --> Server]请输入发送的消息(输入quit退出):");
        scanner.reset();
        String s = scanner.nextLine();
        if ("quit".equals(s)) {
            System.out.println("==> javaBroadcastUdpSocketDemo is quitting...");
            udpBroadcastSocketServer.shutDown();
            udpBroadcastSocketClient.shutDown();
            break;
        }
        // 发送输入的字符串数据
        udpBroadcastSocketClient.sendMessage(s);
        Thread.sleep(500); // 数据输入发送间隔设置为500ms
    }
    Thread.sleep(500); // 多线程关闭延迟
    System.out.println("------------------------- javaBroadcastUdpSocketDemo finished -------------------------");
}

# 运行示例

# 单播示例
UdpSocketServer run: Server receiving...

==> [Client --> Server]请输入发送的消息(输入quit退出):
Hello, I am JiangMing.
UdpSocketClient sendMessage: msg = Hello, I am JiangMing.
UdpSocketServer run: server receiveMessage,  remoteAddress = /127.0.0.1, port = 50464, data = Hello, I am JiangMing.
UdpSocketServer run:  Server has replied to the Message!
UdpSocketServer run: Server receiving...
UdpSocketClient receiveData:  remoteAddress = /127.0.0.1, port = 9999, data = [Server]: Client Msg is Received

==> [Client --> Server]请输入发送的消息(输入quit退出):
quit
==> javaUdpSocketDemo is quitting...
UdpSocketServer shutDown
UdpSocketClient shutDown
------------------------- javaUdpSocketDemo finished -------------------------
UdpSocketServer run: exception = Socket closed
UdpSocketClient receiveData: exception = Socket closed

# 组播示例
**************** NetworkInterfaces ****************
[1]. networkInterface: 
	 > name = eno1,
	 > isLoopback = false,
	 > isVirtual = false,
	 > supportsMulticast = true,
	 > MTU = 1500,
	 > hardwareAddress = 4c:ed:fb:cb:13:49,
	 > interfaceAddress = [/fe80:0:0:0:d790:aae3:d0e0:f77d%eno1/64 [null], /192.168.1.106/24 [/192.168.1.255]]
[2]. networkInterface: 
	 > name = lo,
	 > isLoopback = true,
	 > isVirtual = false,
	 > supportsMulticast = false,
	 > MTU = 65536,
	 > hardwareAddress = null,
	 > interfaceAddress = [/0:0:0:0:0:0:0:1%lo/128 [null], /127.0.0.1/8 [null]]
[3]. exit
***************************************************

==> 请选择一个支持功能操作的网卡(NetworkInterface)序号:1
UdpMulticastSocketServer run: Server receiving...

==> [Client --> Server]请输入发送的消息(输入quit退出):
Hello, send a multicast data.
UdpMulticastSocketClient sendMessage: msg = Hello, send a multicast data.
UdpMulticastSocketServer run: server receiveMessage, remoteAddress = 192.168.1.106, port = 50273, data = Hello, send a multicast data.
UdpMulticastSocketServer run: Server receiving...
UdpMulticastSocketClient receiveData: remoteAddress = 192.168.1.106, port = 9999, data = [Server]: Client Msg is Received

==> [Client --> Server]请输入发送的消息(输入quit退出):
quit
==> javaBroadcastUdpSocketDemo is quitting...
UdpMulticastSocketClient shutDown
UdpMulticastSocketServer shutDown
UdpMulticastSocketClient receiveData: exception = Socket closed
UdpMulticastSocketServer run: exception = Socket closed

# 广播示例
UdpSocketServer run: Server receiving...

******************* 请选择一个广播的方式 *******************
[1]. 局域网全网段广播(部分网路设备不支持或者有限制)
[2]. 指定网段局域网(选择一个指定网段的广播地址)
[3]. Exit
*********************************************************

==> 请选择广播的方式:1
==> 选择的broadcastAddress = 255.255.255.255

==> [Client --> Server]请输入发送的消息(输入quit退出):
Hello, send a broadcast date.
UdpSocketClient sendMessage: msg = Hello, send a broadcast date.
UdpSocketServer run: server receiveMessage,  remoteAddress = /192.168.1.106, port = 45952, data = Hello, send a broadcast date.
UdpSocketServer run:  Server has replied to the Message!
UdpSocketServer run: Server receiving...
UdpSocketClient receiveData:  remoteAddress = /192.168.1.106, port = 9999, data = [Server]: Client Msg is Received

==> [Client --> Server]请输入发送的消息(输入quit退出):
quit
==> javaBroadcastUdpSocketDemo is quitting...
UdpSocketServer shutDown
UdpSocketClient shutDown
UdpSocketServer run: exception = Socket closed
UdpSocketClient receiveData: exception = Socket closed
------------------------- javaBroadcastUdpSocketDemo finished -------------------------

好了,以上简单介绍了一下UDP的概念和相关的传输类型,并将不同的传输类型演示了如何使用的参考代码,到此大家就可以去实际操作一下了,JavaSocket UDP的实现就介绍到这里了。