Java網絡編程從入門到精通(25):創建ServerSocket對象 – 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
    在上面的構造方法中涉及到瞭三個參數:port、backlog和bindAddr。其中port是ServerSocket對象要綁定的端口,backlog是請求隊列的長度,bindAddr是ServerSocket對象要綁定的IP地址。


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


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


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


如果port的值為0,系統就會隨機選取一個端口號。但隨機選取的端口意義不大,因為客戶端在連接服務器時需要明確知道服務端程序的端口號。可以通過ServerSocket的toString方法輸出和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永遠是0。localport是ServerSocket綁定的端口,如果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 S

發佈留言