国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
JAVA網(wǎng)絡(luò)編程,基本概念與開(kāi)發(fā)
TCP/IP協(xié)議簡(jiǎn)介
8.1.1 網(wǎng)絡(luò)基礎(chǔ)知識(shí)
  計(jì)算機(jī)網(wǎng)絡(luò)形式多樣,內(nèi)容繁雜。網(wǎng)絡(luò)上的計(jì)算機(jī)要互相通信,必須遵循一定的協(xié)議。目前使用最廣泛的網(wǎng)絡(luò)協(xié)議是Internet上所使用的TCP/IP協(xié)議
  網(wǎng)絡(luò)編程的目的就是指直接或間接地通過(guò)網(wǎng)絡(luò)協(xié)議與其他計(jì)算機(jī)進(jìn)行通訊。網(wǎng)絡(luò)編程中有兩個(gè)主要的問(wèn)題,一個(gè)是如何準(zhǔn)確的定位網(wǎng)絡(luò)上一臺(tái)或多臺(tái)主機(jī),另一個(gè)就是找到主機(jī)后如何可靠高效的進(jìn)行數(shù)據(jù)傳輸。在TCP/IP協(xié)議中IP層主要負(fù)責(zé)網(wǎng)絡(luò)主機(jī)的定位,數(shù)據(jù)傳輸?shù)穆酚?,由IP地址可以唯一地確定Internet上的一臺(tái)主機(jī)。而TCP層則提供面向應(yīng)用的可靠的或非可靠的數(shù)據(jù)傳輸機(jī)制,這是網(wǎng)絡(luò)編程的主要對(duì)象,一般不需要關(guān)心IP層是如何處理數(shù)據(jù)的。
 
  目前較為流行的網(wǎng)絡(luò)編程模型是客戶(hù)機(jī)/服務(wù)器(C/S)結(jié)構(gòu)。即通信雙方一方作為服務(wù)器等待客戶(hù)提出請(qǐng)求并予以響應(yīng)??蛻?hù)則在需要服務(wù)時(shí)向服務(wù)器提出申請(qǐng)。服務(wù)器一般作為守護(hù)進(jìn)程始終運(yùn)行,監(jiān)聽(tīng)網(wǎng)絡(luò)端口,一旦有客戶(hù)請(qǐng)求,就會(huì)啟動(dòng)一個(gè)服務(wù)進(jìn)程來(lái)響應(yīng)該客戶(hù),同時(shí)自己繼續(xù)監(jiān)聽(tīng)服務(wù)端口,使后來(lái)的客戶(hù)也能及時(shí)得到服務(wù)。
8.1.2網(wǎng)絡(luò)基本概念
  IP地址:標(biāo)識(shí)計(jì)算機(jī)等網(wǎng)絡(luò)設(shè)備的網(wǎng)絡(luò)地址,由四個(gè)8位的二進(jìn)制數(shù)組成,中間以小數(shù)點(diǎn)分隔。
    如:166.111.136.3 , 166.111.52.80
  主機(jī)名(hostname):網(wǎng)絡(luò)地址的助記名,按照域名進(jìn)行分級(jí)管理。
    如:www.tsinghua.edu.cn
      www.fanso.com
  
  端口號(hào)(port number):網(wǎng)絡(luò)通信時(shí)同一機(jī)器上的不同進(jìn)程的標(biāo)識(shí)。
    如:80,21,23,25,其中1~1024為系統(tǒng)保留的端口號(hào)
  
  服務(wù)類(lèi)型(service):網(wǎng)絡(luò)的各種服務(wù)。
    http, telnet, ftp, smtp
 
  我們可以用以下的一幅圖來(lái)描述這里我們所提到的幾個(gè)概念:
                                
  在Internet上IP地址和主機(jī)名是一一對(duì)應(yīng)的,通過(guò)域名解析可以由主機(jī)名得到機(jī)器的IP,由于機(jī)器名更接近自然語(yǔ)言,容易記憶,所以使用比IP地址廣泛,但是對(duì)機(jī)器而言只有IP地址才是有效的標(biāo)識(shí)符。
  通常一臺(tái)主機(jī)上總是有很多個(gè)進(jìn)程需要網(wǎng)絡(luò)資源進(jìn)行網(wǎng)絡(luò)通訊。網(wǎng)絡(luò)通訊的對(duì)象準(zhǔn)確的講不是主機(jī),而應(yīng)該是主機(jī)中運(yùn)行的進(jìn)程。這時(shí)候光有主機(jī)名或IP地址來(lái)標(biāo)識(shí)這么多個(gè)進(jìn)程顯然是不夠的。端口號(hào)就是為了在一臺(tái)主機(jī)上提供更多的網(wǎng)絡(luò)資源而采取得一種手段,也是TCP層提供的一種機(jī)制。只有通過(guò)主機(jī)名或IP地址和端口號(hào)的組合才能唯一的確定網(wǎng)絡(luò)通訊中的對(duì)象:進(jìn)程。
服務(wù)類(lèi)型是在TCP層上面的應(yīng)用層的概念?;赥CP/IP協(xié)議可以構(gòu)建出各種復(fù)雜的應(yīng)用,服務(wù)類(lèi)型是那些已經(jīng)被標(biāo)準(zhǔn)化了的應(yīng)用,一般都是網(wǎng)絡(luò)服務(wù)器(軟件)。讀者可以編寫(xiě)自己的基于網(wǎng)絡(luò)的服務(wù)器,但都不能被稱(chēng)作標(biāo)準(zhǔn)的服務(wù)類(lèi)型。
8.1.3兩類(lèi)傳輸協(xié)議:TCP;UDP
  盡管TCP/IP協(xié)議的名稱(chēng)中只有TCP這個(gè)協(xié)議名,但是在TCP/IP的傳輸層同時(shí)存在TCP和UDP兩個(gè)協(xié)議。
  TCP是Tranfer Control Protocol的簡(jiǎn)稱(chēng),是一種面向連接的保證可靠傳輸?shù)膮f(xié)議。通過(guò)TCP協(xié)議傳輸,得到的是一個(gè)順序的無(wú)差錯(cuò)的數(shù)據(jù)流。發(fā)送方和接收方的成對(duì)的兩個(gè)socket之間必須建立連接,以便在TCP協(xié)議的基礎(chǔ)上進(jìn)行通信,當(dāng)一個(gè)socket(通常都是server socket)等待建立連接時(shí),另一個(gè)socket可以要求進(jìn)行連接,一旦這兩個(gè)socket連接起來(lái),它們就可以進(jìn)行雙向數(shù)據(jù)傳輸,雙方都可以進(jìn)行發(fā)送或接收操作。
  UDP是User Datagram Protocol的簡(jiǎn)稱(chēng),是一種無(wú)連接的協(xié)議,每個(gè)數(shù)據(jù)報(bào)都是一個(gè)獨(dú)立的信息,包括完整的源地址或目的地址,它在網(wǎng)絡(luò)上以任何可能的路徑傳往目的地,因此能否到達(dá)目的地,到達(dá)目的地的時(shí)間以及內(nèi)容的正確性都是不能被保證的。
  下面我們對(duì)這兩種協(xié)議做簡(jiǎn)單比較:
 
  使用UDP時(shí),每個(gè)數(shù)據(jù)報(bào)中都給出了完整的地址信息,因此無(wú)需要建立發(fā)送方和接收方的連接。對(duì)于TCP協(xié)議,由于它是一個(gè)面向連接的協(xié)議,在socket之間進(jìn)行數(shù)據(jù)傳輸之前必然要建立連接,所以在TCP中多了一個(gè)連接建立的時(shí)間。
  使用UDP傳輸數(shù)據(jù)時(shí)是有大小限制的,每個(gè)被傳輸?shù)臄?shù)據(jù)報(bào)必須限定在64KB之內(nèi)。而TCP沒(méi)有這方面的限制,一旦連接建立起來(lái),雙方的socket就可以按統(tǒng)一的格式傳輸大量的數(shù)據(jù)。UDP是一個(gè)不可靠的協(xié)議,發(fā)送方所發(fā)送的數(shù)據(jù)報(bào)并不一定以相同的次序到達(dá)接收方。而TCP是一個(gè)可靠的協(xié)議,它確保接收方完全正確地獲取發(fā)送方所發(fā)送的全部數(shù)據(jù)。
  總之,TCP在網(wǎng)絡(luò)通信上有極強(qiáng)的生命力,例如遠(yuǎn)程連接(Telnet)和文件傳輸(FTP)都需要不定長(zhǎng)度的數(shù)據(jù)被可靠地傳輸。相比之下UDP操作簡(jiǎn)單,而且僅需要較少的監(jiān)護(hù),因此通常用于局域網(wǎng)高可靠性的分散系統(tǒng)中client/server應(yīng)用程序。
  讀者可能要問(wèn),既然有了保證可靠傳輸?shù)腡CP協(xié)議,為什么還要非可靠傳輸?shù)腢DP協(xié)議呢?主要的原因有兩個(gè)。一是可靠的傳輸是要付出代價(jià)的,對(duì)數(shù)據(jù)內(nèi)容正確性的檢驗(yàn)必然占用計(jì)算機(jī)的處理時(shí)間和網(wǎng)絡(luò)的帶寬,因此TCP傳輸?shù)男什蝗鏤DP高。二是在許多應(yīng)用中并不需要保證嚴(yán)格的傳輸可靠性,比如視頻會(huì)議系統(tǒng),并不要求音頻視頻數(shù)據(jù)絕對(duì)的正確,只要保證連貫性就可以了,這種情況下顯然使用UDP會(huì)更合理一些。
8.2 基于URL的高層次Java網(wǎng)絡(luò)編程
8.2.1一致資源定位器URL
  URL(Uniform Resource Locator)是一致資源定位器的簡(jiǎn)稱(chēng),它表示Internet上某一資源的地址。通過(guò)URL我們可以訪問(wèn)Internet上的各種網(wǎng)絡(luò)資源,比如最常見(jiàn)的WWW,F(xiàn)TP站點(diǎn)。瀏覽器通過(guò)解析給定的URL可以在網(wǎng)絡(luò)上查找相應(yīng)的文件或其他資源。
  URL是最為直觀的一種網(wǎng)絡(luò)定位方法。使用URL符合人們的語(yǔ)言習(xí)慣,容易記憶,所以應(yīng)用十分廣泛。而且在目前使用最為廣泛的TCP/IP中對(duì)于URL中主機(jī)名的解析也是協(xié)議的一個(gè)標(biāo)準(zhǔn),即所謂的域名解析服務(wù)。使用URL進(jìn)行網(wǎng)絡(luò)編程,不需要對(duì)協(xié)議本身有太多的了解,功能也比較弱,相對(duì)而言是比較簡(jiǎn)單的,所以在這里我們先介紹在Java中如何使用URL進(jìn)行網(wǎng)絡(luò)編程來(lái)引導(dǎo)讀者入門(mén)。
8.2.2 URL的組成
  protocol://resourceName
  協(xié)議名(protocol)指明獲取資源所使用的傳輸協(xié)議,如http、ftp、gopher、file等,資源名(resourceName)則應(yīng)該是資源的完整地址,包括主機(jī)名、端口號(hào)、文件名或文件內(nèi)部的一個(gè)引用。例如:
  http://www.sun.com/ 協(xié)議名://主機(jī)名
  http://home.netscape.com/home/welcome.html 協(xié)議名://機(jī)器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 協(xié)議名://機(jī)器名+端口號(hào)+文件名+內(nèi)部引用
  
  端口號(hào)是和Socket編程相關(guān)的一個(gè)概念,初學(xué)者不必在此深究,在后面會(huì)有詳細(xì)講解。內(nèi)部引用是HTML中的標(biāo)記,有興趣的讀者可以參考有關(guān)HTML的書(shū)籍。
8.2.3 創(chuàng)建一個(gè)URL
  為了表示URL, java.net中實(shí)現(xiàn)了類(lèi)URL。我們可以通過(guò)下面的構(gòu)造方法來(lái)初始化一個(gè)URL對(duì)象:
  (1) public URL (String spec);
     通過(guò)一個(gè)表示URL地址的字符串可以構(gòu)造一個(gè)URL對(duì)象。
     URL urlBase=new URL("http://www. 263.net/")
 ?。?) public URL(URL context, String spec);
     通過(guò)基URL和相對(duì)URL構(gòu)造一個(gè)URL對(duì)象。
     URL net263=new URL ("     URL index263=new URL(net263, "index.html")
 ?。?) public URL(String protocol, String host, String file);
     new URL("http", "
 ?。?) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "
  注意:類(lèi)URL的構(gòu)造方法都聲明拋棄非運(yùn)行時(shí)例外(MalformedURLException),因此生成URL對(duì)象時(shí),我們必須要對(duì)這一例外進(jìn)行處理,通常是用try-catch語(yǔ)句進(jìn)行捕獲。格式如下:

  try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
  …
  //exception handler code here
  …
  }
 
8.2.4 解析一個(gè)URL
  一個(gè)URL對(duì)象生成后,其屬性是不能被改變的,但是我們可以通過(guò)類(lèi)URL所提供的方法來(lái)獲取這些屬性:
   public String getProtocol() 獲取該URL的協(xié)議名。
   public String getHost() 獲取該URL的主機(jī)名。
   public int getPort() 獲取該URL的端口號(hào),如果沒(méi)有設(shè)置端口,返回-1。
   public String getFile() 獲取該URL的文件名。
   public String getRef() 獲取該URL在文件中的相對(duì)位置。
   public String getQuery() 獲取該URL的查詢(xún)信息。
   public String getPath() 獲取該URL的路徑
   public String getAuthority() 獲取該URL的權(quán)限信息
   public String getUserInfo() 獲得使用者的信息
   public String getRef() 獲得該URL的錨

  下面的例子中,我們生成一個(gè)URL對(duì)象,并獲取它的各個(gè)屬性。
  import java.net.*;
  import java.io.*;
  public class ParseURL{
  public static void main (String [] args) throws Exception{

  在傳統(tǒng)的UNIX環(huán)境下可以操作TCP/IP協(xié)議的接口不止Socket一個(gè),Socket所支持的協(xié)議種類(lèi)也不光TCP/IP一種,因此兩者之間是沒(méi)有必然聯(lián)系的。在Java環(huán)境下,Socket編程主要是指基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程。

  說(shuō)Socket編程是低層次網(wǎng)絡(luò)編程并不等于它功能不強(qiáng)大,恰恰相反,正因?yàn)閷哟蔚?,Socket編程比基于URL的網(wǎng)絡(luò)編程提供了更強(qiáng)大的功能和更靈活的控制,但是卻要更復(fù)雜一些。由于Java本身的特殊性,Socket編程在Java中可能已經(jīng)是層次最低的網(wǎng)絡(luò)編程接口,在Java中要直接操作協(xié)議中更低的層次,需要使用Java的本地方法調(diào)用(JNI),在這里就不予討論了。

8.3.2 Socket通訊的一般過(guò)

  前面已經(jīng)提到Socket通常用來(lái)實(shí)現(xiàn)C/S結(jié)構(gòu)。

  使用Socket進(jìn)行Client/Server程序設(shè)計(jì)的一般連接過(guò)程是這樣的:Server端Listen(監(jiān)聽(tīng))某個(gè)端口是否有連接請(qǐng)求,Client端向Server端發(fā)出Connect(連接)請(qǐng)求,Server端向Client端發(fā)回Accept(接受)消息。一個(gè)連接就建立起來(lái)了。Server端和Client端都可以通過(guò)Send,Write等方法與對(duì)方通信。       
  對(duì)于一個(gè)功能齊全的Socket,都要包含以下基本結(jié)構(gòu),其工作過(guò)程包含以下四個(gè)基本的步驟:
 ?。?) 創(chuàng)建Socket;
 ?。?) 打開(kāi)連接到Socket的輸入/出流;
 ?。?) 按照一定的協(xié)議對(duì)Socket進(jìn)行讀/寫(xiě)操作;
 ?。?) 關(guān)閉Socket.

  其中address、host和port分別是雙向連接中另一方的IP地址、主機(jī)名和端口號(hào),stream指明socket是流socket還是數(shù)據(jù)報(bào)socket,localPort表示本地主機(jī)的端口號(hào),localAddr和bindAddr是本地機(jī)器的地址(ServerSocket的主機(jī)地址),impl是socket的父類(lèi),既可以用來(lái)創(chuàng)建serverSocket又可以用來(lái)創(chuàng)建Socket。count則表示服務(wù)端所能支持的最大連接數(shù)。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);

  注意,在選擇端口時(shí),必須小心。每一個(gè)端口提供一種特定的服務(wù),只有給出正確的端口,才能獲得相應(yīng)的服務(wù)。0~1023的端口號(hào)為系統(tǒng)所保留,例如http服務(wù)的端口號(hào)為80,telnet服務(wù)的端口號(hào)為21,ftp服務(wù)的端口號(hào)為23, 所以我們?cè)谶x擇端口號(hào)時(shí),最好選擇一個(gè)大于1023的數(shù)以防止發(fā)生沖突。

  在創(chuàng)建socket時(shí)如果發(fā)生錯(cuò)誤,將產(chǎn)生IOException,在程序中必須對(duì)之作出處理。所以在創(chuàng)建Socket或ServerSocket是必須捕獲或拋出例外。

8.3.4 客戶(hù)端的Socket

  下面是一個(gè)典型的創(chuàng)建客戶(hù)端Socket的過(guò)程。
   try{
     Socket socket=new Socket("127.0.0.1",4700);
     //127.0.0.1是TCP/IP協(xié)議中默認(rèn)的本機(jī)地址
   }catch(IOException e){
     System.out.println("Error:"+e);
   }

  以上的程序是Server的典型工作模式,只不過(guò)在這里Server只能接收一個(gè)請(qǐng)求,接受完后Server就退出了。實(shí)際的應(yīng)用中總是讓它不停的循環(huán)接收,一旦有客戶(hù)請(qǐng)求,Server總是會(huì)創(chuàng)建一個(gè)服務(wù)線(xiàn)程來(lái)服務(wù)新來(lái)的客戶(hù),而自己繼續(xù)監(jiān)聽(tīng)。程序中accept()是一個(gè)阻塞函數(shù),所謂阻塞性方法就是說(shuō)該方法被調(diào)用后,將等待客戶(hù)的請(qǐng)求,直到有一個(gè)客戶(hù)啟動(dòng)并請(qǐng)求連接到相同的端口,然后accept()返回一個(gè)對(duì)應(yīng)于客戶(hù)的socket。這時(shí),客戶(hù)方和服務(wù)方都建立了用于通信的socket,接下來(lái)就是由各個(gè)socket分別打開(kāi)各自的輸入/輸出流。

  例如:
  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)絡(luò)編程的實(shí)質(zhì)性部分,具體如何構(gòu)造所需要的過(guò)濾流,要根據(jù)需要而定,能否運(yùn)用自如主要看讀者對(duì)Java中輸入輸出部分掌握如何。

  盡管Java有自動(dòng)回收機(jī)制,網(wǎng)絡(luò)資源最終是會(huì)被釋放的。但是為了有效的利用資源,建議讀者按照合理的順序主動(dòng)釋放資源。

 1. 客戶(hù)端程序
  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);
        //向本機(jī)的4700端口發(fā)出客戶(hù)請(qǐng)求
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
        //由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對(duì)象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        //由Socket對(duì)象得到輸出流,并構(gòu)造PrintWriter對(duì)象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //由Socket對(duì)象得到輸入流,并構(gòu)造相應(yīng)的BufferedReader對(duì)象
        String readline;
        readline=sin.readLine(); //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串
        while(!readline.equals("bye")){
        //若從標(biāo)準(zhǔn)輸入讀入的字符串為 "bye"則停止循環(huán)
          os.println(readline);
          //將從系統(tǒng)標(biāo)準(zhǔn)輸入讀入的字符串輸出到Server
          os.flush();
          //刷新輸出流,使Server馬上收到該字符串
          System.out.println("Client:"+readline);
          //在系統(tǒng)標(biāo)準(zhǔn)輸出上打印讀入的字符串
          System.out.println("Server:"+is.readLine());
          //從Server讀入一字符串,并打印到標(biāo)準(zhǔn)輸出上
          readline=sin.readLine(); //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串
        } //繼續(xù)循環(huán)
        os.close(); //關(guān)閉Socket輸出流
        is.close(); //關(guān)閉Socket輸入流
        socket.close(); //關(guān)閉Socket
      }catch(Exception e) {
        System.out.println("Error"+e); //出錯(cuò),則打印出錯(cuò)信息
      }
  }
}

        Socket socket=null;
        try{
          socket=server.accept();
          //使用accept()阻塞等待客戶(hù)請(qǐng)求,有客戶(hù)
          //請(qǐng)求到來(lái)則產(chǎn)生一個(gè)Socket對(duì)象,并繼續(xù)執(zhí)行
        }catch(Exception e) {
          System.out.println("Error."+e);
          //出錯(cuò),打印出錯(cuò)信息
        }
        String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         //由Socket對(duì)象得到輸入流,并構(gòu)造相應(yīng)的BufferedReader對(duì)象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         //由Socket對(duì)象得到輸出流,并構(gòu)造PrintWriter對(duì)象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         //由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對(duì)象

        System.out.println("Client:"+is.readLine());
        //在標(biāo)準(zhǔn)輸出上打印從客戶(hù)端讀入的字符串
        line=sin.readLine();
        //從標(biāo)準(zhǔn)輸入讀入一字符串
        while(!line.equals("bye")){
        //如果該字符串為 "bye",則停止循環(huán)
          os.println(line);
          //向客戶(hù)端輸出該字符串
          os.flush();
          //刷新輸出流,使Client馬上收到該字符串
          System.out.println("Server:"+line);
          //在系統(tǒng)標(biāo)準(zhǔn)輸出上打印讀入的字符串
          System.out.println("Client:"+is.readLine());
          //從Client讀入一字符串,并打印到標(biāo)準(zhǔn)輸出上
          line=sin.readLine();
          //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串
        }  //繼續(xù)循環(huán)
        os.close(); //關(guān)閉Socket輸出流
        is.close(); //關(guān)閉Socket輸入流
        socket.close(); //關(guān)閉Socket
        server.close(); //關(guān)閉ServerSocket
      }catch(Exception e){
        System.out.println("Error:"+e);
        //出錯(cuò),打印出錯(cuò)信息
      }
    }
  }

  從上面的兩個(gè)程序中我們可以看到,socket四個(gè)步驟的使用過(guò)程。讀者可以分別將Socket使用的四個(gè)步驟的對(duì)應(yīng)程序段選擇出來(lái),這樣便于讀者對(duì)socket的使用有進(jìn)一步的了解。

  讀者可以在單機(jī)上試驗(yàn)該程序,最好是能在真正的網(wǎng)絡(luò)環(huán)境下試驗(yàn)該程序,這樣更容易分辨輸出的內(nèi)容和客戶(hù)機(jī),服務(wù)器的對(duì)應(yīng)關(guān)系。同時(shí)也可以修改該程序,提供更為強(qiáng)大的功能,或更加滿(mǎn)足讀者的意圖。 

8.3.9 支持多客戶(hù)的client/server程序設(shè)計(jì)

  前面提供的Client/Server程序只能實(shí)現(xiàn)Server和一個(gè)客戶(hù)的對(duì)話(huà)。在實(shí)際應(yīng)用中,往往是在服務(wù)器上運(yùn)行一個(gè)永久的程序,它可以接收來(lái)自其他多個(gè)客戶(hù)端的請(qǐng)求,提供相應(yīng)的服務(wù)。為了實(shí)現(xiàn)在服務(wù)器方給多個(gè)客戶(hù)提供服務(wù)的功能,需要對(duì)上面的程序進(jìn)行改造,利用多線(xiàn)程實(shí)現(xiàn)多客戶(hù)機(jī)制。服務(wù)器總是在指定的端口上監(jiān)聽(tīng)是否有客戶(hù)請(qǐng)求,一旦監(jiān)聽(tīng)到客戶(hù)請(qǐng)求,服務(wù)器就會(huì)啟動(dòng)一個(gè)專(zhuān)門(mén)的服務(wù)線(xiàn)程來(lái)響應(yīng)該客戶(hù)的請(qǐng)求,而服務(wù)器本身在啟動(dòng)完線(xiàn)程之后馬上又進(jìn)入監(jiān)聽(tīng)狀態(tài),等待下一個(gè)客戶(hù)的到來(lái)。

  客戶(hù)端的程序和上面程序是完全一樣的,讀者如果仔細(xì)閱讀過(guò)上面的程序,可以跳過(guò)不讀,把主要精力集中在Server端的程序上。

 2. 服務(wù)器端程序: MultiTalkServer.java
  import java.io.*;
  import java.net.*;
  import ServerThread;
  public class MultiTalkServer{
   static int clientnum=0; //靜態(tài)成員變量,記錄當(dāng)前客戶(hù)的個(gè)數(shù)
   public static void main(String args[]) throws IOException {
    ServerSocket serverSocket=null;
    boolean listening=true;
    try{
      serverSocket=new ServerSocket(4700);
      //創(chuàng)建一個(gè)ServerSocket在端口4700監(jiān)聽(tīng)客戶(hù)請(qǐng)求
    }catch(IOException e) {
      System.out.println("Could not listen on port:4700.");
      //出錯(cuò),打印出錯(cuò)信息
      System.exit(-1); //退出
    }
    while(listening){ //永遠(yuǎn)循環(huán)監(jiān)聽(tīng)
      new ServerThread(serverSocket.accept(),clientnum).start();
      //監(jiān)聽(tīng)到客戶(hù)請(qǐng)求,根據(jù)得到的Socket對(duì)象和
       客戶(hù)計(jì)數(shù)創(chuàng)建服務(wù)線(xiàn)程,并啟動(dòng)之
      clientnum++; //增加客戶(hù)計(jì)數(shù)
    }
    serverSocket.close(); //關(guān)閉ServerSocket
  }
}
 3. 程序ServerThread.java
  import java.io.*;
  import java.net.*;
  public class ServerThread extends Thread{
   Socket socket=null; //保存與本線(xiàn)程相關(guān)的Socket對(duì)象
   int clientnum; //保存本進(jìn)程的客戶(hù)計(jì)數(shù)
   public ServerThread(Socket socket,int num) { //構(gòu)造函數(shù)
    this.socket=socket; //初始化socket變量
    clientnum=num+1; //初始化clientnum變量
   }
   public void run() { //線(xiàn)程主體
    try{
      String line;
      BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  //由Socket對(duì)象得到輸入流,并構(gòu)造相應(yīng)的BufferedReader對(duì)象
      PrintWriter os=newPrintWriter(socket.getOutputStream());
      //由Socket對(duì)象得到輸出流,并構(gòu)造PrintWriter對(duì)象
      BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
      //由系統(tǒng)標(biāo)準(zhǔn)輸入設(shè)備構(gòu)造BufferedReader對(duì)象
      System.out.println("Client:"+ clientnum +is.readLine());
      //在標(biāo)準(zhǔn)輸出上打印從客戶(hù)端讀入的字符串
      line=sin.readLine();
      //從標(biāo)準(zhǔn)輸入讀入一字符串
      while(!line.equals("bye")){
      //如果該字符串為 "bye",則停止循環(huán)
        os.println(line);
        //向客戶(hù)端輸出該字符串
        os.flush();
        //刷新輸出流,使Client馬上收到該字符串
        System.out.println("Server:"+line);
        //在系統(tǒng)標(biāo)準(zhǔn)輸出上打印該字符串
        System.out.println("Client:"+ clientnum +is.readLine());
        //從Client讀入一字符串,并打印到標(biāo)準(zhǔn)輸出上
        line=sin.readLine();
        //從系統(tǒng)標(biāo)準(zhǔn)輸入讀入一字符串
      } //繼續(xù)循環(huán)
      os.close(); //關(guān)閉Socket輸出流
      is.close(); //關(guān)閉Socket輸入流
      socket.close(); //關(guān)閉Socket
      server.close(); //關(guān)閉ServerSocket
     }catch(Exception e){
      System.out.println("Error:"+e);
      //出錯(cuò),打印出錯(cuò)信息
     }
   }
 }
  這個(gè)程序向讀者展示了網(wǎng)絡(luò)應(yīng)用中最為典型的C/S結(jié)構(gòu),我們可以用下面的圖來(lái)描述這樣一種模型:

8.3.10數(shù)據(jù)報(bào)Datagram通訊

  前面在介紹TCP/IP協(xié)議的時(shí)候,我們已經(jīng)提到,在TCP/IP協(xié)議的傳輸層除了TCP協(xié)議之外還有一個(gè)UDP協(xié)議,相比而言UDP的應(yīng)用不如TCP廣泛,幾個(gè)標(biāo)準(zhǔn)的應(yīng)用層協(xié)議HTTP,F(xiàn)TP,SMTP…使用的都是TCP協(xié)議。但是,隨著計(jì)算機(jī)網(wǎng)絡(luò)的發(fā)展,UDP協(xié)議正越來(lái)越來(lái)顯示出其威力,尤其是在需要很強(qiáng)的實(shí)時(shí)交互性的場(chǎng)合,如網(wǎng)絡(luò)游戲,視頻會(huì)議等,UDP更是顯示出極強(qiáng)的威力,下面我們就介紹一下Java環(huán)境下如何實(shí)現(xiàn)UDP網(wǎng)絡(luò)傳輸。

8.3.11 什么是Datagram

  所謂數(shù)據(jù)報(bào)(Datagram)就跟日常生活中的郵件系統(tǒng)一樣,是不能保證可靠的寄到的,而面向鏈接的TCP就好比電話(huà),雙方能肯定對(duì)方接受到了信息。在本章前面,我們已經(jīng)對(duì)UDP和TCP進(jìn)行了比較,在這里再稍作小節(jié):

  TCP,可靠,傳輸大小無(wú)限制,但是需要連接建立時(shí)間,差錯(cuò)控制開(kāi)銷(xiāo)大。
  UDP,不可靠,差錯(cuò)控制開(kāi)銷(xiāo)較小,傳輸大小限制在64K以下,不需要建立連接。

  總之,這兩種協(xié)議各有特點(diǎn),應(yīng)用的場(chǎng)合也不同,是完全互補(bǔ)的兩個(gè)協(xié)議,在TCP/IP協(xié)議中占有同樣重要的地位,要學(xué)好網(wǎng)絡(luò)編程,兩者缺一不可。

8.3.12 Datagram通訊的表示方法:DatagramSocket;DatagramPacket

  包java.net中提供了兩個(gè)類(lèi)DatagramSocket和DatagramPacket用來(lái)支持?jǐn)?shù)據(jù)報(bào)通信,DatagramSocket用于在程序之間建立傳送數(shù)據(jù)報(bào)的通信連接, DatagramPacket則用來(lái)表示一個(gè)數(shù)據(jù)報(bào)。先來(lái)看一下DatagramSocket的構(gòu)造方法:
   DatagramSocket();
   DatagramSocket(int prot);
   DatagramSocket(int port, InetAddress laddr)
  
  其中,port指明socket所使用的端口號(hào),如果未指明端口號(hào),則把socket連接到本地主機(jī)上一個(gè)可用的端口。laddr指明一個(gè)可用的本地地址。給出端口號(hào)時(shí)要保證不發(fā)生端口沖突,否則會(huì)生成SocketException類(lèi)例外。注意:上述的兩個(gè)構(gòu)造方法都聲明拋棄非運(yùn)行時(shí)例外SocketException,程序中必須進(jìn)行處理,或者捕獲、或者聲明拋棄。

用數(shù)據(jù)報(bào)方式編寫(xiě)client/server程序時(shí),無(wú)論在客戶(hù)方還是服務(wù)方,首先都要建立一個(gè)DatagramSocket對(duì)象,用來(lái)接收或發(fā)送數(shù)據(jù)報(bào),然后使用DatagramPacket類(lèi)對(duì)象作為傳輸數(shù)據(jù)的載體。下面看一下DatagramPacket的構(gòu)造方法 :
   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ù)報(bào)數(shù)據(jù),length為數(shù)據(jù)報(bào)中數(shù)據(jù)的長(zhǎng)度,addr和port旨明目的地址,offset指明了數(shù)據(jù)報(bào)的位移量。

  在接收數(shù)據(jù)前,應(yīng)該采用上面的第一種方法生成一個(gè)DatagramPacket對(duì)象,給出接收數(shù)據(jù)的緩沖區(qū)及其長(zhǎng)度。然后調(diào)用DatagramSocket 的方法receive()等待數(shù)據(jù)報(bào)的到來(lái),receive()將一直等待,直到收到一個(gè)數(shù)據(jù)報(bào)為止。
  DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);

  發(fā)送數(shù)據(jù)前,也要先生成一個(gè)新的DatagramPacket對(duì)象,這時(shí)要使用上面的第二種構(gòu)造方法,在給出存放發(fā)送數(shù)據(jù)的緩沖區(qū)的同時(shí),還要給出完整的目的地址,包括IP地址和端口號(hào)。發(fā)送數(shù)據(jù)是通過(guò)DatagramSocket的方法send()實(shí)現(xiàn)的,send()根據(jù)數(shù)據(jù)報(bào)的目的地址來(lái)尋徑,以傳遞數(shù)據(jù)報(bào)。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet);

在構(gòu)造數(shù)據(jù)報(bào)時(shí),要給出InetAddress類(lèi)參數(shù)。類(lèi)InetAddress在包java.net中定義,用來(lái)表示一個(gè)Internet地址,我們可以通過(guò)它提供的類(lèi)方法getByName()從一個(gè)表示主機(jī)名的字符串獲取該主機(jī)的IP地址,然后再獲取相應(yīng)的地址信息。

    DatagramSocket socket=new DatagramSocklet();
    //創(chuàng)建數(shù)據(jù)報(bào)套接字

    Byte[] buf=new byte[256]; //創(chuàng)建緩沖區(qū)
    InetAddress address=InetAddress.getByName(args [0]);
//由命令行給出的第一個(gè)參數(shù)默認(rèn)為Server的名字,通過(guò)它得到Server的IP信息
    DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445);
    //創(chuàng)建DatagramPacket對(duì)象
    socket.send(packet); //發(fā)送
    packet=new DatagramPacket(buf,buf.length);
    //創(chuàng)建新的DatagramPacket對(duì)象,用來(lái)接收數(shù)據(jù)報(bào)
    socket.receive(packet); //接收
    String received=new String(packet.getData());
    //根據(jù)接收到的字節(jié)數(shù)組生成相應(yīng)的字符串
    System.out.println("Quote of the Moment:"+received );
    //打印生成的字符串

    socket.close(); //關(guān)閉套接口
   }
 }

  public QuoteServerThread() throws IOException {
  //無(wú)參數(shù)的構(gòu)造函數(shù)
    this("QuoteServerThread");
    //以QuoteServerThread為默認(rèn)值調(diào)用帶參數(shù)的構(gòu)造函數(shù)
  }
  public QuoteServerThread(String name) throws IOException {
    super(name); //調(diào)用父類(lèi)的構(gòu)造函數(shù)
    socket=new DatagramSocket(4445);
    //在端口4445創(chuàng)建數(shù)據(jù)報(bào)套接字
    try{
      in= new BufferedReader(new FileReader(" one-liners.txt"));
      //打開(kāi)一個(gè)文件,構(gòu)造相應(yīng)的BufferReader對(duì)象
    }catch(FileNotFoundException e) { //異常處理
      System.err.println("Could not open quote file. Serving time instead.");
       //打印出錯(cuò)信息
    }
  }
  public void run() //線(xiàn)程主體
  {
    while(moreQuotes) {
     try{
       byte[] buf=new byte[256]; //創(chuàng)建緩沖區(qū)
       DatagramPacket packet=new DatagramPacket(buf,buf.length);
       //由緩沖區(qū)構(gòu)造DatagramPacket對(duì)象
       socket.receive(packet); //接收數(shù)據(jù)報(bào)
       String dString=null;
       if(in= =null) dString=new Date().toString();
       //如果初始化的時(shí)候打開(kāi)文件失敗了,
       //則使用日期作為要傳送的字符串
       else dString=getNextQuote();
       //否則調(diào)用成員函數(shù)從文件中讀出字符串
       buf=dString.getByte();
       //把String轉(zhuǎn)換成字節(jié)數(shù)組,以便傳送

       InetAddress address=packet.getAddress();
       //從Client端傳來(lái)的Packet中得到Client地址
       int port=packet.getPort(); //和端口號(hào)
       packet=new DatagramPacket(buf,buf.length,address,port);
       //根據(jù)客戶(hù)端信息構(gòu)建DatagramPacket
       socket.send(packet); //發(fā)送數(shù)據(jù)報(bào)
      }catch(IOException e) { //異常處理
       e.printStackTrace(); //打印錯(cuò)誤棧
       moreQuotes=false; //標(biāo)志變量置false,以結(jié)束循環(huán)
      }
    }
    socket.close(); //關(guān)閉數(shù)據(jù)報(bào)套接字
  }

  protected String getNextQuotes(){
  //成員函數(shù),從文件中讀數(shù)據(jù)
    String returnValue=null;
    try {
       if((returnValue=in.readLine())= =null) {
       //從文件中讀一行,如果讀到了文件尾
       in.close( ); //關(guān)閉輸入流
       moreQuotes=false;
       //標(biāo)志變量置false,以結(jié)束循環(huán)
       returnValue="No more quotes. Goodbye.";
       //置返回值
       } //否則返回字符串即為從文件讀出的字符串
    }catch(IOEception e) { //異常處理
       returnValue="IOException occurred in server";
       //置異常返回值
    }
    return returnValue; //返回字符串
  }
}

  可以看出使用UDP和使用TCP在程序上還是有很大的區(qū)別的。一個(gè)比較明顯的區(qū)別是,UDP的Socket編程是不提供監(jiān)聽(tīng)功能的,也就是說(shuō)通信雙方更為平等,面對(duì)的接口是完全一樣的。但是為了用UDP實(shí)現(xiàn)C/S結(jié)構(gòu),在使用UDP時(shí)可以使用DatagramSocket.receive()來(lái)實(shí)現(xiàn)類(lèi)似于監(jiān)聽(tīng)的功能。因?yàn)閞eceive()是阻塞的函數(shù),當(dāng)它返回時(shí),緩沖區(qū)里已經(jīng)填滿(mǎn)了接受到的一個(gè)數(shù)據(jù)報(bào),并且可以從該數(shù)據(jù)報(bào)得到發(fā)送方的各種信息,這一點(diǎn)跟accept()是很相象的,因而可以根據(jù)讀入的數(shù)據(jù)報(bào)來(lái)決定下一步的動(dòng)作,這就達(dá)到了跟網(wǎng)絡(luò)監(jiān)聽(tīng)相似的效果。 

8.3.14 用數(shù)據(jù)報(bào)進(jìn)行廣播通訊

  DatagramSocket只允許數(shù)據(jù)報(bào)發(fā)送一個(gè)目的地址,java.net包中提供了一個(gè)類(lèi)MulticastSocket,允許數(shù)據(jù)報(bào)以廣播方式發(fā)送到該端口的所有客戶(hù)。MulticastSocket用在客戶(hù)端,監(jiān)聽(tīng)服務(wù)器廣播來(lái)的數(shù)據(jù)。

  我們對(duì)上面的程序作一些修改,利用MulticastSocket實(shí)現(xiàn)廣播通信。新程序完成的功能是使同時(shí)運(yùn)行的多個(gè)客戶(hù)程序能夠接收到服務(wù)器發(fā)送來(lái)的相同的信息,顯示在各自的屏幕上。

     for(int i=0;i<5;i++) {
       byte[] buf=new byte[256];
       //創(chuàng)建緩沖區(qū)
       packet=new DatagramPacket(buf,buf.length);
       //創(chuàng)建接收數(shù)據(jù)報(bào)
       socket.receive(packet); //接收
       String received=new String(packet.getData());
       //由接收到的數(shù)據(jù)報(bào)得到字節(jié)數(shù)組,
       //并由此構(gòu)造一個(gè)String對(duì)象
       System.out.println("Quote of theMoment:"+received);
       //打印得到的字符串
     } //循環(huán)5次
     socket.leaveGroup(address);
     //把廣播套接字從地址上解除綁定
     socket.close(); //關(guān)閉廣播套接字
   }
 }

    public void run() //重寫(xiě)父類(lèi)的線(xiàn)程主體
    {
     while(moreQuotes) {
     //根據(jù)標(biāo)志變量判斷是否繼續(xù)循環(huán)
      try{
        byte[] buf=new byte[256];
        //創(chuàng)建緩沖區(qū)
        String dString=null;
        if(in==null) dString=new Date().toString();
        //如果初始化的時(shí)候打開(kāi)文件失敗了,
        //則使用日期作為要傳送的字符串
        else dString=getNextQuote();
        //否則調(diào)用成員函數(shù)從文件中讀出字符串
        buf=dString.getByte();
        //把String轉(zhuǎn)換成字節(jié)數(shù)組,以便傳送send it
        InetAddress group=InetAddress.getByName("230.0.0.1");
        //得到230.0.0.1的地址信息
        DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
        //根據(jù)緩沖區(qū),廣播地址,和端口號(hào)創(chuàng)建DatagramPacket對(duì)象
        socket.send(packet); //發(fā)送該P(yáng)acket
        try{
          sleep((long)(Math.random()*FIVE_SECONDS));
          //隨機(jī)等待一段時(shí)間,0~5秒之間
        }catch(InterruptedException e) { } //異常處理
      }catch(IOException e){ //異常處理
        e.printStackTrace( ); //打印錯(cuò)誤棧

        moreQuotes=false; //置結(jié)束循環(huán)標(biāo)志
      }
    }
    socket.close( ); //關(guān)閉廣播套接口
   }
 }

  至此,Java網(wǎng)絡(luò)編程這一章已經(jīng)講解完畢。讀者通過(guò)學(xué)習(xí),應(yīng)該對(duì)網(wǎng)絡(luò)編程有了一個(gè)清晰的認(rèn)識(shí),可能對(duì)某些概念還不是十分的清楚,還是需要更多的實(shí)踐來(lái)進(jìn)一步掌握。編程語(yǔ)言的學(xué)習(xí)不同于一般的學(xué)習(xí),及其強(qiáng)調(diào)實(shí)踐的重要性。讀者應(yīng)該對(duì)URL網(wǎng)絡(luò)編程,Socket中的TCP,UDP編程進(jìn)行大量的練習(xí)才能更好的掌握本章中所提到的一些概念,才能真正學(xué)到Java網(wǎng)絡(luò)編程的精髓!

  最后幾個(gè)小節(jié)所舉的例子,讀者務(wù)必要親自試驗(yàn)一下,如果遇到問(wèn)題,想辦法解決之。最好能根據(jù)自己的意圖加以改進(jìn)。這樣才能更好的理解這幾個(gè)程序,理解其中所包含的編程思想。

  后續(xù)的內(nèi)容分為兩大塊,一塊是以URL為主線(xiàn),講解如何通過(guò)URL類(lèi)和URLConnection類(lèi)訪問(wèn)WWW網(wǎng)絡(luò)資源,由于使用URL十分方便直觀,盡管功能不是很強(qiáng),還是值得推薦的一種網(wǎng)絡(luò)編程方法,尤其是對(duì)于初學(xué)者特別容易接受。本質(zhì)上講,URL網(wǎng)絡(luò)編程在傳輸層使用的還是TCP協(xié)議。

  另一塊是以Socket接口和C/S網(wǎng)絡(luò)編程模型為主線(xiàn),依次講解了如何用Java實(shí)現(xiàn)基于TCP的C/S結(jié)構(gòu),主要用到的類(lèi)有Socket,ServerSocket。以及如何用Java實(shí)現(xiàn)基于UDP的C/S結(jié)構(gòu),還討論了一種特殊的傳輸方式,廣播方式,這種方式是UDP所特有的,主要用到的類(lèi)有DatagramSocket , DatagramPacket, MulticastSocket。這一塊在Java網(wǎng)絡(luò)編程中相對(duì)而言是最難的(盡管Java在網(wǎng)絡(luò)編程這方面已經(jīng)做的夠"傻瓜"了,但是網(wǎng)絡(luò)編程在其他環(huán)境下的卻是一件極為頭痛的事情,再"傻瓜"還是有一定的難度),也是功能最為強(qiáng)大的一部分,讀者應(yīng)該好好研究,領(lǐng)悟其中的思想。

  最后要強(qiáng)調(diào)的是要學(xué)好Java網(wǎng)絡(luò)編程,Java語(yǔ)言,最重要的還是在于多多練習(xí)!

打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服