隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0

          導航

          <2009年7月>
          2829301234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678

          公告

          關注我的新浪微博

          我的著作









          常用鏈接

          留言簿(126)

          我參與的團隊

          隨筆分類(818)

          隨筆檔案(310)

          文章分類(1)

          文章檔案(8)

          相冊

          ADSL、3G查詢

          CSDN

          eclipse

          ibm

          Java EE

          Linux

          Web

          云服務

          代理網站

          關注的網站

          協議

          喜歡的Blog

          國內廣告平臺

          圖書出版

          在線培訓

          開發工具

          微博客戶端

          手機鈴聲

          操作系統

          • ReactOS
          • 一個與windowXP/2003兼容的操作系統

          數學

          文件格式

          源碼資源

          移動(Mobile)

          編程語言

          英語學習

          最新隨筆

          搜索

          •  

          積分與排名

          • 積分 - 1972189
          • 排名 - 6

          最新評論

          閱讀排行榜

          評論排行榜

          Java網絡編程從入門到精通(25):創建ServerSocket對象

          本文為原創,如需轉載,請注明作者和出處,謝謝!

          上一篇:Java網絡編程從入門到精通(24):實現HTTP斷點續傳下載工具(附源代碼)

          ServerSocket類的構造方法有四種重載形式,它們的定義如下:

          public ServerSocket() throws IOException
          public ServerSocket(int port) throws IOException
          public ServerSocket(int port, int backlog) throws IOException
          public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

              在上面的構造方法中涉及到了三個參數:portbacklogbindAddr。其中portServerSocket對象要綁定的端口,backlog是請求隊列的長度,bindAddrServerSocket對象要綁定的IP地址。

          一、通過構造方法綁定端口

          通過構造方法綁定端口是創建ServerSocket對象最常用的方式。可以通過如下的構造方法來綁定端口:

          public ServerSocket(int port) throws IOException

          如果port參數所指定的端口已經被綁定,構造方法就會拋出IOException異常。但實際上拋出的異常是BindException。從圖4.2異常類繼承關系圖可以看出,所有和網絡有關的異常都是IOException類的子類。因此,為了ServerSocket構造方法還可以拋出其他的異常,就使用了IOException

          如果port的值為0,系統就會隨機選取一個端口號。但隨機選取的端口意義不大,因為客戶端在連接服務器時需要明確知道服務端程序的端口號。可以通過ServerSockettoString方法輸出和ServerSocket對象相關的信息。下面的代碼輸入了和ServerSocket對象相關的信息。

          ServerSocket serverSocket = new ServerSocket(1320);
          System.out.println(serverSocket);

          運行結果:

          ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=1320]

          上面的輸出結果中的addr是服務端綁定的IP地址,如果未綁定IP地址,這個值是0.0.0.0,在這種情況下,ServerSocket對象將監聽服務端所有網絡接口的所有IP地址。port永遠是0localportServerSocket綁定的端口,如果port值為0(不是輸出結果的port,是ServerSocket構造方法的參數port),localport是一個隨機選取的端口號。

          在操作系統中規定1 ~ 1023為系統使用的端口號。端口號的最小值是1,最大值是65535。在Windows中用戶編寫的程序可以綁定端口號小于1024的端口,但在Linux/Unix下必須使用root登錄才可以綁定小于1024的端口。在前面的文章中曾使用Socket類來判斷本機打開了哪些端口,其實使用ServerSocket類也可以達到同樣的目的。基本原理是用ServerSocket來綁定本機的端口,如果綁定某個端口時拋出BindException異常,就說明這個端口已經打開,反之則這個端口未打開。

          package server;

          import java.net.*;

          public class ScanPort
          {
              
          public static void main(String[] args)
              {
                  
          if (args.length == 0)
                      
          return;
                  
          int minPort = 0, maxPort = 0;
                  String ports[] 
          = args[0].split("[-]");
                  minPort 
          = Integer.parseInt(ports[0]);
                  maxPort 
          = (ports.length > 1? Integer.parseInt(ports[1]) : minPort;
                  
          for (int port = minPort; port <= maxPort; port++)
                      
          try
                      {
                          ServerSocket serverSocket 
          = new ServerSocket(port);
                          serverSocket.close();
                      }
                      
          catch (Exception e)
                      {
                          System.err.println(e.getClass());
                          System.err.println(
          "端口" + port + "已經打開!");
                      }
              }
          }

          在上面的代碼中輸出了創建ServerSocket對象時拋出的異常類的信息。ScanPort通過命令行參數將待掃描的端口號范圍傳入程序,參數格式為:minPort-maxPort,如果只輸入一個端口號,ScanPort程序只掃描這個端口號。

                   測試

          java server.ScanPort 1-1023
              運行結果
          class java.net.BindException
          端口80已經打開!
          class java.net.BindException
          端口135已經打開!

          二、設置請求隊列的長度

          在編寫服務端程序時,一般會通過多線程來同時處理多個客戶端請求。也就是說,使用一個線程來接收客戶端請求,當接到一個請求后(得到一個Socket對象),會創建一個新線程,將這個客戶端請求交給這個新線程處理。而那個接收客戶端請求的線程則繼續接收客戶端請求,這個過程的實現代碼如下:


          ServerSocket serverSocket = new ServerSocket(1234);   // 綁定端口
          // 處理其他任務的代碼
          while(true)
          {
              Socket socket = serverSocket.accept(); // 等待接收客戶端請求
             
          // 處理其他任務的代碼
              new ThreadClass(socket).start();   // 創建并運行處理客戶端請求的線程
          }

          上面代碼中ThreadClass類是Thread類的子類,這個類的構造方法有一個Socket類型的參數,可以通過構造方法將Socket對象傳入ThreadClass對象,并在ThreadClass對象的run方法中處理客戶端請求。這段代碼從表面上看好象是天衣無縫,無論有多少客戶端請求,只要服務器的配置足夠高,就都可以處理。但仔細思考上面的代碼,我們可能會發現一些問題。如果在第2行和第6行有足夠復雜的代碼,執行時間也比較長,這就意味著服務端程序無法及時響應客戶端的請求。

          假設第2行和第6行的代碼是Thread.sleep(3000),這將使程序延遲3秒。那么在這3秒內,程序不會執行accept方法,因此,這段程序只是將端口綁定到了1234上,并未開始接收客戶端請求。如果在這時一個客戶端向端口1234發來了一個請求,從理論上講,客戶端應該出現拒絕連接錯誤,但客戶端卻顯示連接成功。究其原因,就是這節要討論的請求隊列在起作用。

          在使用ServerSocket對象綁定一個端口后,操作系統就會為這個端口分配一個先進先出的隊列(這個隊列長度的默認值一般是50),這個隊列用于保存未處理的客戶端請求,因此叫請求隊列。而ServerSocket類的accept方法負責從這個隊列中讀取未處理的客戶端請求。如果請求隊列為空,accept則處于阻塞狀態。每當客戶端向服務端發來一個請求,服務端會首先將這個客戶端請求保存在請求隊列中,然后accept再從請求隊列中讀取。這也可以很好地解釋為什么上面的代碼在還未執行到accept方法時,仍然可以接收一定數量的客戶端請求。如果請求隊列中的客戶端請求數達到請求隊列的最大容量時,服務端將無法再接收客戶端請求。如果這時客戶端再向服務端發請求,客戶端將會拋出一個SocketException異常。

          ServerSocket類有兩個構造方法可以使用backlog參數重新設置請求隊列的長度。在以下幾種情況,仍然會采用操作系統限定的請求隊列的最大長度:

          •  backlog的值小于等于0
          • backlog的值大于操作系統限定的請求隊列的最大長度。
          • ServerSocket構造方法中未設置backlog參數。

          下面積代碼演示了請求隊列的一些特性,請求隊列長度通過命令行參數傳入SetRequestQueue

          package server;

          import java.net.*;

          class TestRequestQueue
          {
              
          public static void main(String[] args) throws Exception
              {
                  
          for (int i = 0; i < 10; i++)
                  {
                      Socket socket 
          = new Socket("localhost"1234);
                      socket.getOutputStream().write(
          1);
                      System.out.println(
          "已經成功創建第" + String.valueOf(i + 1+ "個客戶端連接!");
                  }
              }
          }
          public class SetRequestQueue
          {
              
          public static void main(String[] args) throws Exception
              {
                  
          if (args.length == 0)
                      
          return;
                  
          int queueLength = Integer.parseInt(args[0]);
                  ServerSocket serverSocket 
          = new ServerSocket(1234, queueLength);
                  System.out.println(
          "端口(1234)已經綁定,請按回車鍵開始處理客戶端請求!");
                  System.in.read();
                  
          int n = 0;
                  
          while (true)
                  {
                      System.out.println(
          "<準備接收第" + (++n) + "個客戶端請求!");
                      Socket socket 
          = serverSocket.accept();
                      System.out.println(
          "正在處理第" + n + "個客戶端請求");
                      Thread.sleep(
          3000);
                      System.out.println(
          "" + n + "個客戶端請求已經處理完畢!>");
                  }
              }
          }

             測試(按著以下步驟操作)
          1. 執行如下命令(在執行這條命令后,先不要按回車鍵):

          java server.SetRequestQueue 2


             運行結果:

          端口(1234)已經綁定,請按回車鍵開始處理客戶端請求!
              2. 執行如下命令:  

          java server.TestRequestQueue

          運行結果:

          已經成功創建第1個客戶端連接!
          已經成功創建第2個客戶端連接!
          Exception in thread 
          "main" java.net.SocketException: Connection reset by peer: socket write error
                                 at java.net.SocketOutputStream.socketWrite0(Native Method)
                                 at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:
          92)
                                 at java.net.SocketOutputStream.write(SocketOutputStream.java:
          115)
                                 at server.TestRequestQueue.main(SetRequestQueue.java:
          12)


              3. 按回車鍵繼續執行SetRequestQueue后,運行結果如下:

          端口(1234)已經綁定,請按回車鍵開始處理客戶端請求!
          <準備接收第1個客戶端請求!
          正在處理第1個客戶端請求
          第1個客戶端請求已經處理完畢!>
          <準備接收第2個客戶端請求!
          正在處理第2個客戶端請求
          第2個客戶端請求已經處理完畢!>
          <準備接收第3個客戶端請求!

              從第二步的運行結果可以看出,當TestRequestQueue創建兩個Socket連接之后,服務端的請求隊列已滿,并且服務端暫時無法繼續執行(由于System.in.read()的原因而暫停程序的執行,等待用戶的輸入)。因此,服務端程序無法再接收客戶端請求。這時TestRequestQueue拋出了一個SocketException異常。在TestRequestQueue已經創建成功的兩個Socket連接已經保存在服務端的請求隊列中。在這時按任意鍵繼續執行SetRequestQueueaccept方法就會從請求隊列中將這兩個客戶端請求隊列中依次讀出來。從第三步的運行結果可以看出,服務端處理完這兩個請求后(一個<…>包含的就是一個處理過程),請求隊列為空,這時accept處理阻塞狀態,等待接收第三個客戶端請求。如果這時再運行TestRequestQueue,服務端會接收幾個客戶端請求呢?如果將請求隊列的長度設為大于10的數,TestRequestQueue的運行結果會是什么呢?讀者可以自己做一下這些實驗,看看和自己認為的結果是否一致。

          三、綁定IP地址

          在有多個網絡接口或多個IP地址的計算機上可以使用如下的構造方法將服務端綁定在某一個IP地址上:

          public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

          bindAddr參數就是要綁定的IP地址。如果將服務端綁定到某一個IP地址上,就只有可以訪問這個IP地址的客戶端才能連接到服務器上。如一臺機器上有兩塊網卡,一塊網卡連接內網,另一塊連接外網。如果用Java實現一個Email服務器,并且只想讓內網的用戶使用它。就可以使用這個構造方法將ServerSocket對象綁定到連接內網的IP地址上。這樣外網就無法訪問Email服務器了。可以使用如下代碼來綁定IP地址:

          ServerSocket serverSocket = new
          ServerSocket(
          12340, InetAddress.getByName("192.168.18.10"));

              上面的代碼將IP地址綁定到了192.168.18.10上,因此,服務端程序只能使用綁定了這個IP地址的網絡接口進行通訊。
          四、默認構造方法的使用

              除了使用ServerSocket類的構造方法綁定端口外,還可以用ServerSocketbind方法來完成構造方法所做的工作。要想使用bind方法,必須得用ServerSocket類的默認構造方法(沒有參數的構造方法)來創建ServerSocket對象。bind方法有兩個重載形式,它們的定義如下:

          public void bind(SocketAddress endpoint) throws IOException
          public void bind(SocketAddress endpoint, int backlog) throws IOException

               bind方法不僅可以綁定端口,也可以設置請求隊列的長度以及綁定IP地址。bind方法的作用是為了在建立ServerSocket對象后設置ServerSocket類的一些選項。而這些選項必須在綁定端口之前設置,一但綁定了端口后,再設置這些選項將不再起作用。下面的代碼演示了bind方法的使用及如何設置ServerSocket類的選項。

          ServerSocket serverSocket1 = new ServerSocket();
          serverSocket1.setReuseAddress(true);
          serverSocket1.bind(new InetSocketAddress(1234));
          ServerSocket serverSocket2 = new ServerSocket();
          serverSocket2.setReuseAddress(true);
          serverSocket2.bind(new InetSocketAddress("192.168.18.10"1234));
          ServerSocket serverSocket3 = new ServerSocket();
          serverSocket3.setReuseAddress(true);
          serverSocket3.bind(new InetSocketAddress("192.168.18.10"1234), 30);       

          在上面的代碼中設置了SO_REUSEADDR 選項(這個選項將在后面的文章中詳細討論)。如果使用下面的代碼,這個選項將不起作用。

          ServerSocket serverSocket3 = new ServerSocket(1234);
          serverSocket3.setReuseAddress(
          true);

          在第6行綁定了IP地址和端口。使用構造方法是無法得到這個組合的(想綁定IP地址,必須得設置backlog參數),因此,bind方法比構造方法更靈活。

          下一篇:
          Java網絡編程從入門到精通(26):在服務端接收和發送數





          Android開發完全講義(第2版)(本書版權已輸出到臺灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2009-07-12 19:40 銀河使者 閱讀(3767) 評論(2)  編輯  收藏 所屬分類: java 原創網絡編程

          評論

          # re: Java網絡編程從入門到精通(25):創建ServerSocket對象  回復  更多評論   

          嗯,好,值得學習,多多研究~
          2009-07-12 21:25 | 瑜伽館

          # re: Java網絡編程從入門到精通(25):創建ServerSocket對象  回復  更多評論   

          很好,拜讀了!
          2009-07-18 23:56 | 心夢帆影
          主站蜘蛛池模板: 鹤壁市| 江源县| 西安市| 遵化市| 二手房| 社旗县| 青河县| 重庆市| 古田县| 林甸县| 曲阜市| 昭苏县| 嘉荫县| 乌兰浩特市| 大连市| 寿阳县| 曲阜市| 靖江市| 九江市| 寻乌县| 宜宾县| 邹平县| 钟祥市| 固阳县| 彩票| 宁陕县| 醴陵市| 广德县| 苏尼特左旗| 二连浩特市| 凭祥市| 马鞍山市| 镇巴县| 丹阳市| 远安县| 永昌县| 紫云| 霸州市| 新津县| 黄大仙区| 德清县|