1. UDP通信

1.1. 简述

  • UDP指的是用户数据报协议(User Datagram Protocol)

  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

  • 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输

  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

1.2. Java中的UDP通信

  • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
  • Java提供了DatagramSocket类作为基于UDP协议的Socket

1.3. Java使用UDP协议

1.3.1. DatagramSocket类

  • 构造方法

    方法名 说明
    DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端
  • DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口-

  • DatagramSocket的 相关方法

    方法名 说明
    void send(DatagramPacket p) 发送数据报包
    void close() 关闭数据报套接字
    void receive(DatagramPacket p) 从此套接字接受数据报包

1.3.2. 发送数据的步骤

  1. 创建发送端的Socket对象(DatagramSocket)

  2. 创建数据,并把数据打包

  3. 调用DatagramSocket对象的方法发送数据

  4. 关闭发送端

  • 代码演示
package link.xiaomo.test3;

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

public class Demo01 {
    public static void main(String[] args) throws IOException {
        // 1 创建DatagramSocket对象
        DatagramSocket socket = new DatagramSocket();

        // 2 把要发送的数据打包
        String text = "你好";
        byte[] bytes = text.getBytes("gbk");
        InetAddress addr = InetAddress.getByName("127.0.0.1");

        // 参1 要发送的数据 byte[]类型 参2 是要发送的数据的长度
        // 参3 InetAddress对象 表示ip  参4 接收的程序的端口号
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, addr, 10000);

        // 3 发送数据
        socket.send(packet);

        // 4 关闭socket
        socket.close();
    }
}

1.3.3. 接收数据的步骤

  1. 创建接收端的Socket对象(DatagramSocket)

  2. 创建一个数据包,用于接收数据

  3. 调用DatagramSocket对象的方法接收数据

  4. 解析数据包,并把数据在控制台显示

  5. 关闭接收端

  • 构造方法

    方法名 说明
    DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包
  • 相关方法

    方法名 说明
    byte[] getData() 返回数据缓冲区
    int getLength() 返回要发送的数据的长度或接收的数据的长度
  • 示例代码

package link.xiaomo.test3;

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

public class Demo02 {
    public static void main(String[] args) throws IOException {
        //1 创建DatagramSocket 用来发送数据
        DatagramSocket ds = new DatagramSocket(11000);

        //2 创建一个空的数据包对象 用来存放接收的数据
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //3 接收数据
        System.out.println(1111);
        ds.receive(dp); //代码在这里等待接收数据
        System.out.println(222);

        //从数据包中获取网络传来的数据
        byte[] data = dp.getData();
        //打印数据 dp.getLength()是获取网络传来的数据的长度 注意和数组的长度是有区别的
        System.out.println(new String(data, 0, dp.getLength(),"gbk"));

        // 4 关闭socket
        ds.close();
    }
}

1.4.UDP三种通讯方式

  • 单播:一对一发送消息。多用于两个主机之间的端对端通信。
  • 组播:对一个分组中的所有IP发消息。组播用于对一组特定的主机进行通信。
  • 广播:用于一个主机对整个局域网上所有主机上的数据通信。

额外说明:组播地址

​ 在 IPv4 中,组播依赖于 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址3类:

  • 局部链接多播地址范围在 224.0.0.0\~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包;
  • 预留多播地址为 224.0.1.0\~238.255.255.255,可用于全球范围(如Internet)或网络协议;
  • 管理权限多播地址为 239.0.0.0\~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围。

1.5. 单播案例

1.5.1. 思路

UDP发送数据:数据来自于键盘录入,可以一直发,直到输入的数据是886,发送数据程序结束

因为接收端不知道发送端什么时候停止发送,所以此处应该采用死循环来接收

1.5.2.代码实现

/*
    UDP发送数据:
        数据来自于键盘录入,直到输入的数据是886,发送数据结束
 */
package link.xiaomo.test4;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class Demo01 {
    public static void main(String[] args) {
        try (//1 创建socket对象
             DatagramSocket ds = new DatagramSocket(20000);) {

            //2 键盘录入要发送的数据
            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.println("请输入要发送的数据");
                String text = sc.next();
                //如果输入886就结束程序
                if ("886".equals(text)) {
                    System.out.println("88");
                    break;
                }
                //3 把要发送的数据打包
                byte[] bytes = text.getBytes();
                //目标地址
                InetAddress addr = InetAddress.getByName("192.168.50.228");
                DatagramPacket dp = new DatagramPacket(bytes, bytes.length, addr, 30000);

                //4 发送数据
                ds.send(dp);
                //5 关闭socket
                //ds.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/*
    UDP接收数据:
        因为接收端不知道发送端什么时候停止发送,故采用死循环接收
 */
package link.xiaomo.test4;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.util.Scanner;

public class Demo02 {
    public static void main(String[] args) {
        try (//1 创建socket对象
             DatagramSocket ds = new DatagramSocket(30000);) {

            //2创建数据包里接收数据
            byte[] bytes = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
            while (true) {
                //3 循环一直接收数据
                ds.receive(dp);
                //4打印获取的数据
                //dp.getSocketAddress() 获取到来自于发送方的ip和端口
                SocketAddress remoteAddr = dp.getSocketAddress();
                String text = new String(dp.getData(), 0, dp.getLength());
                System.out.println("接收到了来自" + remoteAddr + "的数据" + text);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.6. 组播案例

1.6.1. 思路

  • 发送端
    1. 创建发送端的Socket对象(DatagramSocket)
    2. 创建数据,并把数据打包(DatagramPacket)
    3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑。但是在组播当中,这里是发给组播地址)
    4. 释放资源
  • 接收端
    1. 创建接收端Socket对象(MulticastSocket)
    2. 创建一个箱子(DatagramPacket),用于接收数据
    3. 把当前计算机绑定一个组播地址
    4. 将数据接收到箱子中
    5. 解析数据包,并打印数据
    6. 释放资源

1.6.2. 代码实现

// 发送端
public class SendDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        // 2. 创建数据,并把数据打包(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端Socket对象(MulticastSocket)
        MulticastSocket ms = new MulticastSocket(10000);
        // 2. 创建一个箱子,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        // 4. 将数据接收到箱子中
        ms.receive(dp);
        // 5. 解析数据包,并打印数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 6. 释放资源
        ms.close();
    }
}

1.7. 广播案例

1.7.1. 思路

实现广播的方式:发送的目标地址 写成255.255.255.255就可以了!

  • 发送端
    1. 创建发送端Socket对象(DatagramSocket)
    2. 创建存储数据的箱子,将广播地址封装进去
    3. 发送数据
    4. 释放资源
  • 接收端
    1. 创建接收端的Socket对象(DatagramSocket)
    2. 创建一个数据包,用于接收数据
    3. 调用DatagramSocket对象的方法接收数据
    4. 解析数据包,并把数据在控制台显示
    5. 关闭接收端

1.7.2. 代码实现

// 发送端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建发送端Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        // 2. 创建存储数据的箱子,将广播地址封装进去
        String s = "广播 hello";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("255.255.255.255");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 发送数据
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10000);
        // 2. 创建一个数据包,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        // 4. 解析数据包,并把数据在控制台显示
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 5. 关闭接收端
        ds.close();
    }
}

1.8. 同时可以收发数据的UDP案例

  • 发数据的任务
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UdpSendRunnable implements Runnable {

    private String addr;//目标ip
    private int port;//目标端口
    DatagramSocket socket;
    Scanner sc = new Scanner(System.in);

    public UdpSendRunnable(DatagramSocket socket) {
        this.socket = socket;
    }

    public UdpSendRunnable(DatagramSocket socket, String addr, int port) {
        this.socket = socket;
        this.addr = addr;
        this.port = port;
    }

    @Override
    public void run() {
        // 2 把要发送的数据打包
        while (true) {
            try {
                String text = sc.next();
                byte[] bytes = text.getBytes();
                InetAddress iAddr = InetAddress.getByName(addr);
                // 参1 要发送的数据 byte[]类型 参2 是要发送的数据的长度
                // 参3 InetAddress对象 表示ip  参4 接收的程序的端口号
                DatagramPacket packet = new DatagramPacket(bytes, bytes.length, iAddr, port);
                // 3 发送数据
                socket.send(packet);
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }
}
  • 收数据的代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class UdpReceiveRunnable implements Runnable {
    DatagramSocket socket;

    public UdpReceiveRunnable(DatagramSocket socket) {
        this.socket = socket;

    }

    @Override
    public void run() {
        while (true) {
            try {
                // 3 接收的代码
                // 1创建一个空的数据包对象
                byte[] bytes = new byte[1024];
                DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                // 2接收数据 存到数据包DatagramPacket对象里
                socket.receive(packet); // 这里会接收数据 是阻塞的
                // 3 打印数据
                byte[] data = packet.getData();
                // 获取发送信息的 地址
                SocketAddress socketAddress = packet.getSocketAddress();
                System.out.println("来自于" + socketAddress + "发的数据:" + new String(data, 0, packet.getLength()));
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }
}
  • 程序入口
import java.io.IOException;
import java.net.DatagramSocket;
public class Demo04 {
    public static void main(String[] args) throws IOException {
        // 1 创建DatagramSocket对象
        DatagramSocket socket = new DatagramSocket(12000);
        // 创建发送任务的对象
        UdpSendRunnable udpSendRunnable = new UdpSendRunnable(socket, "127.0.0.1", 14000);
        // 创建接收任务的对象
        UdpReceiveRunnable udpReceiveRunnable = new UdpReceiveRunnable(socket);

        new Thread(udpSendRunnable).start(); //开启线程
        new Thread(udpReceiveRunnable).start();//开启线程

        // 4 关闭socket
        // socket.close();
    }
}
如人饮水,冷暖自知。
最后更新于 2023-08-05