Java網絡編程

課前思考
  1. 什麼是TCP/ IP協議?
  2. TCP/IP有哪兩種傳輸協議,各有什麼特點?
  3. 什麼是URL
  4. URLIP地址有什麼樣的關係?
  5. 什麼叫套接字(Socket)?
  6. 套接字(Socket)和TCP/IP協議的關係?
  7. URL和套接字(Socket)的關係?
8.1 網絡編程基本概念,TCP/IP協議簡介
8.1.1 網絡基礎知識
網絡編程的目的就是指直接或間接地通過網絡協議與其他計算機進行通訊。網絡編程中有兩個主要的問題,一個是如何準確的定位網絡上一臺或多臺主機,另一個就是找到主機後如何可靠高效的進行數據傳輸。在TCP/IP協議中IP層主要負責網絡主機的定位,數據傳輸的路由,由IP地址可以唯一地確定Internet上的一臺主機。而TCP層則提供面向應用的可靠的或非可靠的數據傳輸機制,這是網絡編程的主要對象,一般不需要關心IP層是如何處理數據的。
  目前較爲流行的網絡編程模型是客戶機/服務器(C/S)結構。即通信雙方一方作爲服務器等待客戶提出請求並予以響應。客戶則在需要服務時向服務器提出申請。服務器一般作爲守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啓動一個服務進程來響應該客戶,同時自己繼續監聽服務端口,使後來的客戶也能及時得到服務。
8.1.3兩類傳輸協議:TCP;UDP
  儘管TCP/IP協議的名稱中只有TCP這個協議名,但是在TCP/IP的傳輸層同時存在TCPUDP兩個協議。
TCPTranfer Control Protocol的簡稱,是一種面向連接的保證可靠傳輸的協議。通過TCP協議傳輸,得到的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須建立連接,以便在TCP協議的基礎上進行通信,當一個socket(通常都是server socket)等待建立連接時,另一個socket可以要求進行連接,一旦這兩個socket連接起來,它們就可以進行雙向數據傳輸,雙方都可以進行發送或接收操作。
  UDPUser Datagram Protocol的簡稱,是一種無連接的協議,每個數據報都是一個獨立的信息,包括完整的源地址或目的地址,它在網絡上以任何可能的路徑傳往目的地,因此能否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。
  下面我們對這兩種協議做簡單比較:
  使用UDP時,每個數據報中都給出了完整的地址信息,因此無需要建立發送方和接收方的連接。對於TCP協議,由於它是一個面向連接的協議,在socket之間進行數據傳輸之前必然要建立連接,所以在TCP中多了一個連接建立的時間。
 
 使用UDP傳輸數據時是有大小限制的,每個被傳輸的數據報必須限定在64KB之內。而TCP沒有這方面的限制,一旦連接建立起來,雙方的socket就可以按統一的格式傳輸大量的數據。UDP是一個不可靠的協議,發送方所發送的數據報並不一定以相同的次序到達接收方。而TCP是一個可靠的協議,它確保接收方完全正確地獲取發送方所發送的全部數據。
  總之,TCP在網絡通信上有極強的生命力,例如遠程連接(Telnet)和文件傳輸(FTP)都需要不定長度的數據被可靠地傳輸。相比之下UDP操作簡單,而且僅需要較少的監護,因此通常用於局域網高可靠性的分散系統中client/server應用程序。
  讀者可能要問,既然有了保證可靠傳輸的TCP協議,爲什麼還要非可靠傳輸的UDP協議呢?主要的原因有兩個。一是可靠的傳輸是要付出代價的,對數據內容正確性的檢驗必然佔用計算機的處理時間和網絡的帶寬,因此TCP傳輸的效率不如UDP高。二是在許多應用中並不需要保證嚴格的傳輸可靠性,比如視頻會議系統,並不要求音頻視頻數據絕對的正確,只要保證連貫性就可以了,這種情況下顯然使用UDP會更合理一些。
8.2.1一致資源定位器URL
URL(Uniform Resource Locator)是一致資源定位器的簡稱,它表示Internet上某一資源的地址。通過URL我們可以訪問Internet上的各種網絡資源,比如最常見的WWWFTP站點。瀏覽器通過解析給定的URL可以在網絡上查找相應的文件或其他資源。
8.2.2 URL的組成
protocol://resourceName
  協議名(protocol)指明獲取資源所使用的傳輸協議,如httpftpgopherfile等,資源名(resourceName)則應該是資源的完整地址,包括主機名、端口號、文件名或文件內部的一個引用。例如:
  http://www.sun.com/ 協議名://主機名
  http://home.netscape.com/home/welcome.html 協議名://機器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 協議名://機器名+端口號+文件名+內部引用.
8.2.3 創建一個URL
爲了表示URL, java.net中實現了類URL。我們可以通過下面的構造方法來初始化一個URL對象:
  (1) public URL (String spec);
     通過一個表示URL地址的字符串可以構造一個URL對象。
     URL urlBase=new URL("http://www. 263.net/")
  (2) public URL(URL context, String spec);
     通過基URL和相對URL構造一個URL對象。
     URL net263=new URL ("http://www.263.net/");
     URL index263=new URL(net263, "index.html")
  (3) public URL(String protocol, String host, String file);
     new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
  (4) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");
  注意:類URL的構造方法都聲明拋棄非運行時例外(MalformedURLException),因此生成URL對象時,我們必須要對這一例外進行處理,通常是用try-catch語句進行捕獲。格式如下:
try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
    }
8.2.4 解析一個URL
一個URL對象生成後,其屬性是不能被改變的,但是我們可以通過類URL所提供的方法來獲取這些屬性:
   public String getProtocol() 獲取該URL的協議名。
   public String getHost() 獲取該URL的主機名。
   public int getPort() 獲取該URL的端口號,如果沒有設置端口,返回-1。
   public String getFile() 獲取該URL的文件名。
   public String getRef() 獲取該URL在文件中的相對位置。
   public String getQuery() 獲取該URL的查詢信息。
   public String getPath() 獲取該URL的路徑
   public String getAuthority() 獲取該URL的權限信息
   public String getUserInfo() 獲得使用者的信息
    public String getRef() 獲得該URL的錨
8.2.5 從URL讀取WWW網絡資源
當我們得到一個URL對象後,就可以通過它讀取指定的WWW資源。這時我們將使用URL的方法openStream(),其定義爲:
         InputStream openStream();
  
  方法openSteam()與指定的URL建立連接並返回InputStream類的對象以從這一連接中讀取數據。
  public class URLReader {
  public static void main(String[] args) throws Exception {
                      //聲明拋出所有例外
    URL tirc = new URL("http://www.tirc1.cs.tsinghua.edu.cn/");
                      //構建一URL對象
    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
    //使用openStream得到一輸入流並由此構造一個BufferedReader對象
    String inputLine;
    while ((inputLine = in.readLine()) != null)
                 //從輸入流不斷的讀數據,直到讀完爲止
       System.out.println(inputLine); //把讀入的數據打印到屏幕上
    in.close(); //關閉輸入流
  }
  }
8.2.6 通過URLConnetction連接WWW
通過URL的方法openStream(),我們只能從網絡上讀取數據,如果我們同時還想輸出數據,例如向服務器端的CGI程序發送一些數據,我們必須先與URL建立連接,然後才能對其進行讀寫,這時就要用到類URLConnection了。CGI是公共網關接口(Common Gateway Interface)的簡稱,它是用戶瀏覽器和服務器端的應用程序進行連接的接口,有關CGI程序設計,請讀者參考有關書籍。
  URLConnection也在包java.net中定義,它表示Java程序和URL在網絡上的通信連接。當與一個URL建立連接時,首先要在一個URL對象上通過方法openConnection()生成對應的URLConnection對象。例如下面的程序段首先生成一個指向地址http://edu.chinaren.com/index.shtml的對象,然後用openConnection()打開該URL對象上的一個連接,返回一個URLConnection對象。如果連接過程失敗,將產生IOException.
  Try{
    URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");
    URLConnectonn tc = netchinaren.openConnection();
  }catch(MalformedURLException e){ //創建URL()對象失敗
  
  }catch (IOException e){ //openConnection()失敗
  
  }
  類URLConnection提供了很多方法來設置或獲取連接參數,程序設計時最常使用的是getInputStream()getOurputStream(),其定義爲:
     InputSteram getInputSteram();
     OutputSteram getOutputStream();
  通過返回的輸入/輸出流我們可以與遠程對象進行通信。看下面的例子:
  URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");
  //創建一URL對象
  URLConnectin con=url.openConnection();
  //URL對象獲取URLConnection對象
  DataInputStream dis=new DataInputStream (con.getInputSteam());
  //URLConnection獲取輸入流,並構造DataInputStream對象
  PrintStream ps=new PrintSteam(con.getOutupSteam());
  //URLConnection獲取輸出流,並構造PrintStream對象
  String line=dis.readLine(); //從服務器讀入一行
  ps.println("client…"); //向服務器寫出字符串 "client…"
    其中backwards爲服務器端的CGI程序。實際上,類URL的方法openSteam()是通過URLConnection來實現的。它等價於
    openConnection().getInputStream();
  基於URL的網絡編程在底層其實還是基於下面要講的Socket接口的。WWWFTP等標準化的網絡服務都是基於TCP協議的,所以本質上講URL編程也是基於TCP的一種應用.
8.3.1 Socket通訊
網絡上的兩個程序通過一個雙向的通訊連接實現數據的交換,這個雙向鏈路的一端稱爲一個SocketSocket通常用來實現客戶方和服務方的連接。SocketTCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號唯一確定。
  在傳統的UNIX環境下可以操作TCP/IP協議的接口不止Socket一個,Socket所支持的協議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯繫的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程。
8.3.2 Socket通訊的一般過程
使用Socket進行Client/Server程序設計的一般連接過程是這樣的:ServerListen(監聽)某個端口是否有連接請求,Client端向Server端發出Connect(連接)請求,Server端向Client端發回Accept(接受)消息。一個連接就建立起來了。Server端和Client端都可以通過SendWrite等方法與對方通信。
對於一個功能齊全的Socket,都要包含以下基本結構,其工作過程包含以下四個基本的步驟:
  (1創建Socket
  (2打開連接到Socket的輸入/出流;
  (3按照一定的協議對Socket進行讀/寫操作;
  (4關閉Socket.
8.3.3 創建Socket
java在包java.net中提供了兩個類SocketServerSocket,分別用來表示雙向連接的客戶端和服務端。這是兩個封裝得非常好的類,使用很方便。其構造方法如下:
  Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
  Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
  ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
  其中addresshostport分別是雙向連接中另一方的IP地址、主機名和端口號,stream指明socket是流socket還是數據報socketlocalPort表示本地主機的端口號,localAddrbindAddr是本地機器的地址(ServerSocket的主機地址),implsocket的父類,既可以用來創建serverSocket又可以用來創建Socketcount則表示服務端所能支持的最大連接數。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);
  注意,在選擇端口時,必須小心。每一個端口提供一種特定的服務,只有給出正確的端口,才能獲得相應的服務。0~1023的端口號爲系統所保留,例如http服務的端口號爲80,telnet服務的端口號爲21,ftp服務的端口號爲23, 所以我們在選擇端口號時,最好選擇一個大於1023的數以防止發生衝突。
  在創建socket時如果發生錯誤,將產生IOException,在程序中必須對之作出處理。所以在創建SocketServerSocket是必須捕獲或拋出例外。
8.3.8 簡單的Client/Server程序設計
下面我們給出一個用Socket實現的客戶和服務器交互的典型的C/S結構的演示程序,讀者通過仔細閱讀該程序,會對前面所討論的各個概念有更深刻的認識。程序的意義請參考註釋。
1. 客戶端程序
  import java.io.*;
  import java.net.*;
  public class TalkClient {
    public static void main(String args[]) {
      try{
        Socket socket=new Socket("127.0.0.1",4700);
        
//向本機的4700端口發出客戶請求
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
        
//由系統標準輸入設備構造BufferedReader對象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        
//由Socket對象得到輸出流,並構造PrintWriter對象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        
//由Socket對象得到輸入流,並構造相應的BufferedReader對象
        String readline;
        readline=sin.readLine();
//從系統標準輸入讀入一字符串
        while(!readline.equals("bye")){
        
//若從標準輸入讀入的字符串爲 "bye"則停止循環
          os.println(readline);
          
//將從系統標準輸入讀入的字符串輸出到Server
          os.flush();
          
//刷新輸出流,使Server馬上收到該字符串
          System.out.println("Client:"+readline);
          //在系統標準輸出上打印讀入的字符串

          System.out.println("Server:"+is.readLine());
          
//從Server讀入一字符串,並打印到標準輸出上
          readline=sin.readLine(); //從系統標準輸入讀入一字符串
        } //繼續循環
        os.close(); //關閉Socket輸出流
        is.close(); //關閉Socket輸入流
        socket.close(); //關閉Socket
      }catch(Exception e) {
        System.out.println("Error"+e);
//出錯,則打印出錯信息
      }
  }
}
 2. 服務器端程序
  import java.io.*;
  import java.net.*;
  import java.applet.Applet;
  public class TalkServer{
    public static void main(String args[]) {
      try{
        ServerSocket server=null;
        try{
          server=new ServerSocket(4700);
        
//創建一個ServerSocket在端口4700監聽客戶請求
        }catch(Exception e) {
          System.out.println("can not listen to:"+e);
        
//出錯,打印出錯信息
        }
        Socket socket=null;
        try{
          socket=server.accept();
          
//使用accept()阻塞等待客戶請求,有客戶
          //請求到來則產生一個Socket對象,並繼續執行

        }catch(Exception e) {
          System.out.println("Error."+e);
          
//出錯,打印出錯信息
        }
        String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         
//由Socket對象得到輸入流,並構造相應的BufferedReader對象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         
//由Socket對象得到輸出流,並構造PrintWriter對象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         
//由系統標準輸入設備構造BufferedReader對象
        System.out.println("Client:"+is.readLine());
        
//在標準輸出上打印從客戶端讀入的字符串
        line=sin.readLine();
        
//從標準輸入讀入一字符串
        while(!line.equals("bye")){
        
//如果該字符串爲 "bye",則停止循環
          os.println(line);
          
//向客戶端輸出該字符串
          os.flush();
          
//刷新輸出流,使Client馬上收到該字符串
          System.out.println("Server:"+line);
          
//在系統標準輸出上打印讀入的字符串
          System.out.println("Client:"+is.readLine());
          
//從Client讀入一字符串,並打印到標準輸出上
          line=sin.readLine();
          
//從系統標準輸入讀入一字符串
        }  
//繼續循環
        os.close();
//關閉Socket輸出流
        is.close();
//關閉Socket輸入流
        socket.close();
//關閉Socket
        server.close();
//關閉ServerSocket
      }catch(Exception e){
        System.out.println("Error:"+e);
        
//出錯,打印出錯信息
      }
    }
  }
8.3.9 支持多客戶的client/server程序設計
前面提供的Client/Server程序只能實現Server和一個客戶的對話。在實際應用中,往往是在服務器上運行一個永久的程序,它可以接收來自其他多個客戶端的請求,提供相應的服務。爲了實現在服務器方給多個客戶提供服務的功能,需要對上面的程序進行改造,利用多線程實現多客戶機制。服務器總是在指定的端口上監聽是否有客戶請求,一旦監聽到客戶請求,服務器就會啓動一個專門的服務線程來響應該客戶的請求,而服務器本身在啓動完線程之後馬上又進入監聽狀態,等待下一個客戶的到來。
ServerSocket serverSocket=null;
    boolean listening=true;
    try{
      serverSocket=new ServerSocket(4700);
      //創建一個ServerSocket在端口4700監聽客戶請求
    }catch(IOException e) {  }
    while(listening){ //永遠循環監聽
      new ServerThread(serverSocket.accept(),clientnum).start();
      //監聽到客戶請求,根據得到的Socket對象和
       客戶計數創建服務線程,並啓動之
      clientnum++; //增加客戶計數
    }
    serverSocket.close(); //關閉ServerSocket
設計ServerThread
 public class ServerThread extends Thread{
   Socket socket=null; //保存與本線程相關的Socket對象
   int clientnum; //保存本進程的客戶計數
   public ServerThread(Socket socket,int num) { //構造函數
    this.socket=socket; //初始化socket變量
    clientnum=num+1; //初始化clientnum變量
   }
   public void run() { //線程主體
    try{//在這裏實現數據的接受和發送}
8.3.10 據報Datagram通訊
前面在介紹TCP/IP協議的時候,我們已經提到,在TCP/IP協議的傳輸層除了TCP協議之外還有一個UDP協議,相比而言UDP的應用不如TCP廣泛,幾個標準的應用層協議HTTPFTPSMTP…使用的都是TCP協議。但是,隨着計算機網絡的發展,UDP協議正越來越來顯示出其威力,尤其是在需要很強的實時交互性的場合,如網絡遊戲,視頻會議等,UDP更是顯示出極強的威力,下面我們就介紹一下Java環境下如何實現UDP網絡傳輸。
8.3.11 什麼是Datagram
所謂數據報(Datagram)就跟日常生活中的郵件系統一樣,是不能保證可靠的寄到的,而面向鏈接的TCP就好比電話,雙方能肯定對方接受到了信息。在本章前面,我們已經對UDPTCP進行了比較,在這裏再稍作小節:
  TCP,可靠,傳輸大小無限制,但是需要連接建立時間,差錯控制開銷大。
  UDP,不可靠,差錯控制開銷較小,傳輸大小限制在64K以下,不需要建立連接。
8.3.12 Datagram通訊的表示方法:DatagramSocketDatagramPacket
java.net中提供了兩個類DatagramSocketDatagramPacket用來支持數據報通信,DatagramSocket用於在程序之間建立傳送數據報的通信連接, DatagramPacket則用來表示一個數據報。先來看一下DatagramSocket的構造方法:
   DatagramSocket();
   DatagramSocketint prot;
   DatagramSocket(int port, InetAddress laddr)
    其中,port指明socket所使用的端口號,如果未指明端口號,則把socket連接到本地主機上一個可用的端口。laddr指明一個可用的本地地址。給出端口號時要保證不發生端口衝突,否則會生成SocketException類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。
  
用數據報方式編寫client/server程序時,無論在客戶方還是服務方,首先都要建立一個DatagramSocket對象,用來接收或發送數據報,然後使用DatagramPacket類對象作爲傳輸數據的載體。下面看一下DatagramPacket的構造方法
   DatagramPacketbyte buf[],int length);
   DatagramPacket(byte buf[], int length, InetAddress addr, int port);
   DatagramPacket(byte[] buf, int offset, int length)
   DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)

  其中,buf中存放數據報數據,length爲數據報中數據的長度,addrport旨明目的地址,offset指明瞭數據報的位移量。
  在接收數據前,應該採用上面的第一種方法生成一個DatagramPacket對象,給出接收數據的緩衝區及其長度。然後調用DatagramSocket 的方法receive()等待數據報的到來,receive()將一直等待,直到收到一個數據報爲止。
  DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);
  發送數據前,也要先生成一個新的DatagramPacket對象,這時要使用上面的第二種構造方法,在給出存放發送數據的緩衝區的同時,還要給出完整的目的地址,包括IP地址和端口號。發送數據是通過DatagramSocket的方法send()實現的,send()根據數據報的目的地址來尋徑,以傳遞數據報。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet)
   
在構造數據報時,要給出InetAddress類參數。類InetAddress在包java.net中定義,用來表示一個Internet地址,我們可以通過它提供的類方法getByName()從一個表示主機名的字符串獲取該主機的IP地址,然後再獲取相應的地址信息。
8.3.14 用數據報進行廣播通訊
DatagramSocket只允許數據報發送一個目的地址,java.net包中提供了一個類MulticastSocket,允許數據報以廣播方式發送到該端口的所有客戶。MulticastSocket用在客戶端,監聽服務器廣播來的數據。
1. 客戶方程序:MulticastClient.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastClient {
    public static void main(String args[]) throws IOException
    {
     MulticastSocket socket=new MulticastSocket(4446);
     
//創建4446端口的廣播套接字
     InetAddress address=InetAddress.getByName("230.0.0.1");
     
//得到230.0.0.1的地址信息
     socket.joinGroup(address);
     
//使用joinGroup()將廣播套接字綁定到地址上
     DatagramPacket packet;
     for(int i=0;i<5;i++) {
       byte[] buf=new byte[256];
       //創建緩衝區

       packet=new DatagramPacket(buf,buf.length);
       
//創建接收數據報
       socket.receive(packet); //接收
       String received=new String(packet.getData());
       
//由接收到的數據報得到字節數組,
       //並由此構造一個String對象
       System.out.println("Quote of theMoment:"+received);
       
//打印得到的字符串
     } //循環5次
     socket.leaveGroup(address);
     
//把廣播套接字從地址上解除綁定
     socket.close(); //關閉廣播套接字
   }
 }
 2. 服務器方程序:MulticastServer.java
  public class MulticastServer{
    public static void main(String args[]) throws java.io.IOException
    {
      new MulticastServerThread().start();
      
//啓動一個服務器線程
    }
  }
 3. 程序MulticastServerThread.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastServerThread extends QuoteServerThread
  
//從QuoteServerThread繼承得到新的服務器線程類MulticastServerThread
  {
    Private long FIVE_SECOND=5000;
//定義常量,5秒鐘
    public MulticastServerThread(String name) throws IOException
    {
      super("MulticastServerThread");
      
//調用父類,也就是QuoteServerThread的構造函數
    }
    public void run() //重寫父類的線程主體
    {
     while(moreQuotes) {
     
//根據標誌變量判斷是否繼續循環
      try{
        byte[] buf=new byte[256];
        
//創建緩衝區
        String dString=null;
        if(in==null) dString=new Date().toString();
        
//如果初始化的時候打開文件失敗了,
        //則使用日期作爲要傳送的字符串

        else dString=getNextQuote();
        
//否則調用成員函數從文件中讀出字符串
        buf=dString.getByte();
        
//把String轉換成字節數組,以便傳送send it
        InetAddress group=InetAddress.getByName("230.0.0.1");
        
//得到230.0.0.1的地址信息
        DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
        
//根據緩衝區,廣播地址,和端口號創建DatagramPacket對象
        socket.send(packet); //發送該Packet
        try{
          sleep((long)(Math.random()*FIVE_SECONDS));
          
//隨機等待一段時間,0~5秒之間
        }catch(InterruptedException e) { } //異常處理
      }catch(IOException e){ //異常處理
        e.printStackTrace( ); //打印錯誤棧
        moreQuotes=false; //置結束循環標誌
      }
    }
    socket.close( );
//關閉廣播套接口
   }
 }
 2. 服務器方程序:QuoteServerThread.java
package com.socket.source;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Date;
//服?器?程
public class QuoteServerThread extends Thread {
 protected DatagramSocket socket = null;
 // ??和本?象相??的DatagramSocket?象
 protected BufferedReader in = null;
 // 用來?文件的一個Reader
 protected boolean moreQuotes = true;
 // ?志?量,是否??操作
 public QuoteServerThread() throws IOException {
  // 無參數的?造函數
  this("QuoteServerThread");
  // 以QuoteServerThread?默???用?參數的?造函數
 }
 public QuoteServerThread(String name) throws IOException {
  super(name); // ?用父?的?造函數
  socket = new DatagramSocket(4445);
  // 在端口4445?建數據?套接字
  try {
   in = new BufferedReader(new FileReader("one-liners.txt"));
   // 打?一個文件,?造相?的BufferReader?象
  } catch (FileNotFoundException e) { // ?常?理
   System.err
     .println("Could not open quote file. Serving time instead.");
   // 打印出?信息
  }
 }
 public void run() // ?程主體
 {
  while (moreQuotes) {
   try {
    byte[] buf = new byte[256]; // ?建?衝區
    DatagramPacket packet = new DatagramPacket(buf, buf.length);
    // 由?衝區?造DatagramPacket?象
    socket.receive(packet); // 接收數據?
    String dString = null;
    if (in == null)
     dString = new Date().toString();
    // 如果初始化的?候打?文件失?了,
    // ?使用日期作?要?送的字符串
    else
     dString = getNextQuotes();
    // 否??用成?函數從文件中?出字符串
    buf = dString.getBytes();
    // 把String??成字?數?,以便?送
    InetAddress address = packet.getAddress();
    // 從Client端?來的Packet中得到Client地址
    int port = packet.getPort(); // 和端口號
    packet = new DatagramPacket(buf, buf.length, address, port);
    // 根據客?端信息?建DatagramPacket
    socket.send(packet); // ?送數據?
   } catch (IOException e) { // ?常?理
    e.printStackTrace(); // 打印???
    moreQuotes = false; // ?志?量置false,以?束循?
   }
  }
  socket.close(); // ??數據?套接字
 }
 protected String getNextQuotes() {
  // 成?函數,從文件中?數據
  String returnValue = null;
  try {
   if ((returnValue = in.readLine()) == null) {
    // 從文件中?一行,如果?到了文件尾
    in.close(); // ???入流
    moreQuotes = false;
    // ?志?量置false,以?束循?
    returnValue = "No more quotes. Goodbye.";
    // 置返回?
   } // 否?返回字符串即?從文件?出的字符串
  } catch (IOException e) { // ?常?理
   returnValue = "IOException occurred in server";
   // 置?常返回?
  }
  return returnValue; // 返回字符串
 }
}
【本講小結】
本講主要講解了Java環境下的網絡編程。因爲TCP/IP協議是Java網絡編程的基礎知識,本講開篇重點介紹了TCP/IP協議中的一些概念,TCP/IP協議本身是一個十分龐大的系統,用幾個小節是不可能講清楚的。所以我們只是聯繫實際,講解了一些最基本的概念,幫助學生理解後面的相關內容。重點有一下幾個概念:主機名,IP,端口,服務類型,TCPUDP
  後續的內容分爲兩大塊,一塊是以URL爲主線,講解如何通過URL類和URLConnection類訪問WWW網絡資源,由於使用URL十分方便直觀,儘管功能不是很強,還是值得推薦的一種網絡編程方法,尤其是對於初學者特別容易接受。本質上講,URL網絡編程在傳輸層使用的還是TCP協議。
  另一塊是以Socket接口和C/S網絡編程模型爲主線,依次講解了如何用Java實現基於TCPC/S結構,主要用到的類有SocketServerSocket。以及如何用Java實現基於UDPC/S結構,還討論了一種特殊的傳輸方式,廣播方式,這種方式是UDP所特有的,主要用到的類有DatagramSocket , DatagramPacket, MulticastSocket。這一塊在Java網絡編程中相對而言是最難的(儘管Java在網絡編程這方面已經做的夠"傻瓜"了,但是網絡編程在其他環境下的卻是一件極爲頭痛的事情,再"傻瓜"還是有一定的難度),也是功能最爲強大的一部分,讀者應該好好研究,領悟其中的思想。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章