1. 程式人生 > >【Java TCP/IP Socket程式設計】----套接字----UDP Socket

【Java TCP/IP Socket程式設計】----套接字----UDP Socket

目錄

 

簡介

UDP通訊

UDP通訊案例

UDP套接字注意點


簡介

UDP是面向無連線的協議,在資料傳輸時,資料的傳送端和接收端不建立邏輯上的連線。當一臺計算機向另外一臺計算機發送資料時,傳送端不會確認接收端是否存在,同樣接收端接收到資料時,也不會發送反饋給傳送端。實際上UDP協議只實現了兩個功能:

      1)在IP協議的基礎上添加了另一層地址(埠)。

      2)對資料傳輸過程中可能產品的資料錯誤進行了檢測,並拋棄已經損壞的資料。

UDP協議開銷比較小,並且傳輸效率比較高,經常用於傳送音訊,視訊或者普通檔案,即使存在丟失一兩個包的情況,影響不會太大。

Java中提供了DatagramPacket類和DatagramSocket類來使用UDP套接字,客戶端和伺服器端都使用的是DatagramSocket來發送資料,使用DatagramPacket來接收資料。

傳送資訊時,需要建立一個包含了待發送資訊的DatagramPacket例項,並將其作為引數傳遞給DatagramSocket類的send()方法。

接收資訊時,同樣需要建立一個DatagramPacket例項,該例項中預先分配了一些空間(一個位元組陣列byte[]),並將接收到的資訊存放在該空間中,然後將該例項作為引數傳遞給DatagramSocket類的receive()方法。

需要注意的是:除了輸出的資訊外,DatagramPacket例項裡面還包含了地址和埠資訊,如果是傳送資料報文,DatagramPacket裡面地址指明瞭目的地址和埠號,如果是接收到的資料報文,DatagramPacket例項中的地址指明瞭所接收資訊的源地址。

UDP通訊

UDP客戶端:

UDP客戶端首先向被動等待聯絡的伺服器端傳送一個數據報文。一個典型的UDP客戶端主要執行以下三步

      1.建立一個DatagramSocket例項,可以選擇對本地地址和埠號進行設定。

      2.使用DatagramSocket類的send()和receive()方法來發送和接收DatagramPacket例項,進行通訊。

      3.通訊完成後,使用DatagramSocket類的close()方法來銷燬該套接字。

UDP伺服器端

與TCP伺服器一樣,UDP伺服器的工作是建立一個通訊終端,並被動等待客戶端發起連線。但由於UDP是無連線的,UDP通訊通過客戶端的資料報文初始化,並沒有TCP中建立連線那一步。典型的UDP伺服器要執行以下三步

      1.建立一個DatagramSocket例項,指定本地埠號,並可以選擇指定本地地址。此時伺服器已經準備好從任何客戶端接收資料報文。

      2.使用DatagramSocket類receive()方法來接收一個DatagramPacket例項。當receive()方法返回時,資料報文裡面包含了客戶端的地址,由此可知反饋資訊的目的地。

      3.使用DatagramSocket類的send()和receive()方法來發送和接收DatagramPacket例項,進行通訊。

UDP通訊案例

public class UDPEchoClient {
  private static final int MAXTRIES =5;
  private static final String INFO = "hello server";

  public static void main(String[] args) throws IOException {
    DatagramSocket socket = new DatagramSocket(4321);
    socket.setSoTimeout(1000);
    byte bytes[] = new byte[1024];
    InetAddress address = InetAddress.getLocalHost();
    DatagramPacket sendPacket = new DatagramPacket(INFO.getBytes(), INFO.length(), address, 1234);
    DatagramPacket receivePacket = new DatagramPacket(bytes, 1024);
    boolean receiveResponse = false;
    int tries = 0;
    do {
      socket.send(sendPacket);
      try {
        socket.receive(receivePacket);
        if (!receivePacket.getAddress().equals(address)) {
          throw new IOException("Receive Packet from unknown source");
        }
        receiveResponse = true;
      } catch (Exception e) {
        tries+=1;
        System.out.println("time out,"+(MAXTRIES-tries)+"more tries");
      }
    } while ((tries < 5) && (!receiveResponse));
    if (receiveResponse) {
      System.out.println("Received from Server:");
      System.out.println(new String(receivePacket.getData(),0,receivePacket.getLength())+" from:"+receivePacket.getAddress().getHostAddress()+"---"+receivePacket.getPort());
      receivePacket.setLength(1024);
    }else {
      System.out.println("no response -----giving up");
    }
    socket.close();
  }
}
public class UDPEhoServer {
  private static final int LENGTH = 255;
  private static final String MESSAGE = "hello client";

  public static void main(String[] args) throws IOException {
    DatagramSocket socket = new DatagramSocket(1234);
    byte[] buffer = new byte[LENGTH];
    DatagramPacket receivePacket = new DatagramPacket(buffer, LENGTH);
    while (true) {
      socket.receive(receivePacket);
      System.out.println("Received from Client:");
      System.out.println(new String(receivePacket.getData(), 0, receivePacket.getLength()) + " from :"
          + receivePacket.getAddress().getHostAddress() + "---" + receivePacket.getPort());
      DatagramPacket sendPacket = new DatagramPacket(MESSAGE.getBytes(), MESSAGE.length(), receivePacket.getAddress(), 4321);
      socket.send(sendPacket);
      receivePacket.setLength(LENGTH);
    }
  }
}

執行結果:

Received from Server:
hello client from:10.170.234.245---1234

Received from Client:
hello server from :10.170.234.245---4321

可見UDP伺服器端要顯式設定本地埠,並使客戶端知道該埠。設定DatagramPacket引數的埠是為了傳送到伺服器端的指定埠,伺服器端設定DatagramSocket的引數為對應埠以接收指定埠過來的資料。

UDP套接字注意點

1.UDP套接字與TCP套接字重要但微小區別是:UDP協議保留了訊息的邊界資訊。

      DatagramSocket的每一次receive()呼叫最多接收呼叫一次send()方法傳送的資料,不同的receive()方法呼叫絕不會返回同一個send()方法呼叫所傳送的資料。

      當在TCP套接字的輸出流上呼叫write()方法返回後,所有呼叫者都知道資料已經被複制到一個傳輸快取區中,實際上此時資料可能已經被髮送,也有可能還沒有被傳送,而UDP協議沒有提供從網路錯誤中恢復的機制,因此,並不對可能需要重傳的資料進行快取。這就意味著,當send()方法呼叫返回時,訊息已經被髮送到了底層的傳輸通道中。

2.為了保證已接收但未到傳送的資料不被丟棄,接收端提供快取空間應為UDP資料報文最大負載量(65507位元組)

       訊息從網路中到達後,其所包含的資料被read()方法或receive()方法返回前,資料儲存在一個先進先出的接收資料佇列中。對於已連線的TCP套接字來說,所有已接受但還未傳送的位元組都看作是一個連續的位元組序列。然而,對於UDP套接字來說,接收到的資料可能來自不同的傳送者,一個UDP套接字所接收的資料存放在一個訊息佇列中,每個訊息都關聯了其源地址資訊,每次receive()呼叫只返回一條訊息。如果receive()方法在一個快取區大小為n的DatagramPacket例項中呼叫,而接收佇列裡中的第一條訊息的長度大於n,則receive()方法只返回這條訊息的前n個位元組,超出部分會被自動放棄,而且對接收程式沒有任何訊息丟失的提示。

       出於這個原因,接受者應該提供一個有足夠大的快取空間的DatagramPacket例項,以完整地存放呼叫receive()方法時應用程式協議所允許的最大長度的訊息。一個DatagramPacket例項中所允許傳輸的最大資料量為65507個位元組,也即是UDP資料報文所能負載的最多資料。因此,可以用一個65600位元組左右的快取陣列來接受資料。

3.DatagramPacket包含一個內部訊息的長度值,該例項一接收訊息,此長度值就會變化,因此每次呼叫receive()方法前將內部訊息的長度值重置為快取區的實際長度。

      如果一個應用程式多次使用同一個DatagramPacket例項多次呼叫receive()方法,每次呼叫前顯式將訊息的內部長度重置為緩衝區的原始的長度。如:

      packet.setLength(LENGTH);

4.DatagramPacket類的getData()方法總是返回緩衝區原始大小,忽略了實際資料的內部偏移量和長度資訊。

        由於DatagramPacket的getData()方法總是返回緩衝陣列的原始大小,因此如果我們要獲取接收到的資料,就必須擷取getData()方法返回的陣列中只含接收到的資料的那一部分。JDK1.6中可以使用Arrays.copyOfRange()方法來實現。

byte[] destbuf = Arrays.copyOfRange(dp_receive.getData(),dp_receive.getOffset(),

dp_receive.getOffset() + dp_receive.getLength());