JAVA网络编程(二):UDP编程

目录

什么是UDP?

UDP特点

UDP编程核心类

UDP编程

1、基本通信

2、基本类型数据通信

3、对象类型数据通信

4、文件类型通信

5、多次通信

6、双向通信


什么是UDP?

UDP(User Datagram Protocol)用户数据报协议,为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法。

UDP特点

  1. 非面向连接,即通讯前不需要建立连接
  2. 高效
  3. 不可靠,可能存在丢包
  4. 大小有限制,一般来是数据包大小不要超过60K
  5. 不存在客户端与服务器的概念,每个端都是平等的
  6. JAVA编程中,将数据封装到DatagramPacket,指定目标地址进行数据传输

UDP编程核心类

DatagramSocket:该类表示用于发送/接收数据包的套接字

DatagramPacket:该类表示被传输的数据包

UDP编程

UDP编程可以概括为以下几步

发送端

  1. 创建发送端:DatagramSocket(int port)对象
  2. 准备数据,转为字节数组
  3. 将字节数组封装为数据报包:DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)对象
  4. 发送/接收数据
  5. 释放资源

接收端

  1. 创建接收端:DatagramSocket(int port)对象
  2. 准备容器,封装为DatagramPacket包:DatagramPacket(byte[] buf, int offset, int length)对象
  3. 阻塞式接收包
  4. 解析包
  5. 释放资源

1、基本通信

发送端

public class UdpSender {
    public static void main(String[] args) throws Exception  {
        // 1、指定端口,创建发送端
        DatagramSocket client = new DatagramSocket(8888);

        // 2、准备数据,转成字节数组
        String str = "Hello World!你好";
        byte[] datas = str.getBytes();

        // 3、将字节数组封装成包,
        // 发送端构造方法传递 ——> 
        // 数据字节数组,起始位置,长度,IP套接字地址(接收端的ip/主机 + 端口)
        DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
            new InetSocketAddress("localhost", 9999));

        // 4、发送数据
        client.send(packet);

        // 5、释放资源
        client.close();
    }
}

接收端

public class UdpReceiver {
    public static void main(String[] args) throws Exception {
        // 1、指定端口,创建接收端,此处的端口应对应发送端数据包传递的端口
        DatagramSocket server = new DatagramSocket(9999);

        // 2、准备容器,封装成DatagramPacket包
        byte[] contanier = new byte[60*1024]; // 60K
        // 接收端的数据包不用传递IP套接字地址
        DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);

        // 3、阻塞式接收包,会一直等待数据包
        System.out.println("等待接收");
        server.receive(packet);
        System.out.println("接收到数据包,进行解析");

        // 4、解析包
        byte[] datas = packet.getData(); // 字节数组
        int len = packet.getLength(); // 长度
        System.out.println(new String(datas));
        System.out.println(len);

        // 5、释放资源
        server.close();
    }
}

接收端控制台

2、基本类型数据通信

与基本通信相同,唯一需要改动的部分就是发送端的数据转字节数组,接收端的字节数组转数据

发送端

// 1、指定端口,创建发送端

// 2、准备基本类型数据,转成字节数组
// ByteArrayOutputStream 用于将数据转为字节数组
// BufferedOutputStream 用于缓冲,提升效率
// DataOutputStream 用于将基本类型数据写入输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));

dos.writeInt(1);
dos.writeChar('a');
dos.writeBoolean(true);
dos.writeFloat(1.2f);
dos.flush(); // 调用flush()将缓冲区数据输出

byte[] datas = baos.toByteArray();

// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源

接收端

// 1、指定端口,创建接收端
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包

// 4、解析基本类型
// ByteArrayInputStream 获取传递过来的输入流
// BufferedInputStream 用于缓冲提升效率
// DataInputStream 用于读取输入流中的基本数据类型
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());
DataInputStream dis = new DataInputStream(new BufferedInputStream(bais));

// 取值顺序应和写入顺序一致
System.out.println(dis.readInt());
System.out.println(dis.readChar());
System.out.println(dis.readBoolean());
System.out.println(dis.readFloat());

// 5、释放资源

接收端控制台

3、对象类型数据通信

对象要能够被传输,必须实现Serializable接口,最好提供一个serialVersionUID

Person.java

public class Person implements Serializable {
    private static final long serialVersionUID = -3201672726868875942L;

    private String name;
    private transient int age; // 使用transient修饰的属性不会被序列化传输

    public String getName() {
        return name;
    }

    // get/set/constructor方法省略
}

发送端

// 1、指定端口,创建发送端

// 2、准备对象类型数据,转成字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(baos));

oos.writeObject(new Person("IcyDate", 18));
oos.flush();

byte[] datas = baos.toByteArray();

// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源

接收端

// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包

// 4、解析对象类型,取值顺序应和写入顺序一致
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(bais));

Object obj = ois.readObject();
Person person = null;
if (obj instanceof Person) {
    person = (Person) obj;
}

System.out.println("name----->" + person.getName());
System.out.println("age----->" + person.getAge());

// 5、释放资源

接收端控制台

4、文件类型通信

文件在发送端首先使用FileInputStream读取,再写入ByteArrayOutputStrem并转为字节数组

发送端

// 1、指定端口,创建发送端

// 2、准备基本类型数据,转成字节数组
File file = new File("src/test/1.png"); // 文件大小不超过接收端容器大小
FileInputStream fis = new FileInputStream(file); // 也可直接传递文件地址
ByteArrayOutputStream baos = new ByteArrayOutputStream();

// flush代表每次读取和写入的大小,10K
byte[] flush = new byte[1024*10];
int len = -1;
// 文件输入流读取最多flush.length字节的数据到字节数组,读取完毕返回-1
while ((len = fis.read(flush)) != -1) {
    // 往字节输出流中写入读取出的数据
    baos.write(flush, 0, len);
}

byte[] datas = baos.toByteArray();

// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源

接收端

// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包

// 4、解析基本类型,取值顺序应和写入顺序一致
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());

File file = new File("src/test/copy.png"); // 需要写入的文件路径
FileOutputStream fos = new FileOutputStream(file);

byte[] flush = new byte[5];
int len = -1;
// 从数据包中的输入流里读取并写入文件输出流
while ((len = bais.read(flush)) != -1) {
    fos.write(flush, 0, len);
}
fos.flush();


// 5、释放资源

5、多次通信

之前的例子都是单次的通信,多次通信只需要发送端循环接收控制台输入并发送,接收端循环接收数据包并解析即可

发送端

// 1、指定端口,创建发送端

// 2、准备数据,转成字节数组
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
    String str = br.readLine(); // 接收控制台输入
    byte[] datas = str.getBytes();

    // 3、将字节数组封装成包
    DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
            new InetSocketAddress("localhost", 9999));

    // 4、发送数据
    client.send(packet);
    if ("bye".equals(str)) {
        break;
    }
}

// 5、释放资源

接收端

// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口

// 2、准备容器,封装成DatagramPacket包
while (true) {
    byte[] contanier = new byte[60*1024];
    DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);

    // 3、阻塞式接收包
    server.receive(packet);

    // 4、解析包
    byte[] datas = packet.getData();
    int len = packet.getLength();
    String msg = new String(datas, 0, len);
    System.out.println(msg);

    if ("bye".equals(msg)) {
        break;
    }
}

// 5、释放资源

6、双向通信

双向通信要求每一端,既能发送数据,也能接收数据。那就需要一个线程来执行发送端,一个线程执行接收端。也就需要我们把发送端和接受端封装成两个类并实现Runnable接口

发送端

public class UdpSender implements Runnable{
    private DatagramSocket client;
    private BufferedReader br;

    private String toIP;
    private int toPort;

    private String sender;

    // 用构造方法来指定发送端端口,接收端ip+端口,和发送人
    UdpSender(int port, String toIP, int toPort, String sender) {
        this.toIP = toIP;
        this.toPort = toPort;
        this.sender = sender;

        try {
            client = new DatagramSocket(port);
            br = new BufferedReader(new InputStreamReader(System.in));
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            String str = null;
            try {
                str = sender + ":" + br.readLine();
                byte[] datas = str.getBytes();

                // 3、将字节数组封装成包
                DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
                        new InetSocketAddress(toIP, toPort));

                // 4、发送数据
                client.send(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 5、释放资源
        client.close();
    }
}

接收端

public class UdpReceiver implements Runnable{
    private DatagramSocket server;

    // 指定接收端端口
    UdpReceiver(int port) {
        try {
            server = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        // 2、准备容器,封装成DatagramPacket包
        while (true) {
            byte[] contanier = new byte[60*1024];
            DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);

            // 3、阻塞式接收包
            try {
                server.receive(packet);
                // 4、解析包
                byte[] datas = packet.getData();
                int len = packet.getLength();
                String msg = new String(datas, 0, len);
                System.out.println(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 5、释放资源
        server.close();
    }
}

创建一个学生类和教师类,分别启动发送和接收端口

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值