目录

java网络编程UDP协议通信

【java】网络编程——UDP协议通信

UDP

UDP与TCP最大的区别就是UDP不建立连接就可以发数据报,而TCP一定要建立连接。

消息发送(单条)

示例代码

发送方:

package protocol;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UdpSenderDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.建立一个DatagramSocket,建立的是数据报的Socket
        DatagramSocket socket = new DatagramSocket();
        // 2.建包,这个包就是要发送的东西
        // 你要发的内容、发到哪个ip、端口,都可以当参数传
        // 传的内容(注意是byte数组) 范围(数组下标)始 末 ip 端口
        String msg = "你好UDP";
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port =9999;
        DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0,msg.getBytes().length, address, port);
        // 3.发包,是使用建立的DatagramSocket套接字发包
        // 发完就不管了,不论接收方是否接到
        socket.send(packet);
        // 4.关闭Socket
        socket.close();
    }
}
代码思路
  1. 创建 DatagramSocket :发送方同样需要创建一个 DatagramSocket 对象,不过这里不需要指定端口,系统会自动分配一个可用的端口。这个 DatagramSocket 用于发送 UDP 数据报。
  2. 创建 DatagramPacket :将要发送的消息转换为字节数组,并指定目标地址(IP 地址)和端口号,创建一个 DatagramPacket 对象。这个对象封装了要发送的数据以及目标地址和端口信息。
  3. 发送数据报 :调用 DatagramSocketsend 方法,将创建好的 DatagramPacket 发送出去。UDP 是无连接的协议,发送方发送数据报后不会关心接收方是否成功接收到数据。
  4. 关闭 DatagramSocket :数据报发送完成后,关闭 DatagramSocket 以释放资源。

接收方:

package protocol;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpReceiverDemo01 {
    public static void main(String[] args) throws Exception {
        // 也要建立DatagramSocket,UDP协议下,发送方和接收方法都要建立DatagramSocket
        // 这种构造方法,就是告诉你我这个DatagramSocket对象绑定到了9999端口,发送到这个端口的数据我都能接收
        DatagramSocket socket = new DatagramSocket(9999);
        // 接收数据包,用缓冲区
        byte[] buf = new byte[1024];
        // 这个构造方法通常用于创建一个用于接收数据的数据报包。
        // 当使用 DatagramSocket 的 receive 方法接收数据时,需要传入一个 DatagramPacket 对象,
        // DatagramSocket 会将接收到的数据填充到这个 DatagramPacket 对象中。
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        // 调用receive方法时,程序会进入阻塞状态,直到接收到一个数据报为止。
        socket.receive(packet);
        System.out.println("发包人的地址为:"+packet.getAddress());
        System.out.println("发包人的端口为:"+packet.getPort());
        System.out.println("发的信息为:"+new String(packet.getData()));
        socket.close();
    }
}
代码思路2
  1. 创建 DatagramSocket :在 UDP 通信中,接收方需要创建一个 DatagramSocket 对象并将其绑定到一个特定的端口(这里是 9999 端口)。绑定端口后,该 DatagramSocket 就会监听这个端口,接收所有发送到该端口的 UDP 数据报。
  2. 创建接收缓冲区和 DatagramPacket :为了存储接收到的数据,需要创建一个字节数组作为缓冲区( buf ),并使用这个缓冲区创建一个 DatagramPacket 对象。 DatagramPacket 就像是一个容器,用于接收和存储从网络中接收到的数据报。
  3. 接收数据报 :调用 DatagramSocketreceive 方法,该方法会进入阻塞状态,直到接收到一个发送到绑定端口的数据报。一旦接收到数据报,数据会被填充到 DatagramPacket 对象中。
  4. 处理接收到的数据 :从 DatagramPacket 中获取发送方的地址、端口和发送的消息内容,并将这些信息打印输出。

通信过程

  1. 接收方启动 :接收方程序首先运行,创建一个绑定到 9999 端口的 DatagramSocket ,并进入等待接收数据报的状态(调用 receive 方法后进入阻塞状态)。
  2. 发送方发送数据报 :发送方程序运行,创建一个 DatagramSocket 和一个包含消息 “你好 UDP” 的 DatagramPacket ,并将其发送到目标地址 127.0.0.1 的 9999 端口。
  3. 接收方接收数据报 :当发送方的数据报到达接收方的 9999 端口时,接收方的 receive 方法不再阻塞,将数据报填充到 DatagramPacket 对象中。
  4. 接收方处理数据 :接收方从 DatagramPacket 中获取发送方的地址、端口和消息内容,并将这些信息打印输出。

消息发送(多条)

思路就是在发送单条消息的基础上增加循环。

示例代码

发送方:

package protocol;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UdpSenderDemo02 {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(9999);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            String data = reader.readLine();
            byte[] bytes = data.getBytes();
            DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length, InetAddress.getByName("localhost"),8888);
            socket.send(datagramPacket);
            if(data.equals("bye")){
                break;
            }
        }
        System.out.println("我要下线了");
        socket.close();
    }
}
代码思路1
  • 创建 DatagramSocket 并绑定到本地 9999 端口,作为发送数据报的端口。
  • 创建 BufferedReader 用于读取控制台输入的消息。
  • 进入无限循环,读取用户输入的一行文本,将其转换为字节数组。
  • 创建 DatagramPacket 对象,包含数据、长度、目标地址(本地主机)和目标端口(8888)。
  • 调用 send 方法将数据报发送出去。
  • 判断用户输入的消息是否为 “bye”,若是则跳出循环。
  • 打印下线信息,关闭 DatagramSocket 释放资源。

接收方:

package protocol;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpReceiverDemo02 {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(8888);
        while (true) {
            byte[] buf = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf, 1024);
            socket.receive(packet);
            String data = new String(packet.getData());
            data = data.trim();
            System.out.println("我接收到你的消息:"+data+"了");
            if(data.equals("bye")){
                break;
            }
        }
        System.out.println("那我也下线了");
        socket.close();

    }
}
代码思路2
  • 创建 DatagramSocket 并绑定到本地 8888 端口,用于监听发送方的数据报。
  • 进入无限循环,每次循环创建字节数组缓冲区和 DatagramPacket 对象。
  • 调用 receive 方法阻塞等待,直到接收到发送到 8888 端口的数据报。
  • 将接收到的字节数组转换为字符串,去除首尾空白字符后打印消息。
  • 判断接收到的消息是否为 “bye”,若是则跳出循环。
  • 打印下线信息,关闭 DatagramSocket 释放资源。

通信过程

  1. 启动接收方 :接收方程序启动,创建一个绑定到 8888 端口的 DatagramSocket ,并进入循环等待接收数据报。
  2. 启动发送方 :发送方程序启动,创建一个绑定到 9999 端口的 DatagramSocket ,并等待用户输入消息。
  3. 发送消息 :用户在发送方的控制台输入消息,发送方将消息封装成 DatagramPacket 并发送到接收方的 8888 端口。
  4. 接收消息 :接收方接收到数据报后,将其转换为字符串并打印出来。
  5. 结束通信 :当发送方输入 “bye” 时,发送方结束循环并关闭 DatagramSocket 。接收方接收到 “bye” 消息后,也结束循环并关闭 DatagramSocket ,双方通信结束。

实现对话(可以接收消息也可以发送消息)

核心思路:

使用多线程,让每个使用者都有两个线程,一个是发送消息线程,一个是接收消息线程。

所以实现一个简单对话我们要涉及四个类,一个发送消息的线程、一个接收消息的线程、两个对话的人。

以学生与老师的对话为例:

示例代码

发送消息的线程:
package protocol;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;

public class SendThread implements Runnable{
    private DatagramSocket socket = null;
    private BufferedReader reader = null;
    private int fromPort;
    private String toIp;
    private int toPort;

    public SendThread(int fromPort, String toIp, int toPort) {
        this.fromPort = fromPort;
        this.toIp = toIp;
        this.toPort = toPort;
        try {
            // 绑定发送的端口
            socket = new DatagramSocket(this.fromPort);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        reader = new BufferedReader(new InputStreamReader(System.in));
    }

    @Override
    public void run() {
        while(true){
            String data = null;
            try {
                data = reader.readLine();
                byte[] bytes = data.getBytes();
                DatagramPacket datagramPacket = null;
                datagramPacket = new DatagramPacket(bytes,0,bytes.length, InetAddress.getByName(this.toIp),this.toPort);
                socket.send(datagramPacket);
                if(data.equals("bye")){
                    break;
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        socket.close();
    }
}
代码逻辑分析:
  • 类定义与成员变量
    • 定义了一个名为 SendThread 的类,该类实现了 Runnable 接口,表明它是一个可执行的线程任务。
    • 声明了几个成员变量: DatagramSocket 用于进行 UDP 数据报的发送, BufferedReader 用于从标准输入读取用户输入的消息, fromPort 表示发送消息时绑定的本地端口, toIp 是接收方的 IP 地址, toPort 是接收方的端口号。
  • 构造函数
    • 构造函数接收三个参数: fromPorttoIptoPort ,用于初始化相应的成员变量。
    • 在构造函数中,尝试创建一个 DatagramSocket 并将其绑定到指定的本地端口 fromPort ,若创建失败则抛出运行时异常。
    • 创建一个 BufferedReader 对象,用于从标准输入(即控制台)读取用户输入的消息。
  • 线程执行逻辑( run 方法)
    • 使用一个无限 while 循环,持续等待用户输入消息。
    • 在循环内部,通过 reader.readLine() 方法从标准输入读取一行用户输入的消息,并将其存储在 data 变量中。
    • 将读取到的消息转换为字节数组 bytes
    • 创建一个 DatagramPacket 对象,将字节数组作为数据,指定数据的起始位置和长度,同时设置接收方的 IP 地址和端口号。
    • 调用 socket.send(datagramPacket) 方法将数据报包发送出去。
    • 检查用户输入的消息是否为 bye ,如果是,则跳出循环,结束消息发送。
  • 资源关闭
    • 当循环结束后,调用 socket.close() 方法关闭 DatagramSocket ,释放相关资源。
接收消息的线程:
package protocol;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ReceiveThread implements Runnable {
    private DatagramSocket socket = null;
    private String fromMsg ;
    private int myport;

    public ReceiveThread(String fromMsg, int myport) {
        this.fromMsg = fromMsg;
        this.myport = myport;
        try {
            // 绑定接收的端口
            socket = new DatagramSocket(this.myport);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void run() {
        while (true) {
            try{
            byte[] buf = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf, 1024);
            socket.receive(packet);
            String data = new String(packet.getData());
            data = data.trim();
            System.out.println(this.fromMsg+":"+data);
            if(data.equals("bye")){
                break;
            }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        socket.close();
    }

}
代码逻辑分析:

类定义与成员变量

  • 定义了一个名为 ReceiveThread 的类,该类实现了 Runnable 接口,这意味着它可以作为一个线程任务来执行。
  • 声明了三个成员变量:
    • DatagramSocket socket :用于接收 UDP 数据报,它是实现网络通信的关键对象。
    • String fromMsg :用于标识消息的发送方,在输出消息时会显示该信息。
    • int myport :表示接收消息时绑定的本地端口号,其他设备将数据发送到该端口。

构造函数

  • 构造函数接收两个参数: fromMsgmyport ,并将其赋值给对应的成员变量。
  • 在构造函数内部,尝试创建一个 DatagramSocket 实例,并将其绑定到指定的本地端口 myport 。如果绑定过程中出现 SocketException 异常,将抛出一个运行时异常。

线程执行逻辑( run 方法)

  • 使用一个无限 while 循环,以持续接收消息。
  • 在循环内部,执行以下操作:
    • 创建一个长度为 1024 的字节数组 buf ,用于存储接收到的数据。
    • 创建一个 DatagramPacket 对象 packet ,并将 buf 作为数据缓冲区,指定其长度为 1024。
    • 调用 socket.receive(packet) 方法,该方法会阻塞线程,直到接收到一个数据报。
    • 将接收到的数据报中的数据转换为字符串 data
    • data 字符串进行 trim() 操作,去除字符串前后的空白字符。
    • 打印接收到的消息,格式为 fromMsg:data ,方便用户识别消息来源。
    • 检查接收到的消息是否为 bye ,如果是,则跳出循环,结束消息接收。

资源关闭

  • 当循环结束后,调用 socket.close() 方法关闭 DatagramSocket ,释放相关的系统资源,确保程序的资源管理合理。
对话的学生类:
package protocol;

public class TalkStudent {
    public static void main(String[] args) {
        new Thread(new SendThread(5555,"127.0.0.1",7777)).start();
        new Thread(new ReceiveThread("老师",8888)).start();
    }
}
代码逻辑分析:

main 方法逻辑

  • 启动发送消息线程

    • 创建一个

      SendThread

      对象,构造函数传入三个参数:

      • 5555 :表示发送消息时绑定的本地端口号,即该线程将使用本地的 5555 端口来发送数据。
      • "127.0.0.1" :表示接收方的 IP 地址,这里是本地回环地址,意味着消息将发送到本地的另一个程序。
      • 7777 :表示接收方的端口号,即消息将被发送到本地 7777 端口的程序。
    • SendThread 对象作为参数传递给 Thread 类的构造函数,创建一个新的线程。

    • 调用新线程的 start() 方法启动线程,该线程会持续从标准输入读取用户输入的消息,并通过 UDP 协议将消息发送到指定的 IP 地址和端口。

  • 启动接收消息线程

    • 创建一个

      ReceiveThread

      对象,构造函数传入两个参数:

      • "老师" :用于标识消息的发送方,在输出接收到的消息时会显示该信息。
      • 8888 :表示接收消息时绑定的本地端口号,即该线程将监听本地的 8888 端口,等待接收来自其他程序的消息。
    • ReceiveThread 对象作为参数传递给 Thread 类的构造函数,创建另一个新的线程。

    • 调用该线程的 start() 方法启动线程,该线程会持续监听本地 8888 端口,接收来自其他程序的消息,并将消息按特定格式输出到控制台。

对话的老师类:
package protocol;

public class TalkTeacher {
    public static void main(String[] args) {
        new Thread(new SendThread(9999,"127.0.0.1",8888)).start();
        new Thread(new ReceiveThread("学生",7777)).start();
    }
}
代码逻辑分析:
  • main 方法逻辑
    • 启动发送消息线程
      • 创建 SendThread 对象,构造函数传入三个参数。
      • 9999 :作为发送消息时绑定的本地端口,即使用本地 9999 端口发送数据。
      • "127.0.0.1" :接收方的 IP 地址,这里是本地回环地址,表明消息将发送到本地的程序。
      • 8888 :接收方的端口号,意味着消息会被发送到本地 8888 端口的程序。
      • SendThread 对象传入 Thread 构造函数创建新线程。
      • 调用新线程的 start() 方法启动线程,此线程会持续从标准输入读取用户输入的消息,通过 UDP 协议发送到指定的 IP 地址和端口。
    • 启动接收消息线程
      • 创建 ReceiveThread 对象,构造函数传入两个参数。
      • "学生" :用于标识消息的发送方,在输出接收到的消息时显示该信息。
      • 7777 :接收消息时绑定的本地端口号,即该线程会监听本地 7777 端口,等待接收来自其他程序的消息。
      • ReceiveThread 对象传入 Thread 构造函数创建新线程。
      • 调用该线程的 start() 方法启动线程,该线程会持续监听本地 7777 端口,接收消息并按特定格式输出到控制台。

通信过程

学生端通信过程
  1. 启动发送线程
    • TalkStudent 类的 main 方法中创建 SendThread 线程,绑定本地端口 5555。
    • 线程启动后,持续从标准输入(控制台)读取用户输入的消息。
    • 将读取到的消息封装成 DatagramPacket ,并发送到老师端的 IP 地址 127.0.0.1 和端口 7777。
    • 若用户输入 bye ,则结束发送线程并关闭 DatagramSocket
  2. 启动接收线程
    • 创建 ReceiveThread 线程,绑定本地端口 8888,用于接收老师发送的消息。
    • 线程启动后,持续监听本地 8888 端口。
    • 当接收到老师发送的数据报时,将数据转换为字符串并去除前后空白。
    • 老师:消息内容 的格式将消息输出到控制台。
    • 若接收到的消息为 bye ,则结束接收线程并关闭 DatagramSocket
老师端通信过程
  1. 启动发送线程
    • TalkTeacher 类的 main 方法中创建 SendThread 线程,绑定本地端口 9999。
    • 线程启动后,持续从标准输入(控制台)读取用户输入的消息。
    • 将读取到的消息封装成 DatagramPacket ,并发送到学生端的 IP 地址 127.0.0.1 和端口 8888。
    • 若用户输入 bye ,则结束发送线程并关闭 DatagramSocket
  2. 启动接收线程
    • 创建 ReceiveThread 线程,绑定本地端口 7777,用于接收学生发送的消息。
    • 线程启动后,持续监听本地 7777 端口。
    • 当接收到学生发送的数据报时,将数据转换为字符串并去除前后空白。
    • 学生:消息内容 的格式将消息输出到控制台。
    • 若接收到的消息为 bye ,则结束接收线程并关闭 DatagramSocket
通信交互
  • 学生和老师可以在各自的控制台输入消息,输入的消息会通过 UDP 协议发送到对方对应的端口。
  • 双方的接收线程会持续监听相应端口,一旦接收到对方发送的消息,就会将消息显示在控制台。
  • 当任何一方输入 bye 时,该方的发送和接收线程都会结束,通信过程终止。