熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

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

2022-06-13   來源: Java核心技術 

  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

  在上面的構造方法中涉及到了三個參數portbacklog和bindAddr其中port是ServerSocket對象要綁定的端口backlog是請求隊列的長度bindAddr是ServerSocket對象要綁定的IP地址

  通過構造方法綁定端口

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

   public ServerSocket(int port) throws IOException

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

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

   ServerSocket serverSocket = new ServerSocket();
Systemoutprintln(serverSocket);

  運行結果

   ServerSocket[addr=/port=localport=]

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

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

   package server;

import *;

public class ScanPort
{
    public static void main(String[] args)
    {
        if (argslength == )
            return;
        int minPort =  maxPort = ;
        String ports[] = args[]split([]);
        minPort = IntegerparseInt(ports[]);
        maxPort = (portslength > ) ? IntegerparseInt(ports[]) : minPort;
        for (int port = minPort; port <= maxPort; port++)
            try
            {
                ServerSocket serverSocket = new ServerSocket(port);
                serverSocketclose();
            }
            catch (Exception e)
            {
                Systemerrprintln(egetClass());
                Systemerrprintln(端口 + port + 已經打開!);
            }
    }
}

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

  測試

   java serverScanPort 

  運行結果

   class BindException
端口已經打開!
class BindException
端口已經打開!

  設置請求隊列的長度

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

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

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

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

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

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

  ●  backlog的值小於等於

  ●  backlog的值大於操作系統限定的請求隊列的最大長度

  ●  在ServerSocket構造方法中未設置backlog參數

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

   package server;

import *;

class TestRequestQueue
{
    public static void main(String[] args) throws Exception
    {
        for (int i = ; i < ; i++)
        {
            Socket socket = new Socket(localhost );
            socketgetOutputStream()write();
            Systemoutprintln(已經成功創建第 + StringvalueOf(i + ) + 個客戶端連接!);
        }
    }
}
public class SetRequestQueue
{
    public static void main(String[] args) throws Exception
    {
        if (argslength == )
            return;
        int queueLength = IntegerparseInt(args[]);
        ServerSocket serverSocket = new ServerSocket( queueLength);
        Systemoutprintln(端口()已經綁定請按回車鍵開始處理客戶端請求!);
        Systeminread();
        int n = ;
        while (true)
        {
            Systemoutprintln(<准備接收第 + (++n) + 個客戶端請求!);
            Socket socket = serverSocketaccept();
            Systemoutprintln(正在處理第 + n + 個客戶端請求);
            Threadsleep();
            Systemoutprintln( + n + 個客戶端請求已經處理完畢!>);
        }
    }
}

  測試(按著以下步驟操作)

   執行如下命令(在執行這條命令後先不要按回車鍵)

   java serverSetRequestQueue 

  運行結果

  端口()已經綁定請按回車鍵開始處理客戶端請求!

   執行如下命令

   java serverTestRequestQueue

  運行結果

   已經成功創建第個客戶端連接!
已經成功創建第個客戶端連接!
Exception in thread main SocketException: Connection reset by peer: socket write error
                       at SocketOutputStreamsocketWrite(Native Method)
                       at SocketOutputStreamsocketWrite(SocketOutputStreamjava:)
                       at SocketOutputStreamwrite(SocketOutputStreamjava:)
                       at serverTestRequestQueuemain(SetRequestQueuejava:)

   按回車鍵繼續執行SetRequestQueue後運行結果如下

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

  從第二步的運行結果可以看出當TestRequestQueue創建兩個Socket連接之後服務端的請求隊列已滿並且服務端暫時無法繼續執行(由於Systeminread()的原因而暫停程序的執行等待用戶的輸入)因此服務端程序無法再接收客戶端請求這時TestRequestQueue拋出了一個SocketException異常在TestRequestQueue已經創建成功的兩個Socket連接已經保存在服務端的請求隊列中在這時按任意鍵繼續執行SetRequestQueueaccept方法就會從請求隊列中將這兩個客戶端請求隊列中依次讀出來從第三步的運行結果可以看出服務端處理完這兩個請求後(一個<…>包含的就是一個處理過程)請求隊列為空這時accept處理阻塞狀態等待接收第三個客戶端請求如果這時再運行TestRequestQueue服務端會接收幾個客戶端請求呢?如果將請求隊列的長度設為大於的數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(  InetAddressgetByName());

  上面的代碼將IP地址綁定到了因此服務端程序只能使用綁定了這個IP地址的網絡接口進行通訊

  默認構造方法的使用

  除了使用ServerSocket類的構造方法綁定端口外還可以用ServerSocket的bind方法來完成構造方法所做的工作要想使用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 serverSocket = new ServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(new InetSocketAddress());
ServerSocket serverSocket = new ServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(new InetSocketAddress( ));
ServerSocket serverSocket = new ServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(new InetSocketAddress( ) );

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

   ServerSocket serverSocket = new ServerSocket();
serverSocketsetReuseAddress(true);

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


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26075.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.