第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Java UDP Socket 數(shù)據(jù)收發(fā)

1. 前言

UDP 是面向數(shù)據(jù)報(bào)的傳輸協(xié)議。UDP 的包頭非常簡單,總共占用 8 字節(jié)長度,格式如下:

+--------------------+--------------------+
| 源端口(16 bits)   | 目的端口(16 bits) |
+--------------------+--------------------+
| 包的長度(16 bits) | 檢驗(yàn)和(16 bits)   |
+--------------------+--------------------+

源端口號:占用 2 字節(jié)長度,用于標(biāo)識發(fā)送端應(yīng)用程序。
目的端口:占用 2 字節(jié)長度,用于標(biāo)識接收端應(yīng)用程序。
包的長度:表示 UDP 數(shù)據(jù)包的總長度,占用 2 字節(jié)長度。包的長度的值是 UDP 包頭的長度加上 UDP 包體的長度。 包體最大長度是 65536-8 = 65528 字節(jié)。

提示:
網(wǎng)絡(luò)層的 IPv4 Header 也包含了 Length 字段,IPv4 Payload 的最大長度是 65536-60 = 65476 字節(jié)。如果我們控制 UDP 數(shù)據(jù)包總長度不超過 65476 字節(jié),UDP Header 其實(shí)是不需要 UDP Length 字段的。因?yàn)樵趯?shí)際開發(fā)中,程序員會保證傳給 UDP 的數(shù)據(jù)長度不超過 MTU 最大限度,所以 UDP Length 字段顯得有點(diǎn)兒多余。

檢驗(yàn)和:占用 2 字節(jié)長度。UDP 檢驗(yàn)和是用于差錯(cuò)檢測的,檢驗(yàn)計(jì)算包含 UDP 包頭和 UDP 包體兩部分。

從 UDP 的協(xié)議格式可以看出,UDP 保證了應(yīng)用層消息的完整性,比如,UDP 客戶端向服務(wù)器發(fā)送 “Hello Server,I’m client。How are you?”,UDP 客戶端發(fā)送的是具有一定含義的數(shù)據(jù),UDP 服務(wù)端收到也是這個(gè)完整的消息。不像面向字節(jié)流的 TCP 協(xié)議,需要應(yīng)用程序解析消息。為此,UDP 編程會簡單很多。

2. Java DatagramPacket 分析

Java 抽象了 java.net.DatagramPacket 類表示一個(gè) UDP 數(shù)據(jù)報(bào),主要功能如下:

  • 發(fā)送:
    • 設(shè)置發(fā)送的數(shù)據(jù)。
    • 設(shè)置接收此數(shù)據(jù)的目的主機(jī)的 IP 地址和端口號。
    • 獲取發(fā)送此數(shù)據(jù)的源主機(jī)的 IP 地址和端口號。
  • 接收:
    • 設(shè)置接收數(shù)據(jù)的 byte 數(shù)組。
    • 獲取發(fā)送此數(shù)據(jù)的源主機(jī)的 IP 地址和端口號。
    • 獲取接收此數(shù)據(jù)的主機(jī)目的主機(jī)的 IP 地址和端口號。

接收數(shù)據(jù)的構(gòu)造方法:

public DatagramPacket(byte[] buffer, int length)
public DatagramPacket(byte[] buffer, int offset, int length)

當(dāng)接收數(shù)據(jù)的時(shí)候,需要構(gòu)造 java.net.DatagramPacket 的實(shí)例,并且要傳入接收數(shù)據(jù)的 byte 數(shù)組,然后調(diào)用 java.net.DatagramSocket 的 receive 方法就可以接收數(shù)據(jù)。當(dāng) receive 方法調(diào)用返回以后,發(fā)送此數(shù)據(jù)包的源主機(jī) IP 地址和端口號保存在 java.net.DatagramSocket 的實(shí)例中。

發(fā)送數(shù)據(jù)的構(gòu)造方法:

public DatagramPacket(byte[] data, int length,InetAddress destination, int port)
public DatagramPacket(byte[] data, int offset, int length,InetAddress destination, int port)
public DatagramPacket(byte[] data, int length,SocketAddress destination)
public DatagramPacket(byte[] data, int offset, int length,SocketAddress destination)

當(dāng)發(fā)送數(shù)據(jù)的時(shí)候,同樣需要構(gòu)造 java.net.DatagramPacket 的實(shí)例,并且要傳入將要發(fā)送的數(shù)據(jù)的 byte 數(shù)組,同時(shí)要傳入接收此數(shù)據(jù)包的目標(biāo)主機(jī) IP 地址和端口號,然后調(diào)用 java.net.DatagramSocket 的 send 方法就可以發(fā)送數(shù)據(jù)。目標(biāo)主機(jī)的 IP 地址和端口號保存在 java.net.DatagramSocket 的實(shí)例中,你可以調(diào)用它的 getSocketAddress 方法獲取。

獲取或設(shè)置數(shù)據(jù):

public byte[] getData()

public void setData(byte[] data)
public void setData(byte[] data, int offset, int length)

獲取或設(shè)置數(shù)據(jù)的長度:

public int getLength()
public void setLength(int length)

獲取設(shè)置 IP 地址和端口號

public int getPort()
public InetAddress getAddress() // 只能獲取 IP
public SocketAddress getSocketAddress()// 同時(shí)獲取 IP 和 Port

public void setAddress(InetAddress remote)// 只能設(shè)置 IP
public void setPort(int port)
public void setAddress(SocketAddress remote)// 設(shè)置 SocketAddress,同時(shí)設(shè)置 IP 和 Port

3. UDP 消息序列化與反序列化

java.io.ByteArrayInputStream 和 java.io.ByteArrayOutputStream 繼承自 java.io.InputStream 和 java.io.OutputStream。可以作為流的源和流的目標(biāo)類,當(dāng)你需要解析復(fù)雜的協(xié)議格式的時(shí)候,可以配合 java.io.DataInputStream 和 java.io.DataOutputStream 類實(shí)現(xiàn)消息的序列化、反序列化。

下來我們定義一個(gè)簡單的消息格式,通常在音視頻通信中會遇到這樣的消息格式,我們這里只是一個(gè)演示版本。具體字段如下:

  • version 表示協(xié)議版本號,這是一般協(xié)議格式都會包含的一個(gè)字段。
  • flag,一些控制標(biāo)志,主要表現(xiàn)在用不同的 bit 位表示不同的控制標(biāo)志。
  • sequence,對每個(gè)消息進(jìn)行編號,用來檢測是否有丟包發(fā)生。
  • timestamp,每一個(gè)消息都攜帶一個(gè)發(fā)送時(shí)間戳,可以計(jì)算網(wǎng)絡(luò)延遲、抖動。
  • 消息體,消息的具體內(nèi)容。

圖示如下:

+-----------------+-----------------+-----------------|-----------------+
| version(8 bits) | flag(8 bits)    |          Sequence(16 bits)        |
+-----------------|-----------------+-----------------------------------+
|                  Timestamp(32 bits)                                   |
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                                                                       |
|                       Message Body                                    |
|                                                                       |
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

對于這樣一個(gè)格式,通過 java.net.DatagramPacket 類讀取或者是設(shè)置的是一個(gè) byte 數(shù)組,要想解析數(shù)組中消息各個(gè)字段的含義,需要借助 java.io.ByteArrayInputStream 和 java.io.ByteArrayOutputStream 類,以及 java.io.DataInputStream 和 java.io.DataOutputStream 類。

我們設(shè)計(jì)了一個(gè) Message 類用來表示消息結(jié)構(gòu),當(dāng)然 Message 類要包含協(xié)議格式中的各個(gè)字段。除了提供各個(gè)屬性的 getter/setter 方法外,還提供了 serialize 和 deserialize 方法,實(shí)現(xiàn)了消息的序列化、反序列化。最后,我們覆蓋了 toString 方,將 Message 轉(zhuǎn)換成 String 形式。

代碼清單如下:

import java.io.*;
import java.net.DatagramPacket;

public class Message implements Serializable {
    private static final int SEND_BUFF_LEN = 512;
    private static ByteArrayOutputStream outArray = new ByteArrayOutputStream(SEND_BUFF_LEN);

    private byte version =1;
    private byte flag;
    private short sequence;
    private int timestamp;
    private byte[] body = null;
    private int bodyLength = 0;

    public byte getVersion() {
        return version;
    }

    public void setVersion(byte version) {
        this.version = version;
    }

    public byte getFlag() {
        return flag;
    }

    public void setFlag(byte flag) {
        this.flag = flag;
    }

    public short getSequence() {
        return sequence;
    }

    public void setSequence(short sequence) {
        this.sequence = sequence;
    }

    public int getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(int timestamp) {
        this.timestamp = timestamp;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }

    public DatagramPacket serialize()
    {
        try {
            outArray.reset();
            DataOutputStream out = new DataOutputStream(outArray);

            out.writeByte(this.getVersion());
            out.writeByte(this.getFlag());
            out.writeShort(this.getSequence());
            out.writeInt(this.getTimestamp());
            out.write(this.body);
            // 構(gòu)造發(fā)送數(shù)據(jù)包,需要傳入消息內(nèi)容和目標(biāo)地址結(jié)構(gòu) SocketAddress
            byte[] outBytes = outArray.toByteArray();
            DatagramPacket message = new DatagramPacket(outBytes,  outBytes.length);

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

        return null;
    }
    public void deserialize(DatagramPacket inMessage)
    {
        try {
            DataInputStream in = new DataInputStream(
                    new ByteArrayInputStream(inMessage.getData(), 0, inMessage.getLength()));
            this.version = in.readByte();
            this.flag = in.readByte();
            this.sequence = in.readShort();
            this.timestamp = in.readInt();
            this.body = new byte[512];
            this.bodyLength = in.read(this.body);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return " version: " + this.getVersion()
                + " flag: " + this.getFlag()
                + " sequence: " + this.getSequence()
                + " timestamp: " + this.getSequence()
                + " message body: " +new String(body, 0, this.bodyLength);
    }
}

通過 DataOutputStream 和 ByteArrayOutputStream 的配合,實(shí)現(xiàn) serialize 功能。通過 DataInputStream 和 ByteArrayInputStream 配合,實(shí)現(xiàn) deserialize 功能。

Message 序列化的用法:

private static final int PORT = 9002;
private static final String DST_HOST = "127.0.0.1";
private static short sequence = 1;

SocketAddress to = new InetSocketAddress(DST_HOST, PORT);

String req = "Hello Server!";
Message sMsg = new Message();
sMsg.setVersion((byte)1);
sMsg.setFlag((byte)21);
sMsg.setSequence(sequence++);
sMsg.setTimestamp((int)System.currentTimeMillis()&0xFFFFFFFF);
sMsg.setBody(req.getBytes());
DatagramPacket outMessage = sMsg.serialize();
 outMessage.setSocketAddress(to);

Message 反列化的用法:

Message rMsg = new Message();
rMsg.deserialize(inMessage);// inMessage 是一個(gè) DatagramPacket 類型的實(shí)例

4. 小結(jié)

本節(jié)首先介紹了 java.net.DatagramPacket 類的基本功能,這是 Java UDP Socket 程序進(jìn)行數(shù)據(jù)讀寫的基礎(chǔ)類。在調(diào)用 receive 方法接收數(shù)據(jù)之前,首先要創(chuàng)建 DatagramPacket 的實(shí)例,同時(shí)要為他提供一個(gè)介紹數(shù)據(jù)的字節(jié)數(shù)組。當(dāng) receive 方法成功返回后,你可以調(diào)用 DatagramPacket 的 getSocketAddress 方法獲取發(fā)送主機(jī)的源 IP 地址和端口號。在調(diào)用 send 方法發(fā)送數(shù)據(jù)之前,首先要創(chuàng)建 DatagramPacket 的實(shí)例,將要發(fā)送的數(shù)據(jù)傳給他,同時(shí)要將接收數(shù)據(jù)的目標(biāo)主機(jī)的 IP 地址和端口號設(shè)置給它。

接著我們重點(diǎn)介紹了 UDP 編程中常見的協(xié)議格式定義、解析方法,主要是通過 java.io.ByteArrayInputStream 和 java.io.ByteArrayOutputStream 類,以及 java.io.DataInputStream 和 java.io.DataOutputStream 類實(shí)現(xiàn)消息的序列化、反序列化功能。我們提供了完整的實(shí)現(xiàn)代碼,已經(jīng)序列化、反序列化的具體用法??梢哉f這一部分內(nèi)容在實(shí)踐中經(jīng)常會遇到,需要好好掌握。