已閱讀5頁,還剩18頁未讀, 繼續(xù)免費閱讀
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
網(wǎng)絡編程的基本概念, TCP/IP協(xié)議簡介 8.1.1 網(wǎng)絡基礎知識 計算機網(wǎng)絡形式多樣,內容繁雜。網(wǎng)絡上的計算機要互相通信,必須遵循一定的協(xié)議。目前使用最廣泛的網(wǎng)絡協(xié)議是 Internet 上所使用的 TCP/IP 協(xié)議。 網(wǎng)絡編程的目的就是指直接或間接地通過網(wǎng)絡協(xié)議與其他計算機進行通訊。網(wǎng)絡編程中有兩個主要的問題,一個是如何準確的定位網(wǎng)絡上一臺或多臺主機,另一個就是找到主機后如何可靠高效的進行數(shù)據(jù)傳輸。在 TCP/IP 協(xié)議中 IP 層主要負責網(wǎng)絡主機的定位,數(shù)據(jù)傳輸?shù)穆酚?,?IP 地址可以唯一地確定 Internet 上 的一臺主機。而 TCP 層則提供面向應用的可靠的或非可靠的數(shù)據(jù)傳輸機制,這是網(wǎng)絡編程的主要對象,一般不需要關心 IP 層是如何處理數(shù)據(jù)的。 目前較為流行的網(wǎng)絡編程模型是客戶機 /服務器( C/S)結構。即通信雙方一方作為服務器等待客戶提出請求并予以響應。客戶則在需要服務時向服務器提出申請。服務器一般作為守護進程始終運行,監(jiān)聽網(wǎng)絡端口,一旦有客戶請求,就會啟動一個服務進程來響應該客戶,同時自己繼續(xù)監(jiān)聽服務端口,使后來的客戶也能及時得到服務。 8.1.2網(wǎng)絡基本概念 IP 地址:標識計算機等網(wǎng)絡設備的網(wǎng)絡地址,由 四個 8 位的二進制數(shù)組成,中間以小數(shù)點分隔。 如: , 0 主機名 (hostname):網(wǎng)絡地址的助記名,按照域名進行分級管理。 如: 端口號 (port number):網(wǎng)絡通信時同一機器上的不同進程的標識。 如 :80, 21, 23, 25,其中 11024 為系統(tǒng)保留的端口號 服務類型 (service):網(wǎng)絡的各種服務。 http, telnet, ftp, smtp 我們可以用以下的一幅圖來描述這里我們所提到的幾個概念: 在 Internet 上 IP 地址和主機名是一一對應的,通過域名解析可以由主機名得到機器的IP,由于機器名更接近自然語言,容易記憶,所以使用比 IP 地址廣泛,但是對機器而言只有 IP 地址才是有效的標識符。 通常一臺主機上總是有很多個進程需要網(wǎng)絡資源進行網(wǎng)絡通訊 。網(wǎng)絡通訊的對象準確的講不是主機,而應該是主機中運行的進程。這時候光有主機名或 IP 地址來標識這么多個進程顯然是不夠的。端口號就是為了在一臺主機上提供更多的網(wǎng)絡資源而采取得一種手段,也是 TCP 層提供的一種機制。只有通過主機名或 IP 地址和端口號的組合才能唯一的確定網(wǎng)絡通訊中的對象:進程。 服務類型是在 TCP 層上面的應用層的概念?;?TCP/IP 協(xié)議可以構建出各種復雜的應用,服務類型是那些已經被標準化了的應用,一般都是網(wǎng)絡服務器(軟件)。讀者可以編寫自己的基于網(wǎng)絡的服務器,但都不能被稱作標準的服務類型。 8.1.3兩類傳輸協(xié)議: TCP; UDP 盡管 TCP/IP 協(xié)議的名稱中只有 TCP 這個協(xié)議名,但是在 TCP/IP 的傳輸層同時存在 TCP 和 UDP 兩個協(xié)議。 TCP是 Tranfer Control Protocol 的簡稱,是一種面向連接的保證可靠傳輸?shù)膮f(xié)議。通過 TCP協(xié)議傳輸,得到的是一個順序的無差錯的數(shù)據(jù)流。發(fā)送方和接收方的成對的兩個 socket 之間必須建立連接,以便在 TCP 協(xié)議的基礎上進行通信,當一個 socket(通常都是 server socket)等待建立連接時,另一個 socket 可以要求進行連接,一旦這兩個 socket 連接起來,它們就可以進行雙向數(shù)據(jù)傳輸,雙方都可以進行發(fā)送或接收操作。 UDP是 User Datagram Protocol 的簡稱,是一種無連接的協(xié)議,每個數(shù)據(jù)報都是一個獨立的信息,包括完整的源地址或目的地址,它在網(wǎng)絡上以任何可能的路徑傳往目的地,因此能否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。 下面我們對這兩種協(xié)議做簡單比較: 使用 UDP 時,每個數(shù)據(jù)報中都給出了完整的地址信息,因此無需要建立發(fā)送方和接收方的連接。對于 TCP 協(xié)議,由于它是一個面向連接的協(xié)議 ,在 socket 之間進行數(shù)據(jù)傳輸之前必然要建立連接,所以在 TCP 中多了一個連接建立的時間。 使用 UDP 傳輸數(shù)據(jù)時是有大小限制的,每個被傳輸?shù)臄?shù)據(jù)報必須限定在 64KB 之內。而 TCP 沒有這方面的限制,一旦連接建立起來,雙方的 socket 就可以按統(tǒng)一的格式傳輸大量的數(shù)據(jù)。 UDP 是一個不可靠的協(xié)議,發(fā)送方所發(fā)送的數(shù)據(jù)報并不一定以相同的次序到達接收方。而 TCP 是一個可靠的協(xié)議,它確保接收方完全正確地獲取發(fā)送方所發(fā)送的全部數(shù)據(jù)。 總之, TCP 在網(wǎng)絡通信上有極強的生命力,例如遠程連接( Telnet)和文件傳輸 ( FTP)都需要不定長度的數(shù)據(jù)被可靠地傳輸。相比之下 UDP 操作簡單,而且僅需要較少的監(jiān)護,因此通常用于局域網(wǎng)高可靠性的分散系統(tǒng)中 client/server 應用程序。 既然有了保證可靠傳輸?shù)?TCP 協(xié)議,為什么還要非可靠傳輸?shù)?UDP 協(xié)議呢?主要的原因有兩個。一是可靠的傳輸是要付出代價的,對數(shù)據(jù)內容正確性的檢驗必然占用計算機的處理時間和網(wǎng)絡的帶寬,因此 TCP 傳輸?shù)男什蝗?UDP 高。二是在許多應用中并不需要保證嚴格的傳輸可靠性,比如視頻會議系統(tǒng),并不要求音頻視頻數(shù)據(jù)絕對的正確,只要保證連貫性就可以了,這種情況下顯 然使用 UDP 會更合理一些。 8.2 基于 URL 的高層次 Java 網(wǎng)絡編程 8.2.1一致資源定位器 URL URL(Uniform Resource Locator)是一致資源定位器的簡稱,它表示 Internet 上某一資源的地址。通過 URL 我們可以訪問 Internet 上的各種網(wǎng)絡資源,比如最常見的 WWW, FTP 站點。瀏覽器通過解析給定的 URL 可以在網(wǎng)絡上查找相應的文件或其他資源。 URL 是最為直觀的一種網(wǎng)絡定位方法。使用 URL 符合人們的語言習慣,容易記憶,所以應用十分廣泛。而且在目前使用最為廣泛的 TCP/IP 中對于 URL 中主機名的解析也是協(xié)議的一個標準,即所謂的域名解析服務。使用 URL 進行網(wǎng)絡編程,不需要對協(xié)議本身有太多的了解,功能也比較弱,相對而言是比較簡單的,所以在這里我們先介紹在 Java 中如何使用 URL 進行網(wǎng)絡編程來引導讀者入門。 8.2.2 URL 的組成 protocol:/resourceName 協(xié)議名( protocol)指明獲取資源所使用的傳輸協(xié)議,如 http、 ftp、 gopher、 file 等,資源名( resourceName)則應該是資源的完整地址,包括主機名、端口號、文件名或 文件內部的一個引用。例如: / 協(xié)議名 :/主機名 /home/welcome.html 協(xié)議名 :/機器名文件名 :80/Gamelan/network.html#BOTTOM 協(xié)議名 :/機器名端口號文件名內部引用 端口號是和 Socket 編程相關的一個概念,初學者不必在此深究,在后面會有詳細講解。內部引用是HTML 中的標記,有興趣的讀者可以參考有關 HTML 的書籍。 8.2.3 創(chuàng)建一個 URL 為了表示 URL, 中實現(xiàn)了類 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 (/); URL index263=new URL(net263, index.html) ( 3) public URL(String protocol, String host, String file); new URL(http, , /pages/G. html); ( 4) public URL(String protocol, String host, int port, String file); URL gamelan=new URL(http, , 80, Pages/Gwork.html); 注意: 類 URL 的構造方法都聲明拋棄非運行時例外( MalformedURLException),因此生成 URL 對象時,我們必須要對這一例外進行處理,通常是用 try-catch 語句進行捕獲。格式如下: try URL myURL= new URL() catch (MalformedURLException e) /exception handler code here 8.2.4 解析一個 URL 一個 URL 對象生成后,其屬性是不能被改變的,但是我們可以通過類 URL 所提供的方法來獲取這些屬性: public String getProtocol() 獲取該 URL 的協(xié)議名。 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 對象,并獲取它的各個屬性。 import .*; import java.io.*; public class ParseURL public static void main (String args) throws Exception URL Aurl=new URL(:80/docs/books/); URL tuto=new URL(Aurl,ro.html#DOWNLOADING); System.out.println(protocol=+ tuto.getProtocol(); System.out.println(host =+ tuto.getHost(); System.out.println(filename=+ tuto.getFile(); System.out.println(port=+ tuto.getPort(); System.out.println(ref=+tuto.getRef(); System.out.println(query=+tuto.getQuery(); System.out.println(path=+tuto.getPath(); System.out.println(UserInfo=+tuto.getUserInfo(); System.out.println(Authority=+tuto.getAuthority(); 執(zhí)行結果為: protocol=http host = filename=/docs/books/ro.html port=80 ref=DOWNLOADING query=null path=/docs/books/ro.html UserInfo=null Authority=:80 8.2.5 從 URL 讀取 WWW網(wǎng)絡資源 當我們得到一個 URL 對象后,就可以通過它讀取指定的 WWW 資源。這時我們將使用 URL的方法 openStream(),其定義為: InputStream openStream(); 方法 openSteam()與指定的 URL 建立連接并返回 InputStream 類的對象以從這一連接中讀取數(shù)據(jù)。 public class URLReader public static void main(String args) throws Exception /聲明拋出所有例外 URL tirc = new URL(/); /構建一 URL 對象 BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream(); /使用 openStream 得到一輸入流并由此構造一個 BufferedReader 對象 String inputLine; while (inputLine = in.readLine() != null) /從輸入流不斷的讀數(shù)據(jù),直到讀完為止 System.out.println(inputLine); /把讀入的數(shù)據(jù)打印到屏幕上 in.close(); /關閉輸入流 8.2.6 通過 URLConnetction連接 WWW 通過 URL 的方法 openStream(),我們只能從網(wǎng) 絡上讀取數(shù)據(jù),如果我們同時還想輸出數(shù)據(jù),例如向服務器端的 CGI 程序發(fā)送一些數(shù)據(jù),我們必須先與 URL 建立連接,然后才能對其進行讀寫,這時就要用到類 URLConnection 了。 CGI 是公共網(wǎng)關接口( Common Gateway Interface)的簡稱,它是用戶瀏覽器和服務器端的應用程序進行連接的接口,有關 CGI 程序設計,請讀者參考有關書籍。 類 URLConnection 也在包 中定義,它表示 Java 程序和 URL 在網(wǎng)絡上的通信連接。當與一個 URL 建立連接時,首先要在一個 URL 對象上通過方 法 openConnection()生成對應的 URLConnection 對 象 。 例 如 下 面 的 程 序 段 首 先 生 成 一 個 指 向 地 址/index.shtml 的對象,然后用 openConnection()打開該 URL 對象上的一個連接,返回一個 URLConnection 對象。如果連接過程失敗,將產生 IOException. Try URL netchinaren = new URL (/index.shtml); URLConnectonn tc = netchinaren.openConnection(); catch(MalformedURLException e) /創(chuàng)建 URL()對象失敗 catch (IOException e) /openConnection()失敗 類 URLConnection 提供了很多方法來設置或獲取連接參數(shù),程序設計時最常使用的是getInputStream()和 getOurputStream(),其定義為: InputSteram getInputSteram(); OutputSteram getOutputStream(); 通過返回的輸入 /輸出流我們可以與遠程對象進行通信??聪旅娴睦樱?URL url =new URL (/cgi-bin/backwards); /創(chuàng)建一 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 來實現(xiàn)的。它等價于 openConnection().getInputStream(); 基于 URL 的網(wǎng)絡編程在底層其實還是基于下面要講的 Socket 接口的。 WWW, FTP 等標準化的網(wǎng)絡服務都是基于 TCP 協(xié)議的,所以本質上講 URL 編程也是基于 TCP 的一種應用。 8.3 基于 Socket(套接字)的低層次 Java 網(wǎng)絡編程 8.3.1 Socket 通訊 網(wǎng)絡上的兩個程序通過一個雙向的通訊連接實現(xiàn)數(shù)據(jù)的交換,這個雙向鏈路的一端稱為一個 Socket。 Socket 通常用來實現(xiàn)客戶方和服務方的連接。 Socket 是 TCP/IP 協(xié)議的一個十分流行的編程界面,一個 Socket 由一個 IP 地址和一個端口號唯一確定。 在傳統(tǒng)的 UNIX 環(huán)境下可以操作 TCP/IP 協(xié)議的接口不止 Socket 一個, Socket 所支持的協(xié)議種類也不光 TCP/IP 一種,因此兩者之間是沒有必然聯(lián)系的。在 Java 環(huán)境下, Socket 編程主要是指基于 TCP/IP 協(xié)議的網(wǎng)絡編程。 說 Socket 編程是低層次網(wǎng)絡編程并不等于它功能不強大,恰恰相反,正因為層次低,Socket 編程比基于 URL 的網(wǎng)絡編程提供了更強大的功能和更靈活的控制,但是卻要更復雜一些。由于 Java 本身的特殊性, Socket 編程在 Java 中可能已經是層次最低的網(wǎng)絡編程接口,在 Java 中要直接操作協(xié)議中更低的層次,需要使用 Java 的本地方法調用( JNI),在這里就不予討論了。 8.3.2 Socket 通訊的一般過程 前面已經提到 Socket 通常用來實現(xiàn) C/S 結構。 使用 Socket 進行 Client/Server 程序設計的一般連接過程是這樣的: Server 端 Listen(監(jiān)聽 )某個端口是否有連接請求, Client 端向 Server 端發(fā)出 Connect(連接 )請求, Server 端向 Client端發(fā)回 Accept(接受)消息。一個連接就建立起來了。 Server 端和 Client 端都可以通過 Send,Write 等方法與對方通信。 對于一個功能齊全的 Socket,都 要包含以下基本結構,其工作過程包含以下四個基本的步驟: ( 1) 創(chuàng)建 Socket; ( 2) 打開連接到 Socket 的輸入 /出流; ( 3) 按照一定的協(xié)議對 Socket 進行讀 /寫操作; ( 4) 關閉 Socket. 第三步是程序員用來調用 Socket 和實現(xiàn)程序功能的關鍵步驟,其他三步在各種程序中基本相同。 以上 4個步驟是針對 TCP 傳輸而言的,使用 UDP 進行傳輸時略有不同,在后面會有具體講解。 8.3.3 創(chuàng)建 Socket java 在包 中提供了兩個類 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 還是數(shù)據(jù)報 socket, localPort 表示本地主機的端口號, localAddr 和bindAddr 是本地機器的地址( ServerSocket 的主機地址), impl 是 socket 的父類,既可以用來創(chuàng)建 serverSocket 又可以用來創(chuàng)建 Socket。 count 則表示服務端所能支持的最大連接數(shù)。例如: Socket client = new Socket(127.0.01., 80); ServerSocket server = new ServerSocket(80); 注意,在選擇端口時,必須小心。每一個端口提供一種特定的服務,只有給出正確的端口,才能獲得相應的服務。 01023的端口號為系統(tǒng)所保留,例如 http 服務的端口號為 80,telnet服務的端口號為 21,ftp 服務的端口號為 23, 所以我們在選擇端口號時,最好選擇一個大于1023的數(shù)以防止發(fā)生沖突。 在創(chuàng)建 socket 時如果發(fā)生錯誤,將產 生 IOException,在程序中必須對之作出處理。所以在創(chuàng)建 Socket 或 ServerSocket 是必須捕獲或拋出例外。 8.3.4 客戶端的 Socket 下面是一個典型的創(chuàng)建客戶端 Socket 的過程。 try Socket socket=new Socket(,4700); /是 TCP/IP 協(xié)議中默認的本機地址 catch(IOException e) System.out.println(Error:+e); 這是最簡單的在客戶端創(chuàng)建一個 Socket 的一個小程序段,也是使用 Socket 進行網(wǎng)絡通訊的第一步,程序相當簡單,在這里不作過多解釋了。在后面的程序中會用到該小程序段。 8.3.5 服務器端的 ServerSocket 下面是一個典型的創(chuàng)建 Server 端 ServerSocket 的過程。 ServerSocket server=null; try server=new ServerSocket(4700); /創(chuàng)建一個 ServerSocket 在端口 4700監(jiān) 聽客戶請求 catch(IOException e) System.out.println(can not listen to :+e); Socket socket=null; try socket=server.accept(); /accept()是一個阻塞的方法,一旦有客戶請求,它就會返回一個 Socket 對象用于同客戶進行交互 catch(IOException e) System.out.println(Error:+e); 以上的程序是 Server 的典型工作模式,只不過在這里 Server 只能接收一個請求,接受完后 Server 就退出了。實際的應用中總是讓它不停的循環(huán)接收,一旦有客戶請求, Server 總是會創(chuàng)建一個服務線程來服務新來的客戶,而自己繼續(xù)監(jiān)聽。程序中 accept()是一個阻塞函數(shù),所謂阻塞性方法就是說該方法被調用后,將等待客戶的請求,直到有一個客戶啟動并請求連接到相同的端口,然后 accept()返回一個對應于客戶的socket。這時,客戶方和服務方都建立了用于通信的 socket,接下來就是由各個 socket 分別打開各自的輸入/輸出流。 8.3.6 打開輸入 /出流 類 Socket 提供了方法 getInputStream ()和 getOutStream()來得到對應的輸入 /輸出流以進行讀 /寫操作,這兩個方法分別返回 InputStream 和 OutputSteam 類對象。為了便于讀 /寫數(shù)據(jù),我們可以在返回的輸入 /輸出流對象上建立過濾流,如 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(); 輸入輸出流是網(wǎng)絡編程的實質性部分,具體如何構造所需要的過濾流,要根據(jù)需要而定,能否運用自如主要看讀者對 Java 中輸入輸出部分掌握如何。 8.3.7 關閉 Socket 每一個 Socket 存在時,都將占用一定的資源,在 Socket 對象使用完畢時,要其關閉。關閉Socket 可以調用 Socket 的 Close()方法。在關閉 Socket 之前, 應將與 Socket 相關的所有的輸入 /輸出流全部關閉,以釋放所有的資源。而且要注意關閉的順序,與 Socket 相關的所有的輸入 /輸出該首先關閉,然后再關閉 Socket。 os.close(); is.close(); socket.close(); 盡管 Java 有自動回收機制,網(wǎng)絡資源最終是會被釋放的。但是為了有效的利用資源,建議讀者按照合理的順序主動釋放資源。 8.3.8 簡單的 Client/Server 程序設計 下面我們給出一個用 Socket 實現(xiàn)的客戶和服務器交互的典型的 C/S 結構的演示 程序,讀者通過仔細閱讀該程序,會對前面所討論的各個概念有更深刻的認識。程序的意義請參考注釋。 1. 客戶端程序 import java.io.*; import .*; public class TalkClient public static void main(String args) try Socket socket=new Socket(,4700); /向本機的 4700端口發(fā)出 客戶請求 BufferedReader sin=new BufferedReader(new InputStreamReader(System.in); /由系統(tǒng)標準輸入設備構造 BufferedReader 對象 PrintWriter os=new PrintWriter(socket.getOutputStream(); /由 Socket 對象得到輸出流,并構造 PrintWriter 對象 BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream(); /由 Socket 對象得到輸入流,并構造相應的 BufferedReader 對象 String readline; readline=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 while(!readline.equals(bye) /若從標準輸入讀入的字符串為 bye則停止循環(huán) os.println(readline); /將從系統(tǒng)標準輸入讀入的字符串輸出到 Server os.flush(); /刷新輸出流,使 Server 馬上收到該字符串 System.out.println(Client:+readline); /在系統(tǒng)標準輸出上打印讀入的字符串 System.out.println(Server:+is.readLine(); /從 Server 讀入一字符串,并打印到標準輸出上 readline=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 /繼續(xù)循環(huán) os.close(); /關閉 Socket 輸出流 is.close(); /關閉 Socket 輸入流 socket.close(); /關閉 Socket catch(Exception e) System.out.println(Error+e); /出錯,則打印出錯信息 2. 服務器端程序 import java.io.*; import .*; import java.applet.Applet; public class TalkServer public static void main(String args) try ServerSocket server=null; try server=new ServerSocket(4700); /創(chuàng)建一個 ServerSocket 在端口 4700監(jiān)聽客戶請求 catch(Exception e) System.out.println(can not listen to:+e); /出錯,打印出錯信息 Socket socket=null; try socket=server.accept(); /使用 accept()阻塞等待客戶請求,有客戶 /請求到來則產生一個 Socket 對象,并繼續(xù)執(zhí)行 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); /由系統(tǒng)標準輸入設備構造 BufferedReader 對象 System.out.println(Client:+is.readLine(); /在標準輸出上打印從客戶端讀入的字符串 line=sin.readLine(); /從標準輸入讀入一字符串 while(!line.equals(bye) /如果該字符串為 bye,則停止循環(huán) os.println(line); /向客戶端輸出該字符串 os.flush(); /刷新輸出流,使 Client 馬上收到該字符串 System.out.println(Server:+line); /在系統(tǒng)標準輸出上打印讀入的字符串 System.out.println(Client:+is.readLine(); /從 Client 讀入一字符串,并打印到標準輸出上 line=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 /繼續(xù)循環(huán) os.close(); /關閉 Socket 輸出流 is.close(); /關閉 Socket 輸入流 socket.close(); /關閉 Socket server.close(); /關 閉 ServerSocket catch(Exception e) System.out.println(Error:+e); /出錯,打印出錯信息 從上面的兩個程序中我們可以看到, socket 四個步驟的使用過程。讀者可以分別將 Socket 使用的四個步驟的對應程序段選擇出來,這樣便于讀者對 socket 的使用有進一步的了解。 讀者可以在單機上試驗該程序,最好是能在真正的網(wǎng)絡環(huán)境下試驗該程序,這樣更容易分辨輸出 的內容和客戶機,服務器的對應關系。同時也可以修改該程序,提供更為強大的功能,或更加滿足讀者的意圖。 8.3.9 支持多客戶的 client/server 程序設計 前面提供的 Client/Server 程序只能實現(xiàn) Server 和一個客戶的對話。在實際應用中,往往是在服務器上運行一個永久的程序,它可以接收來自其他多個客戶端的請求,提供相應的服務。為了實現(xiàn)在服務器方給多個客戶提供服務的功能,需要對上面的程序進行改造,利用多線程實現(xiàn)多客戶機制。服務器總是在指定的端口上監(jiān)聽是否有客戶請求,一旦監(jiān)聽到客戶請求,服務器 就會啟動一個專門的服務線程來響應該客戶的請求,而服務器本身在啟動完線程之后馬上又進入監(jiān)聽狀態(tài),等待下一個客戶的到來。 客戶端的程序和上面程序是完全一樣的,讀者如果仔細閱讀過上面的程序,可以跳過不讀,把主要精力集中在 Server 端的程序上。 1. 客戶端程序: MultiTalkClient.java import java.io.*; import .*; public class MultiTalkClient public static void main(String args) try Socket socket=new Socket(,4700); /向本機的 4700端口發(fā)出客戶請求 BufferedReader sin=new BufferedReader(new InputStreamReader(System.in); /由系統(tǒng)標準輸入設備構造 BufferedReader 對象 PrintWriter os=new PrintWriter(socket.getOutputStream(); /由 Socket 對象得到輸出流,并構造 PrintWriter 對象 BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream(); /由 Socket 對象得到輸入流,并構造相應的 BufferedReader 對象 String readline; readline=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 while(!readline.equals(bye) /若從標準輸入讀入的字符串為 bye則停止循環(huán) os.println(readline); /將從系統(tǒng)標準輸入讀入的字符串輸出到 Server os.flush(); /刷新輸出流,使 Server 馬上收到該字符串 System.out.println(Client:+readline); /在系統(tǒng)標準輸出上打印讀入的字符串 System.out.println(Server:+is.readLine(); /從 Server 讀入一字符串,并打印到標準輸出上 readline=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 /繼續(xù)循環(huán) os.close(); /關閉 Socket 輸出流 is.close(); /關閉 Socket 輸入流 socket.close(); /關閉 Socket catch(Exception e) System.out.println(Error+e); /出錯,則打印出錯信息 2. 服務器端程序 : MultiTalkServer.java import java.io.*; import .*; import ServerThread; public class MultiTalkServer static int clientnum=0; /靜態(tài)成員變量,記錄當前客戶的個數(shù) public static void main(String args) throws IOException ServerSocket serverSocket=null; boolean listening=true; try serverSocket=new ServerSocket(4700); /創(chuàng)建一個 ServerSocket 在端口 4700監(jiān)聽客戶請求 catch(IOException e) System.out.println(Could not listen on port:4700.); /出錯,打印出錯信息 System.exit(-1); /退出 while(listening) /永遠循環(huán)監(jiān)聽 new ServerThread(serverSocket.accept(),clientnum).start(); /監(jiān)聽到客戶請求,根據(jù)得到的 Socket 對象和 客戶計數(shù)創(chuàng)建服務線程,并啟動之 clientnum+; /增加客戶計數(shù) serverSocket.close(); /關閉 ServerSocket 3. 程序 ServerThread.java import java.io.*; import .*; public class ServerThread extends Thread Socket socket=null; /保存與本線程相關的 Socket 對象 int clientnum; /保存本進程的客戶計數(shù) public ServerThread(Socket socket,int num) /構造函數(shù) this.socket=socket; /初始化 socket 變量 clientnum=num+1; /初始化 clientnum 變量 public void run() /線程主體 try 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); /由系統(tǒng)標準輸入設備構造 BufferedReader 對象 System.out.println(Client:+ clientnum +is.readLine(); /在標準輸出上打印從客戶端讀入的字符串 line=sin.readLine(); /從標準輸入讀入一字符串 while(!line.equals(bye) /如果該 字符串為 bye,則停止循環(huán) os.println(line); /向客戶端輸出該字符串 os.flush(); /刷新輸出流,使 Client 馬上收到該字符串 System.out.println(Server:+line); /在系統(tǒng)標準輸出上打印該字符串 System.out.println(Client:+ clientnum +is.readLine(); /從 Client 讀入一字符串,并打印到標準輸出上 line=sin.readLine(); /從系統(tǒng)標準輸入讀入一字符串 /繼續(xù)循環(huán) os.close(); /關閉 Socket 輸出流 is.close(); /關閉 Socket 輸入流 socket.close(); /關閉 Socket server.close(); /關閉 ServerSocket catch(Exception e) System.out.println(Error:+e); /出錯,打印出錯信息 這個程序向讀者展示了網(wǎng)絡應用中最為典型的 C/S 結構,我們可以用下面的圖來描述這樣一種模型: 通過以上的學習,讀者應該對 Java 的面向流的網(wǎng)絡編程有了一個比較全面的認識,這些都是基于 TCP的應用,后面我們將介 紹基于 UDP 的 Socket 編程。 8.3.10 據(jù)報 Datagram 通訊 前面在介紹 TCP/IP 協(xié)議的時候,我們已經提到,在 TCP/IP 協(xié)議的傳輸層除了 TCP協(xié)議之外還有一個 UDP 協(xié)議,相比而言 UDP 的應用不如 TCP 廣泛,幾個標準的應用層協(xié)議 HTTP, FTP, SMTP 使用的都是 TCP 協(xié)議。但是,隨著計算機網(wǎng)絡的發(fā)展, UDP 協(xié)議正越來越來顯示出其威力,尤其是在需要很強的實時交互性的場合,如網(wǎng)絡游戲,視頻會議等, UDP 更是顯示出極強的威力,下面我們就介紹一下 Java 環(huán)境下如何實現(xiàn) UDP 網(wǎng)絡傳輸。 8.3.11 什么是 Datagram 所謂數(shù)據(jù)報( Datagram)就跟日常生活中的郵件系統(tǒng)一樣,是不能保證可靠的寄到的,而面向鏈接的 TCP 就好比電話,雙方能肯定對方接受到了信息。在本章前面,我們已經對UDP 和 TCP 進行了比較,在這里再稍作小節(jié): TCP,可靠,傳輸大小無限制,但是需要連接建立時間,差錯控制開銷大。 UDP,不可靠,差錯控制開銷較小,傳輸大小限制在 64K 以下,不需要建立連接。 總之,這兩種協(xié)議各有特點,應用的場合也不同,是完全互補的兩個協(xié)議,在 TCP/IP協(xié)議中占有同樣重要的地位,要 學好網(wǎng)絡編程,兩者缺一不可。 8.3.12 Datagram 通訊的表示方法: DatagramSocket; DatagramPacket 包 中提供了兩個類 DatagramSocket 和 DatagramPacket 用來支持數(shù)據(jù)報通信,DatagramSocket 用于在程序之間建立傳送數(shù)據(jù)報的通信連接, DatagramPacket 則用來表示一個數(shù)據(jù)報。先來看一下 DatagramSocket 的構造方法: DatagramSocket(); DatagramSocket( int prot) ; DatagramSocket(int port, InetAddress laddr) 其中, port 指明 socket 所使用的端口號,如果未指明端口號,則把 socket 連接到本地主機上一個可用的端口。 laddr 指明一個可用的本地地址。給出端口號時要保證不發(fā)生端口沖突,否則會生成 SocketException 類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外 SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。 用數(shù)據(jù)報方式編寫 client/server 程序 時,無論在客戶方還是服務方,首先都要建立一個DatagramSocket 對象,用來接收或發(fā)送數(shù)據(jù)報,然后使用 DatagramPacket 類對象作為傳輸數(shù)據(jù)的載體。下面看一下 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 中存放數(shù)據(jù)報數(shù)據(jù), length 為數(shù)據(jù)報中數(shù)據(jù)的長度, addr 和 port 旨明目的地址, offset 指明了數(shù)據(jù)報的位移量。 在接收數(shù)據(jù)前,應該采用上面的第一種方法生成一個 DatagramPacket 對象,給出接收數(shù)據(jù)的緩沖區(qū)及其長度。然后調用 DatagramSocket 的方法 receive()等待數(shù)據(jù)報的到來,receive()將一直等待,直到收到一個數(shù)據(jù)報為止。 DatagramPacket packet=new DatagramPacket(buf, 256); Socket.receive (packet); 發(fā)送數(shù)據(jù)前,也要先生成一個新的 DatagramPacket 對象,這時要使用上面的第二種構造方法,在給出存放發(fā)送數(shù)據(jù)的緩沖區(qū)的同時,還要給出完整的目的地址,包括 IP 地址和端口號。發(fā)送數(shù)據(jù)是通過 DatagramSocket 的方法 send()實現(xiàn)的, send()根據(jù)數(shù)據(jù)報的目的地址來尋徑,以傳遞數(shù)據(jù)報。 DatagramPacket packet=new DatagramPacket(buf, length, address, port); Socket.send(packet); 在構造數(shù)據(jù)報時,要給出 InetAddress 類參數(shù)。類 InetAddress 在包 中定義,用來表示一個 Internet 地址,我們可以通過它提供的類方法 getByName()從一個表示主機名的字符串獲取該主機的 IP 地址,然后再獲取相應的地址信息。 8.3.13 基于 UDP 的簡單的 Client/Server 程序設計 有了上面的知識,我們就可以來構件一個基于 UDP 的 C/S 網(wǎng)絡傳輸模型 1. 客戶方程序 QuoteClient.java import java.io.*; import .*; import java.util.*; public class QuoteClient public static void main(String args) throws IOException if(args.length!=1) /如果啟動的時候沒有給出 Server 的名字,那么出錯退出 System.out.println(Usage:java QuoteClient ); /打印出錯信息 return; /返回 DatagramSocket socket=new DatagramSocklet(); /創(chuàng)建數(shù)據(jù)報套接字 Byte buf=new byte256; /創(chuàng)建緩沖區(qū) InetAddress address=InetAddress.getByName(args 0); /由命令行給出的第一個參數(shù)默認為 Server 的名字,通過它得到 Server 的 IP 信息 DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445); /創(chuàng)建 DatagramPacket 對象 socket.send(packet); /發(fā)送 packet=new DatagramPacket(buf,buf.length); /創(chuàng)建新的 DatagramPacket 對象,用來接收數(shù)據(jù)報 socket.receive(packet); /接收 String received=new String(packet.getData(); /根據(jù)接收到的字節(jié)數(shù)組生成相應的字符串 System.out.println(Quote of the Moment:+received ); /打印生成的字符串 socket.close(); /關閉套接口 2. 服務器方程序 :QuoteServer.java public class QuoteServer public static void main(String args) throws java.io.IOException new QuoteServerThread().start(); /啟動一個 QuoteServerThread 線程 3. 程序 QuoteServerThread.java import java.io.*; import .*; import java.util.*; /服務器線程 public class QuoteServerThread extends Thread protected DatagramSocket socket=null; /記錄和本對象相關聯(lián)的 DatagramSocket 對象 protected BufferedReader in=null; /用來讀文件的一個 Reader protected boolean moreQuotes=true; /標志變量,是否繼續(xù)操作 public QuoteServerThread() throws IOException /無參數(shù)的構造函數(shù) this(QuoteServerThread); /以 QuoteServerThread 為默認值調用帶參數(shù)的構造函數(shù) public QuoteServerThread(String name) throws IOException super(name); /調用父類的構造函數(shù) socket=new DatagramSocket(4445); /在端口 4445創(chuàng)建數(shù)據(jù)報套接字 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 byte256; /創(chuàng)建緩沖區(qū) DatagramPacket packet=new DatagramPacket(buf,buf.length); /由緩沖區(qū)構造 DatagramPacket 對象 socket.receive(packet); /接收數(shù)據(jù)報 String dString=null; if(in= =null) dString=new Date().toString(); /如果初始化的時候打開文件失敗了, /則使用日期作為要傳送的字符串 else dString=getNextQuote(); /否則調用成員函數(shù)從文件中讀出 字符串 buf=dString.getByte(); /把 String 轉換成字節(jié)數(shù)組,以便傳送 InetAddress address=packet.getAddress(); /從 Client 端傳來的 Packet 中得到 Client 地址 int port=packet.getPort(); /和端口號 packet=new DatagramPacket(buf,buf.length,address,port); /根據(jù)客戶端信息構建 DatagramPacket socket.send(packet); /發(fā)送數(shù)據(jù)報 catch(IOException e) /異常處理 e.printStackTrace(); /打印錯誤棧 moreQuotes=false; /標志變量置 false,以結束循環(huán) socket.close(); /關閉數(shù)據(jù)報套接字 protected String getNextQuotes() /成員函數(shù),從文件中讀數(shù)據(jù) String returnValue=null; try if(returnValue=in.readLine()= =null) /從文件中讀一行,如果讀到了文件尾 in.close( ); /關閉輸入流 moreQuotes=false; /標志變量置 false,以結束循環(huán) returnValue=No more quotes. Goodbye.; /置返回值 /否則返回字符串即為從文件讀出的字符串 catch(IOEception e) /異常處理 returnValue=IOException occurred in server; /置異常返回值 return returnValue; /返回字符串 可以看出使用 UDP 和使用 TCP 在程序上還是有很大的區(qū)別的。一個比較明顯的區(qū)別是, UDP 的 Socket編程是不提供監(jiān)聽功能的,也就是說通信雙方更為平等,面對的接口是完全一樣的。但是為了用 UDP 實現(xiàn)C/S 結構,在使用 UDP 時可以使用 DatagramSocket.receive()來實現(xiàn)類似于監(jiān)聽的功能。因為 receive()是阻塞的函數(shù),當它返回時,緩沖區(qū)里已經填滿了接受到的一個數(shù)據(jù)報,并且可以從該數(shù)據(jù)報得到發(fā)送方的各種信息,這一點跟 accept()是很相象的,因而可以根據(jù)讀入的數(shù)據(jù)報來決定下一步的動作,這
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 醫(yī)院檔案室管理制度
- 出版社作者協(xié)議書
- 游戲協(xié)議書是霸王條款
- 品牌旗艦店協(xié)議書
- 2026年財務分析師面試題與標準答案指南
- 2026年從初級到高級財務人員職業(yè)發(fā)展路徑與常見問題解答
- 2026年有色集團電氣工程師崗位技能考試題集含答案
- 2026年客服經理職位面試題及答案
- 2026年黃金集團市場營銷部長面試問題及參考答案
- 2025年廣西北海市政府采購評審專家測試題庫及參考答案
- DL∕T 5343-2018 110kV~750kV架空輸電線路張力架線施工工藝導則
- 房產證授權委托書的模板
- 傳染病防治知識試題庫(共100題)
- 個人信息保護培訓課件
- 理想信念教育勵志類主題班會
- 《建筑基坑降水工程技術規(guī)程》DBT29-229-2014
- 特應性皮炎臨床路徑
- 2024屆重慶外國語學校高一數(shù)學第一學期期末檢測模擬試題含解析
- 2023年廣東學業(yè)水平考試物理??贾R點
- 中山版-四年級第一學期綜合實踐活動教案
- 中外政治思想史-復習資料
評論
0/150
提交評論