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();
    }
}

創建一個學生類和教師類,分別啓動發送和接收端口

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章