Java網絡編程 講解

java網絡編程(1) php


網絡編程的目的就是指直接或間接地經過網絡協議與其餘計算機進行通信。網絡編程中有兩個主要的問題,一個是如何準確的定位網絡上一臺或多臺主機,另外一個就是找到主機後如何可靠高效的進行數據傳輸。在TCP/IP協議中IP層主要負責網絡主機的定位,數據傳輸的路由,由IP地址能夠惟一地肯定Internet上的一臺主機。而TCP層則提供面向應用的可靠的或非可靠的數據傳輸機制,這是網絡編程的主要對象,通常不須要關心IP層是如何處理數據的。
 
  目前較爲流行的網絡編程模型是客戶機/服務器(C/S)結構。即通訊雙方一方做爲服務器等待客戶提出請求並予以響應。客戶則在須要服務時向服務器提出申請。服務器通常做爲守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啓動一個服務進程來響應該客戶,同時本身繼續監聽服務端口,使後來的客戶也能及時獲得服務。
1.網絡的基本概念
IP地址:標識計算機等網絡設備的網絡地址,由四個8位的二進制數組成,中間以小數點分隔。
    如:166.111.136.3 , 166.111.52.80

  主機名(hostname):網絡地址的助記名,按照域名進行分級管理。
    如:www.baidu.com
      www.fanso.com
  
  端口號(port number):網絡通訊時同一機器上的不一樣進程的標識。
    如:80,21,23,25,其中1~1024爲系統保留的端口號
  
  服務類型(service):網絡的各類服務。
    http, telnet, ftp, smtp
 

  咱們能夠用如下的一幅圖來描述這裏咱們所提到的幾個概念: html

 在Internet上IP地址和主機名是一一對應的,經過域名解析能夠由主機名獲得機器的IP,因爲機器名更接近天然語言,容易記憶,因此使用比IP地址普遍,可是對機器而言只有IP地址纔是有效的標識符。

  一般一臺主機上老是有不少個進程須要網絡資源進行網絡通信。網絡通信的對象準確的講不是主機,而應該是主機中運行的進程。這時候光有主機名或IP地址來標識這麼多個進程顯然是不夠的。端口號就是爲了在一臺主機上提供更多的網絡資源而採起得一種手段,也是TCP層提供的一種機制。只有經過主機名或IP地址和端口號的組合才能惟一的肯定網絡通信中的對象:進程。

服務類型是在TCP層上面的應用層的概念。基於TCP/IP協議能夠構建出各類複雜的應用,服務類型是那些已經被標準化了的應用,通常都是網絡服務器(軟件)。讀者能夠編寫本身的基於網絡的服務器,但都不能被稱做標準的服務類型。


2.兩類傳輸協議 TCP UDP
TCP是Tranfer Control Protocol的簡稱,是一種面向鏈接的保證可靠傳輸的協議。經過TCP協議傳輸,獲得的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須創建鏈接,以便在TCP協議的基礎上進行通訊,當一個socket(一般都是server socket)等待創建鏈接時,另外一個socket能夠要求進行鏈接,一旦這兩個socket鏈接起來,它們就能夠進行雙向數據傳輸,雙方均可以進行發送或接收操做。

  UDP是User Datagram Protocol的簡稱,是一種無鏈接的協議,每一個數據報都是一個獨立的信息,包括完整的源地址或目的地址,它在網絡上以任何可能的路徑傳往目的地,所以可否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。 

  下面咱們對這兩種協議作簡單比較:
 
  使用UDP時,每一個數據報中都給出了完整的地址信息,所以無須要創建發送方和接收方的鏈接。對於TCP協議,因爲它是一個面向鏈接的協議,在socket之間進行數據傳輸以前必然要創建鏈接,因此在TCP中多了一個鏈接創建的時間。

  使用UDP傳輸數據時是有大小限制的,每一個被傳輸的數據報必須限定在64KB以內。而TCP沒有這方面的限制,一旦鏈接創建起來,雙方的socket就能夠按統一的格式傳輸大量的數據。UDP是一個不可靠的協議,發送方所發送的數據報並不必定以相同的次序到達接收方。而TCP是一個可靠的協議,它確保接收方徹底正確地獲取發送方所發送的所有數據。

  總之,TCP在網絡通訊上有極強的生命力,例如遠程鏈接(Telnet)和文件傳輸(FTP)都須要不定長度的數據被可靠地傳輸。相比之下UDP操做簡單,並且僅須要較少的監護,所以一般用於局域網高可靠性的分散系統中client/server應用程序。

既然有了保證可靠傳輸的TCP協議,爲何還要非可靠傳輸的UDP協議呢?主要的緣由有兩個。一是可靠的傳輸是要付出代價的,對數據內容正確性的檢驗必然佔用計算機的處理時間和網絡的帶寬,所以TCP傳輸的效率不如UDP高。二是在許多應用中並不須要保證嚴格的傳輸可靠性,好比視頻會議系統,並不要求音頻視頻數據絕對的正確,只要保證連貫性就能夠了,這種狀況下顯然使用UDP會更合理一些。


3.基於URL高層次的Java網絡編程
URL(Uniform Resource Locator)是一致資源定位器的簡稱,它表示Internet上某一資源的地址。經過URL咱們能夠訪問Internet上的各類網絡資源,好比最多見的WWW,FTP站點。瀏覽器經過解析給定的URL能夠在網絡上查找相應的文件或其餘資源。

  URL是最爲直觀的一種網絡定位方法。使用URL符合人們的語言習慣,容易記憶,因此應用十分普遍。並且在目前使用最爲普遍的TCP/IP中對於URL中主機名的解析也是協議的一個標準,即所謂的域名解析服務。使用URL進行網絡編程,不須要對協議自己有太多的瞭解,功能也比較弱,相對而言是比較簡單的。



4.URL的組成
protocol://resourceName
  協議名(protocol)指明獲取資源所使用的傳輸協議,如http、ftp、gopher、file等,資源名(resourceName)則應該是資源的完整地址,包括主機名、端口號、文件名或文件內部的一個引用。例如:
  http://www.sun.com/ 協議名://主機名
  http://home.netscape.com/home/welcome.html 協議名://機器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 協議名://機器名+端口號+文件名+內部引用



5.建立一個URL
java

爲了表示URL, java.net中實現了類URL。咱們能夠經過下面的構造方法來初始化一個URL對象:
  (1) public URL (String spec);
     經過一個表示URL地址的字符串能夠構造一個URL對象。
     URL urlBase=new URL("http://www.163.com/") 

  (2) public URL(URL context, String spec);
     經過基URL和相對URL構造一個URL對象。
     URL com163=new URL ("http://www.163.com/");
     URL com163=new URL(com163, "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"); android

  注意:類URL的構造方法都聲明拋棄非運行時例外(MalformedURLException),所以生成URL對象時,咱們必需要對這一例外進行處理,一般是用try-catch語句進行捕獲。格式以下: 程序員

  try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
  …
  //exception handler code here

      …
  }

6.解析一個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的錨



下面的例子中,咱們生成一個URL對象,並獲取它的各個屬性。
編程

  1. import java.net.*;
  2.   import java.io.*;

  3.   public class ParseURL{
  4.   public static void main (String [] args) throws Exception{

  5.   URL Aurl=new URL("http://java.sun.com:80/docs/books/");
  6.   URL tuto=new URL(Aurl,"tutorial.intro.html#DOWNLOADING"); 
  7.   System.out.println("protocol="+ tuto.getProtocol());
  8.   System.out.println("host ="+ tuto.getHost());
  9.   System.out.println("filename="+ tuto.getFile());
  10.   System.out.println("port="+ tuto.getPort());
  11.   System.out.println("ref="+tuto.getRef());
  12.   System.out.println("query="+tuto.getQuery());
  13.   System.out.println("path="+tuto.getPath());
  14.   System.out.println("UserInfo="+tuto.getUserInfo());
  15.   System.out.println("Authority="+tuto.getAuthority());
  16.   }
  17.   }
複製代碼


執行結果爲:
   protocol=http host =java.sun.com filename=/docs/books/tutorial.intro.html 
   port=80 
   ref=DOWNLOADING 
   query=null 
   path=/docs/books/tutorial.intro.html 
   UserInfo=null 
   Authority=java.sun.com:80


============================================================ 小程序

java網絡編程(2) 數組


7.URL讀取WWW網絡資源 瀏覽器

當咱們獲得一個URL對象後,就能夠經過它讀取指定的WWW資源。這時咱們將使用URL的方法openStream(),其定義爲:InputStream openStream(); 服務器

方法openSteam()與指定的URL創建鏈接並返回InputStream類的對象以從這一鏈接中讀取數據。

  1. public class URLReader {
  2.   public static void main(String[] args) throws Exception { 
  3.                       //聲明拋出全部例外
  4.     URL tirc = new URL("http://www.eoeandroid.com/"); 
  5.                       //構建一URL對象
  6.     BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
  7.     //使用openStream獲得一輸入流並由此構造一個BufferedReader對象
  8.     String inputLine;
  9.     while ((inputLine = in.readLine()) != null) 
  10.                  //從輸入流不斷的讀數據,直到讀完爲止
  11.        System.out.println(inputLine); //把讀入的數據打印到屏幕上
  12.     in.close(); //關閉輸入流
  13.   }
  14.   }
複製代碼


8.經過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.

  1. Try{
  2.     URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");
  3.     URLConnectonn tc = netchinaren.openConnection();
  4.   }catch(MalformedURLException e){ //建立URL()對象失敗
  5.   …
  6.   }catch (IOException e){ //openConnection()失敗
  7.   …
  8.   }
複製代碼


URLConnection提供了不少方法來設置或獲取鏈接參數,程序設計時最常使用的是getInputStream()getOurputStream(),其定義爲:
     InputSteram getInputSteram();
     OutputSteram getOutputStream();

  經過返回的輸入/輸出流咱們能夠與遠程對象進行通訊。看下面的例子:

  1. URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards"); 
  2.   //建立一URL對象
  3.   URLConnectin con=url.openConnection(); 
  4.   //由URL對象獲取URLConnection對象
  5.   DataInputStream dis=new DataInputStream (con.getInputSteam()); 
  6.   //由URLConnection獲取輸入流,並構造DataInputStream對象
  7.   PrintStream ps=new PrintSteam(con.getOutupSteam());
  8.   //由URLConnection獲取輸出流,並構造PrintStream對象
  9.   String line=dis.readLine(); //從服務器讀入一行
  10.   ps.println("client…"); //向服務器寫出字符串 "client…"
複製代碼

其中backwards爲服務器端的CGI程序。實際上,類URL的方法openSteam()是經過URLConnection來實現的。它等價於
    openConnection().getInputStream();


基於URL的網絡編程在底層其實仍是基於下面要講的Socket接口的。WWWFTP等標準化的網絡服務都是基於TCP協議的,因此本質上講URL編程也是基於TCP的一種應用。

============================================================

java網絡編程(3)

9.基於Socket(套接字)的低層次Java網絡編程

Socket通信

網絡上的兩個程序經過一個雙向的通信鏈接實現數據的交換,這個雙向鏈路的一端稱爲一個Socket。Socket一般用來實現客戶方和服務方的鏈接。Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號惟一肯定。
  在傳統的UNIX環境下能夠操做TCP/IP協議的接口不止Socket一個,Socket所支持的協議種類也不光TCP/IP一種,所以二者之間是沒有必然聯繫的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程。
  說Socket編程是低層次網絡編程並不等於它功能不強大,偏偏相反,正由於層次低,Socket編程比基於URL的網絡編程提供了更強大的功能和更靈活的控制,可是卻要更復雜一些。因爲Java自己的特殊性,Socket編程在Java中可能已是層次最低的網絡編程接口,在Java中要直接操做協議中更低的層次,須要使用Java的本地方法調用(JNI),在這裏就不予討論了。

Socket通信的通常過程

使用Socket進行Client/Server程序設計的通常鏈接過程是這樣的:Server端Listen(監聽)某個端口是否有鏈接請求,Client端向Server端發出Connect(鏈接)請求,Server端向Client端發回Accept(接受)消息。一個鏈接就創建起來了。Server端和Client端均可以經過Send,Write等方法與對方通訊。


對於一個功能齊全的Socket,都要包含如下基本結構,其工做過程包含如下四個基本的步驟:
  (1) 建立Socket;
  (2) 打開鏈接到Socket的輸入/出流;
  (3) 按照必定的協議對Socket進行讀/寫操做;
  (4) 關閉Socket.



  第三步是程序員用來調用Socket和實現程序功能的關鍵步驟,其餘三步在各類程序中基本相同。

以上4個步驟是針對TCP傳輸而言的,使用UDP進行傳輸時略有不一樣



建立Socket

java在包java.net中提供了兩個類Socket和ServerSocket,分別用來表示雙向鏈接的客戶端和服務端。這是兩個封裝得很是好的類,使用很方便。其構造方法以下:
  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)

  其中address、host和port分別是雙向鏈接中另外一方的IP地址、主機名和端口號,stream指明socket是流socket仍是數據報socket,localPort表示本地主機的端口號,localAddr和bindAddr是本地機器的地址(ServerSocket的主機地址),impl是socket的父類,既能夠用來建立serverSocket又能夠用來建立Socket。count則表示服務端所能支持的最大鏈接數。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);

  注意,在選擇端口時,必須當心。每個端口提供一種特定的服務,只有給出正確的端口,才能得到相應的服務。0~1023的端口號爲系統所保留,例如http服務的端口號爲80,telnet服務的端口號爲21,ftp服務的端口號爲23, 因此咱們在選擇端口號時,最好選擇一個大於1023的數以防止發生衝突。

  在建立socket時若是發生錯誤,將產生IOException,在程序中必須對之做出處理。因此在建立Socket或ServerSocket是必須捕獲或拋出例外。


客戶端的Socket
下面是一個典型的建立客戶端Socket的過程。

  1. try{
  2.      Socket socket=new Socket("127.0.0.1",4700); 
  3.      //127.0.0.1是TCP/IP協議中默認的本機地址
  4.    }catch(IOException e){
  5.      System.out.println("Error:"+e);
  6.    }
複製代碼

這是最簡單的在客戶端建立一個Socket的一個小程序段,也是使用Socket進行網絡通信的第一步,程序至關簡單,在這裏不做過多解釋了。


服務器端的ServerSocket

下面是一個典型的建立Server端ServerSocket的過程。

  1. ServerSocket server=null;
  2.   try {
  3.      server=new ServerSocket(4700); 
  4.      //建立一個ServerSocket在端口4700監聽客戶請求
  5.   }catch(IOException e){
  6.      System.out.println("can not listen to :"+e);
  7.   }
  8.   Socket socket=null;
  9.   try {
  10.     socket=server.accept(); 
  11.     //accept()是一個阻塞的方法,一旦有客戶請求,它就會返回一個Socket對象用於同客戶進行交互
  12.   }catch(IOException e){
  13.     System.out.println("Error:"+e);
  14.   }
複製代碼


以上的程序是Server的典型工做模式,只不過在這裏Server只能接收一個請求,接受完後Server就退出了。實際的應用中老是讓它不停的循環接收,一旦有客戶請求,Server老是會建立一個服務線程來服務新來的客戶,而本身繼續監聽。程序中accept()是一個阻塞函數,所謂阻塞性方法就是說該方法被調用後,將等待客戶的請求,直到有一個客戶啓動並請求鏈接到相同的端口,而後accept()返回一個對應於客戶的socket。這時,客戶方和服務方都創建了用於通訊的socket,接下來就是由各個socket分別打開各自的輸入/輸出流。


打開輸入/出流

Socket提供了方法getInputStream ()和getOutStream()來獲得對應的輸入/輸出流以進行讀/寫操做,這兩個方法分別返回InputStream和OutputSteam類對象。爲了便於讀/寫數據,咱們能夠在返回的輸入/輸出流對象上創建過濾流,如DataInputStream、DataOutputStream或PrintStream類對象,對於文本方式流對象,能夠採用InputStreamReader和OutputStreamWriter、PrintWirter等處理。

  例如:
  PrintStream os=new PrintStream(new BufferedOutputStreem(socket.getOutputStream()));
  DataInputStream is=new DataInputStream(socket.getInputStream());
  PrintWriter out=new PrintWriter(socket.getOutStream(),true);

  BufferedReader in=new ButfferedReader(new InputSteramReader(Socket.getInputStream()));

輸入輸出流是網絡編程的實質性部分,具體如何構造所須要的過濾流,要根據須要而定,可否運用自如主要看讀者對Java中輸入輸出部分掌握如何。


關閉Socket

每個Socket存在時,都將佔用必定的資源,在Socket對象使用完畢時,要其關閉。關閉Socket能夠調用Socket的Close()方法。在關閉Socket以前,應將與Socket相關的全部的輸入/輸出流所有關閉,以釋放全部的資源。並且要注意關閉的順序,與Socket相關的全部的輸入/輸出該首先關閉,而後再關閉Socket。
  os.close();
  is.close();
  socket.close();

儘管Java有自動回收機制,網絡資源最終是會被釋放的。可是爲了有效的利用資源,建議按照合理的順序主動釋放資源。


10.簡單的Client/Server程序設計
客戶端程序

  1.  import java.io.*;
  2.   import java.net.*;
  3.   public class TalkClient {
  4.     public static void main(String args[]) {
  5.       try{
  6.         Socket socket=new Socket("127.0.0.1",4700); 
  7.         //向本機的4700端口發出客戶請求
  8.         BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
  9.         //由系統標準輸入設備構造BufferedReader對象
  10.         PrintWriter os=new PrintWriter(socket.getOutputStream());
  11.         //由Socket對象獲得輸出流,並構造PrintWriter對象
  12.         BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  13.         //由Socket對象獲得輸入流,並構造相應的BufferedReader對象
  14.         String readline;
  15.         readline=sin.readLine(); //從系統標準輸入讀入一字符串
  16.         while(!readline.equals("bye")){ 
  17.         //若從標準輸入讀入的字符串爲 "bye"則中止循環
  18.           os.println(readline); 
  19.           //將從系統標準輸入讀入的字符串輸出到Server
  20.           os.flush(); 
  21.           //刷新輸出流,使Server立刻收到該字符串
  22.           System.out.println("Client:"+readline); 
  23.           //在系統標準輸出上打印讀入的字符串
  24.           System.out.println("Server:"+is.readLine()); 
  25.           //從Server讀入一字符串,並打印到標準輸出上
  26.           readline=sin.readLine(); //從系統標準輸入讀入一字符串
  27.         } //繼續循環
  28.         os.close(); //關閉Socket輸出流
  29.         is.close(); //關閉Socket輸入流
  30.         socket.close(); //關閉Socket
  31.       }catch(Exception e) {
  32.         System.out.println("Error"+e); //出錯,則打印出錯信息
  33.       }
  34.   }
  35. }
複製代碼


服務器端程序

  1. import java.io.*;
  2.   import java.net.*;
  3.   import java.applet.Applet;
  4.   public class TalkServer{
  5.     public static void main(String args[]) {
  6.       try{
  7.         ServerSocket server=null;
  8.         try{ 
  9.           server=new ServerSocket(4700); 
  10.         //建立一個ServerSocket在端口4700監聽客戶請求
  11.         }catch(Exception e) {
  12.           System.out.println("can not listen to:"+e); 
  13.         //出錯,打印出錯信息
  14.         }

  15.         Socket socket=null;
  16.         try{
  17.           socket=server.accept(); 
  18.           //使用accept()阻塞等待客戶請求,有客戶
  19.           //請求到來則產生一個Socket對象,並繼續執行
  20.         }catch(Exception e) {
  21.           System.out.println("Error."+e); 
  22.           //出錯,打印出錯信息
  23.         }
  24.         String line;
  25.         BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  26.          //由Socket對象獲得輸入流,並構造相應的BufferedReader對象
  27.         PrintWriter os=newPrintWriter(socket.getOutputStream());
  28.          //由Socket對象獲得輸出流,並構造PrintWriter對象
  29.         BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
  30.          //由系統標準輸入設備構造BufferedReader對象

  31.         System.out.println("Client:"+is.readLine()); 
  32.         //在標準輸出上打印從客戶端讀入的字符串
  33.         line=sin.readLine(); 
  34.         //從標準輸入讀入一字符串
  35.         while(!line.equals("bye")){ 
  36.         //若是該字符串爲 "bye",則中止循環
  37.           os.println(line); 
  38.           //向客戶端輸出該字符串
  39.           os.flush(); 
  40.           //刷新輸出流,使Client立刻收到該字符串
  41.           System.out.println("Server:"+line); 
  42.           //在系統標準輸出上打印讀入的字符串
  43.           System.out.println("Client:"+is.readLine());
  44.           //從Client讀入一字符串,並打印到標準輸出上
  45.           line=sin.readLine(); 
  46.           //從系統標準輸入讀入一字符串
  47.         }  //繼續循環
  48.         os.close(); //關閉Socket輸出流
  49.         is.close(); //關閉Socket輸入流
  50.         socket.close(); //關閉Socket
  51.         server.close(); //關閉ServerSocket
  52.       }catch(Exception e){
  53.         System.out.println("Error:"+e); 
  54.         //出錯,打印出錯信息
  55.       }
  56.     }
  57.   }
複製代碼


從上面的兩個程序中咱們能夠看到,socket四個步驟的使用過程。讀者能夠分別將Socket使用的四個步驟的對應程序段選擇出來,這樣便於讀者對socket的使用有進一步的瞭解。

最好是能在真正的網絡環境下試驗該程序,這樣更容易分辨輸出的內容和客戶機,服務器的對應關係。同時也能夠修改該程序,提供更爲強大的功能

================================================================

java網絡編程(4)


11.支持多客戶的client/server程序設計

客戶端程序:MultiTalkClient.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   public class MultiTalkClient {
  4.    public static void main(String args[]) {
  5.     try{
  6.       Socket socket=new Socket("127.0.0.1",4700); 
  7.       //向本機的4700端口發出客戶請求
  8.       BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
  9.       //由系統標準輸入設備構造BufferedReader對象
  10.       PrintWriter os=new PrintWriter(socket.getOutputStream());
  11.       //由Socket對象獲得輸出流,並構造PrintWriter對象
  12.       BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  13.       //由Socket對象獲得輸入流,並構造相應的BufferedReader對象
  14.       String readline;
  15.       readline=sin.readLine(); //從系統標準輸入讀入一字符串
  16.       while(!readline.equals("bye")){ 
  17.       //若從標準輸入讀入的字符串爲 "bye"則中止循環
  18.         os.println(readline); 
  19.         //將從系統標準輸入讀入的字符串輸出到Server
  20.         os.flush(); 
  21.         //刷新輸出流,使Server立刻收到該字符串
  22.         System.out.println("Client:"+readline); 
  23.         //在系統標準輸出上打印讀入的字符串
  24.         System.out.println("Server:"+is.readLine()); 
  25.         //從Server讀入一字符串,並打印到標準輸出上
  26.         readline=sin.readLine(); 
  27.         //從系統標準輸入讀入一字符串
  28.       } //繼續循環
  29.       os.close(); //關閉Socket輸出流
  30.       is.close(); //關閉Socket輸入流
  31.       socket.close(); //關閉Socket
  32.     }catch(Exception e) {
  33.       System.out.println("Error"+e); //出錯,則打印出錯信息
  34.     }
  35.   }
  36. }
複製代碼


服務器端程序: MultiTalkServer.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   import ServerThread;
  4.   public class MultiTalkServer{
  5.    static int clientnum=0; //靜態成員變量,記錄當前客戶的個數
  6.    public static void main(String args[]) throws IOException {
  7.     ServerSocket serverSocket=null;
  8.     boolean listening=true;
  9.     try{
  10.       serverSocket=new ServerSocket(4700); 
  11.       //建立一個ServerSocket在端口4700監聽客戶請求
  12.     }catch(IOException e) {
  13.       System.out.println("Could not listen on port:4700."); 
  14.       //出錯,打印出錯信息
  15.       System.exit(-1); //退出
  16.     }
  17.     while(listening){ //永遠循環監聽
  18.       new ServerThread(serverSocket.accept(),clientnum).start();
  19.       //監聽到客戶請求,根據獲得的Socket對象和
  20.        客戶計數建立服務線程,並啓動之
  21.       clientnum++; //增長客戶計數
  22.     }
  23.     serverSocket.close(); //關閉ServerSocket
  24.   }
  25. }
複製代碼


程序ServerThread.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   public class ServerThread extends Thread{
  4.    Socket socket=null; //保存與本線程相關的Socket對象
  5.    int clientnum; //保存本進程的客戶計數
  6.    public ServerThread(Socket socket,int num) { //構造函數
  7.     this.socket=socket; //初始化socket變量
  8.     clientnum=num+1; //初始化clientnum變量
  9.    }
  10.    public void run() { //線程主體
  11.     try{
  12.       String line;
  13.       BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  14.   //由Socket對象獲得輸入流,並構造相應的BufferedReader對象
  15.       PrintWriter os=newPrintWriter(socket.getOutputStream());
  16.       //由Socket對象獲得輸出流,並構造PrintWriter對象
  17.       BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
  18.       //由系統標準輸入設備構造BufferedReader對象
  19.       System.out.println("Client:"+ clientnum +is.readLine());
  20.       //在標準輸出上打印從客戶端讀入的字符串
  21.       line=sin.readLine(); 
  22.       //從標準輸入讀入一字符串
  23.       while(!line.equals("bye")){ 
  24.       //若是該字符串爲 "bye",則中止循環
  25.         os.println(line); 
  26.         //向客戶端輸出該字符串
  27.         os.flush(); 
  28.         //刷新輸出流,使Client立刻收到該字符串
  29.         System.out.println("Server:"+line); 
  30.         //在系統標準輸出上打印該字符串
  31.         System.out.println("Client:"+ clientnum +is.readLine());
  32.         //從Client讀入一字符串,並打印到標準輸出上
  33.         line=sin.readLine(); 
  34.         //從系統標準輸入讀入一字符串
  35.       } //繼續循環
  36.       os.close(); //關閉Socket輸出流
  37.       is.close(); //關閉Socket輸入流
  38.       socket.close(); //關閉Socket
  39.       server.close(); //關閉ServerSocket
  40.      }catch(Exception e){
  41.       System.out.println("Error:"+e); 
  42.       //出錯,打印出錯信息
  43.      }
  44.    }
  45.  }
複製代碼

========================================================

java網絡編程(5)

12.據報Datagram通信

前面在介紹TCP/IP協議的時候,咱們已經提到,在TCP/IP協議的傳輸層除了TCP協議以外還有一個UDP協議,相比而言UDP的應用不如TCP普遍,幾個標準的應用層協議HTTP,FTP,SMTP…使用的都是TCP協議。可是,隨着計算機網絡的發展,UDP協議正愈來愈來顯示出其威力,尤爲是在須要很強的實時交互性的場合,如網絡遊戲,視頻會議等,UDP更是顯示出極強的威力,下面咱們就介紹一下Java環境下如何實現UDP網絡傳輸。


13.什麼是Datagram

所謂數據報(Datagram)就跟平常生活中的郵件系統同樣,是不能保證可靠的寄到的,而面向連接的TCP就比如電話,雙方能確定對方接受到了信息。在本章前面,咱們已經對UDP和TCP進行了比較,在這裏再稍做小節:


  TCP,可靠,傳輸大小無限制,可是須要鏈接創建時間,差錯控制開銷大。

  UDP,不可靠,差錯控制開銷較小,傳輸大小限制在64K如下,不須要創建鏈接。


  總之,這兩種協議各有特色,應用的場合也不一樣,是徹底互補的兩個協議,在TCP/IP協議中佔有一樣重要的地位,要學好網絡編程,二者缺一不可。


14.Datagram通信的表示方法:DatagramSocket;DatagramPacket包

java.net中提供了兩個類DatagramSocket和DatagramPacket用來支持數據報通訊,DatagramSocket用於在程序之間創建傳送數據報的通訊鏈接, DatagramPacket則用來表示一個數據報。先來看一下DatagramSocket的構造方法:

   DatagramSocket();
   DatagramSocket(int prot);
   DatagramSocket(int port, InetAddress laddr)


  其中,port指明socket所使用的端口號,若是未指明端口號,則把socket鏈接到本地主機上一個可用的端口。laddr指明一個可用的本地地址。給出端口號時要保證不發生端口衝突,不然會生成SocketException類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。

用數據報方式編寫client/server程序時,不管在客戶方仍是服務方,首先都要創建一個DatagramSocket對象,用來接收或發送數據報,而後使用DatagramPacket類對象做爲傳輸數據的載體。下面看一下DatagramPacket的構造方法 :
   DatagramPacket(byte 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爲數據報中數據的長度,addr和port旨明目的地址,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地址,而後再獲取相應的地址信息。


15.基於UDP的簡單的Client/Server程序設計

客戶方程序 QuoteClient.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   import java.util.*;
  4.   public class QuoteClient {
  5.    public static void main(String[] args) throws IOException 
  6.    {
  7.     if(args.length!=1) { 
  8.     //若是啓動的時候沒有給出Server的名字,那麼出錯退出
  9.      System.out.println("Usage:java QuoteClient <hostname>"); 
  10.      //打印出錯信息
  11.      return; //返回
  12.     }

  13.     DatagramSocket socket=new DatagramSocklet(); 
  14.     //建立數據報套接字

  15.     Byte[] buf=new byte[256]; //建立緩衝區
  16.     InetAddress address=InetAddress.getByName(args [0]); 
  17. //由命令行給出的第一個參數默認爲Server的名字,經過它獲得Server的IP信息
  18.     DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445);
  19.     //建立DatagramPacket對象
  20.     socket.send(packet); //發送
  21.     packet=new DatagramPacket(buf,buf.length); 
  22.     //建立新的DatagramPacket對象,用來接收數據報
  23.     socket.receive(packet); //接收
  24.     String received=new String(packet.getData()); 
  25.     //根據接收到的字節數組生成相應的字符串
  26.     System.out.println("Quote of the Moment:"+received ); 
  27.     //打印生成的字符串

  28.     socket.close(); //關閉套接口
  29.    }
  30.  }
複製代碼


服務器方程序:QuoteServer.java

  1.  public class QuoteServer{
  2.    public static void main(String args[]) throws java.io.IOException 
  3.    {
  4.     new QuoteServerThread().start(); 
  5.     //啓動一個QuoteServerThread線程
  6.    }
  7.   }
複製代碼


程序QuoteServerThread.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   import java.util.*;
  4.   //服務器線程
  5.   public class QuoteServerThread extends Thread
  6.   {
  7.   protected DatagramSocket socket=null; 
  8.   //記錄和本對象相關聯的DatagramSocket對象
  9.   protected BufferedReader in=null; 
  10.   //用來讀文件的一個Reader
  11.   protected boolean moreQuotes=true; 
  12.   //標誌變量,是否繼續操做

  13.   public QuoteServerThread() throws IOException { 
  14.   //無參數的構造函數
  15.     this("QuoteServerThread"); 
  16.     //以QuoteServerThread爲默認值調用帶參數的構造函數
  17.   }
  18.   public QuoteServerThread(String name) throws IOException {
  19.     super(name); //調用父類的構造函數
  20.     socket=new DatagramSocket(4445); 
  21.     //在端口4445建立數據報套接字
  22.     try{
  23.       in= new BufferedReader(new FileReader(" one-liners.txt"));
  24.       //打開一個文件,構造相應的BufferReader對象
  25.     }catch(FileNotFoundException e) { //異常處理
  26.       System.err.println("Could not open quote file. Serving time instead."); 
  27.        //打印出錯信息
  28.     }
  29.   }
  30.   public void run() //線程主體
  31.   {
  32.     while(moreQuotes) {
  33.      try{
  34.        byte[] buf=new byte[256]; //建立緩衝區
  35.        DatagramPacket packet=new DatagramPacket(buf,buf.length);
  36.        //由緩衝區構造DatagramPacket對象
  37.        socket.receive(packet); //接收數據報
  38.        String dString=null;
  39.        if(in= =null) dString=new Date().toString(); 
  40.        //若是初始化的時候打開文件失敗了,
  41.        //則使用日期做爲要傳送的字符串
  42.        else dString=getNextQuote(); 
  43.        //不然調用成員函數從文件中讀出字符串
  44.        buf=dString.getByte(); 
  45.        //把String轉換成字節數組,以便傳送

  46.        InetAddress address=packet.getAddress(); 
  47.        //從Client端傳來的Packet中獲得Client地址
  48.        int port=packet.getPort(); //和端口號
  49.        packet=new DatagramPacket(buf,buf.length,address,port); 
  50.        //根據客戶端信息構建DatagramPacket
  51.        socket.send(packet); //發送數據報
  52.       }catch(IOException e) { //異常處理
  53.        e.printStackTrace(); //打印錯誤棧
  54.        moreQuotes=false; //標誌變量置false,以結束循環
  55.       }
  56.     }
  57.     socket.close(); //關閉數據報套接字
  58.   }

  59.   protected String getNextQuotes(){ 
  60.   //成員函數,從文件中讀數據
  61.     String returnValue=null;
  62.     try {
  63.        if((returnValue=in.readLine())= =null) { 
  64.        //從文件中讀一行,若是讀到了文件尾
  65.        in.close( ); //關閉輸入流
  66.        moreQuotes=false; 
  67.        //標誌變量置false,以結束循環
  68.        returnValue="No more quotes. Goodbye."; 
  69.        //置返回值
  70.        } //不然返回字符串即爲從文件讀出的字符串
  71.     }catch(IOEception e) { //異常處理
  72.        returnValue="IOException occurred in server"; 
  73.        //置異常返回值
  74.     }
  75.     return returnValue; //返回字符串
  76.   }
  77. }
複製代碼


能夠看出使用UDP和使用TCP在程序上仍是有很大的區別的。一個比較明顯的區別是,UDP的Socket編程是不提供監聽功能的,也就是說通訊雙方更爲平等,面對的接口是徹底同樣的。可是爲了用UDP實現C/S結構,在使用UDP時可使用DatagramSocket.receive()來實現相似於監聽的功能。由於receive()是阻塞的函數,當它返回時,緩衝區裏已經填滿了接受到的一個數據報,而且能夠從該數據報獲得發送方的各類信息,這一點跟accept()是很相象的,於是能夠根據讀入的數據報來決定下一步的動做,這就達到了跟網絡監聽類似的效果。


16.用數據報進行廣播通信

客戶方程序:MulticastClient.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   import java.util.*;
  4.   public class MulticastClient {
  5.     public static void main(String args[]) throws IOException
  6.     {
  7.      MulticastSocket socket=new MulticastSocket(4446); 
  8.      //建立4446端口的廣播套接字
  9.      InetAddress address=InetAddress.getByName("230.0.0.1"); 
  10.      //獲得230.0.0.1的地址信息
  11.      socket.joinGroup(address); 
  12.      //使用joinGroup()將廣播套接字綁定到地址上
  13.      DatagramPacket packet;

  14.      for(int i=0;i<5;i++) {
  15.        byte[] buf=new byte[256]; 
  16.        //建立緩衝區
  17.        packet=new DatagramPacket(buf,buf.length); 
  18.        //建立接收數據報
  19.        socket.receive(packet); //接收
  20.        String received=new String(packet.getData()); 
  21.        //由接收到的數據報獲得字節數組,
  22.        //並由此構造一個String對象
  23.        System.out.println("Quote of theMoment:"+received); 
  24.        //打印獲得的字符串
  25.      } //循環5次
  26.      socket.leaveGroup(address); 
  27.      //把廣播套接字從地址上解除綁定
  28.      socket.close(); //關閉廣播套接字
  29.    }
  30.  }
複製代碼


服務器方程序:MulticastServer.java

  1. public class MulticastServer{
  2.     public static void main(String args[]) throws java.io.IOException 
  3.     {
  4.       new MulticastServerThread().start(); 
  5.       //啓動一個服務器線程
  6.     }
  7.   }
複製代碼


程序MulticastServerThread.java

  1. import java.io.*;
  2.   import java.net.*;
  3.   import java.util.*;
  4.   public class MulticastServerThread extends QuoteServerThread 
  5.   //從QuoteServerThread繼承獲得新的服務器線程類MulticastServerThread
  6.   {
  7.     Private long FIVE_SECOND=5000; //定義常量,5秒鐘
  8.     public MulticastServerThread(String name) throws IOException 
  9.     {
  10.       super("MulticastServerThread"); 
  11.       //調用父類,也就是QuoteServerThread的構造函數
  12.     }

  13.     public void run() //重寫父類的線程主體
  14.     {
  15.      while(moreQuotes) { 
  16.      //根據標誌變量判斷是否繼續循環
  17.       try{
  18.         byte[] buf=new byte[256]; 
  19.         //建立緩衝區
  20.         String dString=null;
  21.         if(in==null) dString=new Date().toString(); 
  22.         //若是初始化的時候打開文件失敗了,
  23.         //則使用日期做爲要傳送的字符串
  24.         else dString=getNextQuote(); 
  25.         //不然調用成員函數從文件中讀出字符串
  26.         buf=dString.getByte(); 
  27.         //把String轉換成字節數組,以便傳送send it
  28.         InetAddress group=InetAddress.getByName("230.0.0.1"); 
  29.         //獲得230.0.0.1的地址信息
  30.         DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
  31.         //根據緩衝區,廣播地址,和端口號建立DatagramPacket對象
  32.         socket.send(packet); //發送該Packet
  33.         try{
  34.           sleep((long)(Math.random()*FIVE_SECONDS)); 
  35.           //隨機等待一段時間,0~5秒之間
  36.         }catch(InterruptedException e) { } //異常處理
  37.       }catch(IOException e){ //異常處理
  38.         e.printStackTrace( ); //打印錯誤棧

  39.         moreQuotes=false; //置結束循環標誌
  40.       }
  41.     }
  42.     socket.close( ); //關閉廣播套接口
  43.    }
  44.  }
複製代碼


至此,Java網絡編程這一章已經講解完畢。讀者經過學習,應該對網絡編程有了一個清晰的認識,可能對某些概念還不是十分的清楚,仍是須要更多的實踐來進一步掌握。編程語言的學習不一樣於通常的學習,及其強調實踐的重要性。讀者應該對URL網絡編程,Socket中的TCP,UDP編程進行大量的練習才能更好的掌握本章中所提到的一些概念,才能真正學到Java網絡編程的精髓!

最後幾個小節所舉的例子,務必要親自試驗一下,若是遇到問題,想辦法解決之。最好能根據本身的意圖加以改進。這樣才能更好的理解這幾個程序,理解其中所包含的編程思想。


小結:

主要講解了Java環境下的網絡編程。由於TCP/IP協議是Java網絡編程的基礎知識,因此開篇重點介紹了TCP/IP協議中的一些概念,TCP/IP協議自己是一個十分龐大的系統,用幾個小節是不可能講清楚的。因此咱們只是聯繫實際,講解了一些最基本的概念,幫助學生理解後面的相關內容。重點有一下幾個概念:主機名,IP,端口,服務類型,TCP,UDP。後續的內容分爲兩大塊,一塊是以URL爲主線,講解如何經過URL類和URLConnection類訪問WWW網絡資源,因爲使用URL十分方便直觀,儘管功能不是很強,仍是值得推薦的一種網絡編程方法,尤爲是對於初學者特別容易接受。本質上講,URL網絡編程在傳輸層使用的仍是TCP協議。

另外一塊是以Socket接口和C/S網絡編程模型爲主線,依次講解了如何用Java實現基於TCP的C/S結構,主要用到的類有Socket,ServerSocket。以及如何用Java實現基於UDP的C/S結構,還討論了一種特殊的傳輸方式,廣播方式,這種方式是UDP所特有的,主要用到的類有DatagramSocket , DatagramPacket, MulticastSocket。這一塊在Java網絡編程中相對而言是最難的(儘管Java在網絡編程這方面已經作的夠"傻瓜"了,可是網絡編程在其餘環境下的倒是一件極爲頭痛的事情,再"傻瓜"仍是有必定的難度),也是功能最爲強大的一部分,應該好好研究,領悟其中的思想。


最後要強調的是要學好Java網絡編程,Java語言,最重要的仍是在於多多練習!

轉自:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=48337 

相關文章
相關標籤/搜索