程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java數據報編程

java數據報編程

編輯:關於JAVA

大家迄今看到的例子使用的都是“傳輸控制協議”(TCP),亦稱作“基於數據流的套接字”。根據該協議的設計宗旨,它具有高度的可靠性,而且能保證數據順利抵達目的地。換言之,它允許重傳那些由於各種原因半路“走失”的數據。而且收到字節的順序與它們發出來時是一樣的。當然,這種控制與可靠性需要我們付出一些代價:TCP具有非常高的開銷。
還有另一種協議,名為“用戶數據報協議”(UDP),它並不刻意追求數據包會完全發送出去,也不能擔保它們抵達的順序與它們發出時一樣。我們認為這是一種“不可靠協議”(TCP當然是“可靠協議”)。聽起來似乎很糟,但由於它的速度快得多,所以經常還是有用武之地的。對某些應用來說,比如聲音信號的傳輸,如果少量數據包在半路上丟失了,那麼用不著太在意,因為傳輸的速度顯得更重要一些。大多數互聯網游戲,如Diablo,采用的也是UDP協議通信,因為網絡通信的快慢是游戲是否流暢的決定性因素。也可以想想一台報時服務器,如果某條消息丟失了,那麼也真的不必過份緊張。另外,有些應用也許能向服務器傳回一條UDP消息,以便以後能夠恢復。如果在適當的時間裡沒有響應,消息就會丟失。
Java對數據報的支持與它對TCP套接字的支持大致相同,但也存在一個明顯的區別。對數據報來說,我們在客戶和服務器程序都可以放置一個DatagramSocket(數據報套接字),但與ServerSocket不同,前者不會干巴巴地等待建立一個連接的請求。這是由於不再存在“連接”,取而代之的是一個數據報陳列出來。另一項本質的區別的是對TCP套接字來說,一旦我們建好了連接,便不再需要關心誰向誰“說話”——只需通過會話流來回傳送數據即可。但對數據報來說,它的數據包必須知道自己來自何處,以及打算去哪裡。這意味著我們必須知道每個數據報包的這些信息,否則信息就不能正常地傳遞。
DatagramSocket用於收發數據包,而DatagramPacket包含了具體的信息。准備接收一個數據報時,只需提供一個緩沖區,以便安置接收到的數據。數據包抵達時,通過DatagramSocket,作為信息起源地的因特網地址以及端口編號會自動得到初化。所以一個用於接收數據報的DatagramPacket構建器是:
DatagramPacket(buf, buf.length)
其中,buf是一個字節數組。既然buf是個數組,大家可能會奇怪為什麼構建器自己不能調查出數組的長度呢?實際上我也有同感,唯一能猜到的原因就是C風格的編程使然,那裡的數組不能自己告訴我們它有多大。
可以重復使用數據報的接收代碼,不必每次都建一個新的。每次用它的時候(再生),緩沖區內的數據都會被覆蓋。
緩沖區的最大容量僅受限於允許的數據報包大小,這個限制位於比64KB稍小的地方。但在許多應用程序中,我們都寧願它變得還要小一些,特別是在發送數據的時候。具體選擇的數據包大小取決於應用程序的特定要求。
發出一個數據報時,DatagramPacket不僅需要包含正式的數據,也要包含因特網地址以及端口號,以決定它的目的地。所以用於輸出DatagramPacket的構建器是:
DatagramPacket(buf, length, inetAddress, port)
這一次,buf(一個字節數組)已經包含了我們想發出的數據。length可以是buf的長度,但也可以更短一些,意味著我們只想發出那麼多的字節。另兩個參數分別代表數據包要到達的因特網地址以及目標機器的一個目標端口(注釋②)。

②:我們認為TCP和UDP端口是相互獨立的。也就是說,可以在端口8080同時運行一個TCP和UDP服務程序,兩者之間不會產生沖突。

大家也許認為兩個構建器創建了兩個不同的對象:一個用於接收數據報,另一個用於發送它們。如果是好的面向對象的設計方案,會建議把它們創建成兩個不同的類,而不是具有不同的行為的一個類(具體行為取決於我們如何構建對象)。這也許會成為一個嚴重的問題,但幸運的是,DatagramPacket的使用相當簡單,我們不需要在這個問題上糾纏不清。這一點在下例裡將有很明確的說明。該例類似於前面針對TCP套接字的MultiJabberServer和MultiJabberClient例子。多個客戶都會將數據報發給服務器,後者會將其反饋回最初發出消息的同樣的客戶。
為簡化從一個String裡創建DatagramPacket的工作(或者從DatagramPacket裡創建String),這個例子首先用到了一個工具類,名為Dgram:

 

//: Dgram.java
// A utility class to convert back and forth
// Between Strings and DataGramPackets.
import java.net.*;

public class Dgram {
  public static DatagramPacket toDatagram(
    String s, InetAddress destIA, int destPort) {
    // Deprecated in Java 1.1, but it works:
    byte[] buf = new byte[s.length() + 1];
    s.getBytes(0, s.length(), buf, 0);
    // The correct Java 1.1 approach, but it's
    // Broken (it truncates the String):
    // byte[] buf = s.getBytes();
    return new DatagramPacket(buf, buf.length, 
      destIA, destPort);
  }
  public static String toString(DatagramPacket p){
    // The Java 1.0 approach:
    // return new String(p.getData(), 
    //  0, 0, p.getLength());
    // The Java 1.1 approach:
    return 
      new String(p.getData(), 0, p.getLength());
  }
} ///:~


Dgram的第一個方法采用一個String、一個InetAddress以及一個端口號作為自己的參數,將String的內容復制到一個字節緩沖區,再將緩沖區傳遞進入DatagramPacket構建器,從而構建一個DatagramPacket。注意緩沖區分配時的"+1"——這對防止截尾現象是非常重要的。String的getByte()方法屬於一種特殊操作,能將一個字串包含的char復制進入一個字節緩沖。該方法現在已被“反對”使用;Java 1.1有一個“更好”的辦法來做這個工作,但在這裡卻被當作注釋屏蔽掉了,因為它會截掉String的部分內容。所以盡管我們在Java 1.1下編譯該程序時會得到一條“反對”消息,但它的行為仍然是正確無誤的(這個錯誤應該在你讀到這裡的時候修正了)。
Dgram.toString()方法同時展示了Java 1.0的方法和Java 1.1的方法(兩者是不同的,因為有一種新類型的String構建器)。
下面是用於數據報演示的服務器代碼:

 

//: ChatterServer.java
// A server that echoes datagrams
import java.net.*;
import java.io.*;
import java.util.*;

public class ChatterServer {
  static final int INPORT = 1711;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  private DatagramSocket socket;

  public ChatterServer() {
    try {
      socket = new DatagramSocket(INPORT);
      System.out.println("Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = Dgram.toString(dp) +
          ", from address: " + dp.getAddress() +
          ", port: " + dp.getPort();
        System.out.println(rcvd);
        String echoString = 
          "Echoed: " + rcvd;
        // Extract the address and port from the
        // received datagram to find out where to
        // send it back:
        DatagramPacket echo = 
          Dgram.toDatagram(echoString,
            dp.getAddress(), dp.getPort());
        socket.send(echo);
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new ChatterServer();
  }
} ///:~


ChatterServer創建了一個用來接收消息的DatagramSocket(數據報套接字),而不是在我們每次准備接收一條新消息時都新建一個。這個單一的DatagramSocket可以重復使用。它有一個端口號,因為這屬於服務器,客戶必須確切知道自己把數據報發到哪個地址。盡管有一個端口號,但沒有為它分配因特網地址,因為它就駐留在“這”台機器內,所以知道自己的因特網地址是什麼(目前是默認的localhost)。在無限while循環中,套接字被告知接收數據(receive())。然後暫時掛起,直到一個數據報出現,再把它反饋回我們希望的接收人——DatagramPacket dp——裡面。數據包(Packet)會被轉換成一個字串,同時插入的還有數據包的起源因特網地址及套接字。這些信息會顯示出來,然後添加一個額外的字串,指出自己已從服務器反饋回來了。
大家可能會覺得有點兒迷惑。正如大家會看到的那樣,許多不同的因特網地址和端口號都可能是消息的起源地——換言之,客戶程序可能駐留在任何一台機器裡(就這一次演示來說,它們都駐留在localhost裡,但每個客戶使用的端口編號是不同的)。為了將一條消息送回它真正的始發客戶,需要知道那個客戶的因特網地址以及端口號。幸運的是,所有這些資料均已非常周到地封裝到發出消息的DatagramPacket內部,所以我們要做的全部事情就是用getAddress()和getPort()把它們取出來。利用這些資料,可以構建DatagramPacket echo——它通過與接收用的相同的套接字發送回來。除此以外,一旦套接字發出數據報,就會添加“這”台機器的因特網地址及端口信息,所以當客戶接收消息時,它可以利用getAddress()和getPort()了解數據報來自何處。事實上,getAddress()和getPort()唯一不能告訴我們數據報來自何處的前提是:我們創建一個待發送的數據報,並在正式發出之前調用了getAddress()和getPort()。到數據報正式發送的時候,這台機器的地址以及端口才會寫入數據報。所以我們得到了運用數據報時一項重要的原則:不必跟蹤一條消息的來源地!因為它肯定保存在數據報裡。事實上,對程序來說,最可靠的做法是我們不要試圖跟蹤,而是無論如何都從目標數據報裡提取出地址以及端口信息(就象這裡做的那樣)。
為測試服務器的運轉是否正常,下面這程序將創建大量客戶(線程),它們都會將數據報包發給服務器,並等候服務器把它們原樣反饋回來。

 

//: ChatterServer.java
// A server that echoes datagrams
import java.net.*;
import java.io.*;
import java.util.*;

public class ChatterServer {
  static final int INPORT = 1711;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  private DatagramSocket socket;

  public ChatterServer() {
    try {
      socket = new DatagramSocket(INPORT);
      System.out.println("Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = Dgram.toString(dp) +
          ", from address: " + dp.getAddress() +
          ", port: " + dp.getPort();
        System.out.println(rcvd);
        String echoString = 
          "Echoed: " + rcvd;
        // Extract the address and port from the
        // received datagram to find out where to
        // send it back:
        DatagramPacket echo = 
          Dgram.toDatagram(echoString,
            dp.getAddress(), dp.getPort());
        socket.send(echo);
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new ChatterServer();
  }
} ///:~


ChatterClient被創建成一個線程(Thread),所以可以用多個客戶來“騷擾”服務器。從中可以看到,用於接收的DatagramPacket和用於ChatterServer的那個是相似的。在構建器中,創建DatagramPacket時沒有附帶任何參數(自變量),因為它不需要明確指出自己位於哪個特定編號的端口裡。用於這個套接字的因特網地址將成為“這台機器”(比如localhost),而且會自動分配端口編號,這從輸出結果即可看出。同用於服務器的那個一樣,這個DatagramPacket將同時用於發送和接收。
hostAddress是我們想與之通信的那台機器的因特網地址。在程序中,如果需要創建一個准備傳出去的DatagramPacket,那麼必須知道一個准確的因特網地址和端口號。可以肯定的是,主機必須位於一個已知的地址和端口號上,使客戶能啟動與主機的“會話”。
每個線程都有自己獨一無二的標識號(盡管自動分配給線程的端口號是也會提供一個唯一的標識符)。在run()中,我們創建了一個String消息,其中包含了線程的標識編號以及該線程准備發送的消息編號。我們用這個字串創建一個數據報,發到主機上的指定地址;端口編號則直接從ChatterServer內的一個常數取得。一旦消息發出,receive()就會暫時被“堵塞”起來,直到服務器回復了這條消息。與消息附在一起的所有信息使我們知道回到這個特定線程的東西正是從始發消息中投遞出去的。在這個例子中,盡管是一種“不可靠”協議,但仍然能夠檢查數據報是否到去過了它們該去的地方(這在localhost和LAN環境中是成立的,但在非本地連接中卻可能出現一些錯誤)。
運行該程序時,大家會發現每個線程都會結束。這意味著發送到服務器的每個數據報包都會回轉,並反饋回正確的接收者。如果不是這樣,一個或更多的線程就會掛起並進入“堵塞”狀態,直到它們的輸入被顯露出來。
大家或許認為將文件從一台機器傳到另一台的唯一正確方式是通過TCP套接字,因為它們是“可靠”的。然而,由於數據報的速度非常快,所以它才是一種更好的選擇。我們只需將文件分割成多個數據報,並為每個包編號。接收機器會取得這些數據包,並重新“組裝”它們;一個“標題包”會告訴機器應該接收多少個包,以及組裝所需的另一些重要信息。如果一個包在半路“走丟”了,接收機器會返回一個數據報,告訴發送者重傳。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved