1. 程式人生 > >[瘋狂Java]UDP:接收發送資料報、獲取接收到的資料報的相關資訊

[瘋狂Java]UDP:接收發送資料報、獲取接收到的資料報的相關資訊

1. Java的UDP模型:

    1) DatagramSocket是UDP的socket,由於它只是個碼頭只能發貨和收貨,因此它就只有兩個方法,一個是send用來發送資料報,一個即使receive用來接收資料報;

    2) 由於DatagramSocket只是個碼頭,因此只能繫結自己的IP地址和埠(構造器),不能在構造器中指定目的地的IP地址和埠,碼頭怎麼會自己移動呢?因此只能繫結自己的地理位置而已;

    3) 資料傳遞的主體是資料報DatagramPacket,因此資料報送到哪兒只有資料報自己知道,所以要在資料報中指定目的地的IP地址和埠號(在資料報的構造器中指定);

    4) 傳送資料/接受資料的步驟:

         i. 傳送時肯定是先建立一個數據報,然後往裡面寫上內容,並附上目的地的IP地址和埠,然後在通過socket的send傳送出去;

         ii. 接收時肯定是先建立一個空的資料報,然後用socket的receive方法接收(將受到的資料寫入空的資料報中),然後取出資料分析;

2. 接收和傳送:

    1) 首先需要構造資料報:所有的資料報都需要依賴一個位元組陣列來存放資料,即byte[],因為資料報底層要求必須用無型別的位元組塊傳輸(且大小不超過64KB),不能用流傳輸(什麼InputStream之類的,流就是不行);

         i. 接收資料的資料報:

            a. DatagramPacket(byte buf[], int length); // 指定快取buff和長度length

            b. DatagramPacket(byte buf[], int offset, int length); // 指定從buf的offset位置處存放資料

!!可以看到,已經有一種強烈的C語言風格,非常的繁瑣,不僅要指定快取區,還要指定長度,這些都不能自動識別

         ii. 傳送資料:肯定還需要指定目的地的IP地址和埠號了

             a. DatagramPacket(byte buf[], int length, InetAddress address, int port);

             b. DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port); // 指定從buf的offset開始傳送資料

             c. 最後兩個引數可以合二為一直接用一個SocketAddress物件代替(過載版本),即InetAddress和port的結構體;

    2) 接著就是傳送和接受資料報了,直接使用DatagramSocket的send和receive方法(都是物件方法)就行了:

         i. void DatagramSocket.send(DatagramPacket p); // 準備好的資料報

!!傳送資料時socket還會將客戶端的有關資訊(IP和埠)一併包裝在資料報的報頭中傳送,這樣在伺服器端可以通過各種方法獲取報頭中有關客戶端的相關資訊;

         ii. synchronized void receive(DatagramPacket p); // 準備好接收用的資料報

!!receive到資料報之前呼叫該方法的執行緒會一直阻塞,要注意這點,合理利用多執行緒提高效率;

!!可以看到,這裡沒有C/S一說,你一個數據報既可以做傳送資料報也可以做接收資料報,由於send不改變資料報中的內容,而receive會改變,因此receive需要同步;

    3) 當然在接受和傳送之前肯定要準備好DatagramSocket物件的,瞭解一下它的構造器:只需要繫結本機(本應用)的IP地址和埠號即可:

         i. DatagramSocket(); // 繫結預設IP和埠

         ii. DatagramSocket(int port); // 繫結預設IP和指定埠

         iii. DatagramSocket(int port, InetAddress laddr); // 全指定

         iv. DatagramSocket(SocketAddress bindaddr); // 全指定,更簡潔方便

2. 獲取接收到的資料報中的相關資訊:

    1) 這裡是指獲取和客戶端相關的資訊,無非就是客戶端的IP地址和埠號而已,以及獲取其中的資料;

    2) receive之後便可以呼叫DatagramPacket的相關方法獲取資訊了;

    3) synchronized int getPort(); // 獲取客戶端的埠號

    4) synchronized InetAddress getAddress(); // 獲取客戶端的IP地址

    5) synchronized SocketAddress getSocketAddress(); // 打包一起獲取

    6) 獲取接受到的資料:synchronized byte[] getData(); // 返回的就是構造DatagramPacket傳入的快取buf,可以自己測試一下

    7) 所有的這些方法都是同步的,因為UDP部分發送資料的資料報和接收資料的資料報,兩者用的都是同一種類型,因此一個傳送資料的資料報可以在幾行程式碼之後變成接收資料的資料報,這就存在多執行緒衝突的問題,因此必須同步;

!!但是不建議兩者混用,傳送和接收區分開來,這樣不容易出錯,養成良好的習慣,保證程式的安全性和健壯性

3. 示例:

public class Server {
	
	private static final int PORT = 30000; // 本機埠
	private static final int PACKET_SIZE = 4096; // 報的大小
	
	private byte[] buff = new byte[PACKET_SIZE]; // 輸入緩衝區
	private DatagramPacket inPack = new DatagramPacket(buff, buff.length);
	private DatagramPacket outPack;
	
	private String[] books = new String[] { // 隨便返回給客戶端的內容
			"學雷鋒",
			"長征",
			"天安門",
			"延安"
	};
	
	public void init() throws IOException {
		try (DatagramSocket sock = new DatagramSocket(PORT)) {
			for (int i = 0; i < 100; i++) {
				sock.receive(inPack);
				System.out.println(buff == inPack.getData()); // true
				System.out.println(new String(buff, 0, inPack.getLength())); // 列印接收包的內容
				
				byte[] outBuff = books[i % 4].getBytes(); // 包裝回送
				outPack = new DatagramPacket(outBuff, outBuff.length, inPack.getSocketAddress());
				sock.send(outPack);
			}
		}
	}

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		new Server().init();
	}

}
public class Client {

	private static final int DEST_PORT = 30000;
	private static final String DEST_IP = "127.0.0.1";
	private static final int PACK_SIZE = 4096;
	private byte[] inbuf = new byte[PACK_SIZE];
	private DatagramPacket inpack = new DatagramPacket(inbuf, inbuf.length);
	private DatagramPacket outpack;
	
	public void init() throws IOException {
		try(DatagramSocket sock = new DatagramSocket()) {
			outpack = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT);
			Scanner scan = new Scanner(System.in);
			while (scan.hasNextLine()) { // 鍵盤輸入
				byte[] buf = scan.nextLine().getBytes(); // 捕獲傳送
				outpack.setData(buf);
				sock.send(outpack);
				
				sock.receive(inpack); // 阻塞等待接受
				System.out.println(new String(inbuf, 0, inpack.getLength())); // 列印回送內容
			}
		}
	}
	
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		new Client().init();
	}

}
!可以直接通過DatagramSocket的setData方法改變和其繫結的byte[]快取區:synchronized void DatagramSocket.setData(byte[] buf); // 大小預設設成buf的length,預設從offset=0處開始