1. 什麼是TCP/ IP協議?
2. TCP/IP有哪兩種傳輸協議,各有什麼特點?
3. 什麼是URL?
4. URL和IP地址有什麼樣的關係?
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的傳輸層同時存在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會更合理一些。
8.2 基於URL的高層次Java網路編程
8.2.1一致資源定位器URL
URL(Uniform Resource Locator)是一致資源定位器的簡稱,它表示Internet上某一資源的地址。通過URL我們可以訪問Internet上的各種網路資源,比如最常見的WWW,FTP網站。瀏覽器通過解析給定的URL可以在網路上尋找相應的檔案或其他資源。
8.2.2 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 協議名://機器名+連接埠號碼+檔案名稱+內部引用.
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介面的。WWW,FTP等標準化的網路服務都是基於TCP協議的,所以本質上講URL編程也是基於TCP的一種應用.
8.3 基於Socket的低層次Java網路編程
8.3.1 Socket通訊
網路上的兩個程式通過一個雙向的通訊串連實現資料的交換,這個雙向鏈路的一端稱為一個Socket。Socket通常用來實現客戶方和服務方的串連。Socket是TCP/IP協議的一個十分流行的編程介面,一個Socket由一個IP地址和一個連接埠號碼唯一確定。
在傳統的UNIX環境下可以操作TCP/IP協議的介面不止Socket一個,Socket所支援的協議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯絡的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網路編程。
8.3.2 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.
8.3.3 建立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是必須捕獲或拋出例外。
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廣泛,幾個標準的應用程式層協議HTTP,FTP,SMTP…使用的都是TCP協議。但是,隨著電腦網路的發展,UDP協議正越來越來顯示出其威力,尤其是在需要很強的即時互動性的場合,如網路遊戲,視頻會議等,UDP更是顯示出極強的威力,下面我們就介紹一下Java環境下如何?UDP網路傳輸。
8.3.11 什麼是Datagram
所謂資料報(Datagram)就跟日常生活中的郵件系統一樣,是不能保證可靠的寄到的,而面向連結的TCP就好比電話,雙方能肯定對方接受到了資訊。在本章前面,我們已經對UDP和TCP進行了比較,在這裡再稍作小節:
TCP,可靠,傳輸大小無限制,但是需要串連建立時間,差錯控制開銷大。
UDP,不可靠,差錯控制開銷較小,傳輸大小限制在64K以下,不需要建立串連。
8.3.12 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地址,然後再擷取相應的地址資訊。
8.3.14 用資料報進行廣播通訊
DatagramSocket只允許資料報發送一個目的地址,java.net包中提供了一個類MulticastSocket,允許資料報以廣播方式發送到該連接埠的所有客戶。MulticastSocket用在用戶端,監聽伺服器廣播來的資料。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/coolriver/archive/2004/09/13/102420.aspx
註明:這哥們喜歡把注釋寫在代碼下邊,看的我老噁心了。暈……