1. 程式人生 > 其它 >【網路程式設計socket】BIO & Socket和ServerSocket的簡單例子

【網路程式設計socket】BIO & Socket和ServerSocket的簡單例子

技術標籤:TcpIp

文章目錄

概述

ServerSocket是基於BIO的

1、構造ServerSocket

ServerSocket的構造方法有以下幾種過載形式:

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

在以上構造方法中,引數port指定伺服器要繫結的埠(伺服器要監聽的埠),引數backlog指定客戶連線請求佇列的長度,引數bindAddr指定伺服器要繫結的IP地址。

1.1 、繫結埠

除不帶引數的構造方法以外,其他構造方法都會使伺服器與特定埠繫結,該埠由引數port指定。

如果埠被其他服務程序佔用,或是,在某些系統中,若沒有以超級使用者身份執行伺服器程式,作業系統不允許伺服器繫結到1-1023的埠時,會丟擲BindException。

1.2、設定客戶連線請求佇列的長度

當伺服器程序執行時,可能會同時監聽到多個客戶的連線請求。管理客戶端連線請求的任務是由作業系統來完成的。作業系統將連線請求儲存在一個先進先出佇列中。許多作業系統限定了佇列的最大長度,一般為50。當佇列中的連線請求達到了佇列的最大容量時,伺服器程序所在的主機會拒絕新的連線請求。只有當伺服器程序通過ServerSocket的accept()方法從佇列中取出連線請求,使佇列騰出空位時,佇列才能繼續加入新的連線請求。

對於客戶程序,如果它發出的連線請求被加入到伺服器的佇列中,就意味著客戶與伺服器的連線建立成功,客戶程序從Socket構造方法中正常返回。如果客戶程序發出的連線請求被伺服器拒絕,Socket構造方法就會丟擲ConnectionException。

ServerSocket構造方法的backlog引數用來顯式設定連線請求佇列的長度,它將覆蓋作業系統限定的佇列的最大長度。

在以下幾種情況,仍然採用作業系統限定的佇列最大長度:

  • backlog引數的值大於作業系統限定的佇列的最大長度;

  • backlog引數的值小於或等於0;
    在這裡插入圖片描述

  • 在ServerSocket構造方法中沒有設定backlog引數。

1.3、設定繫結的IP地址

若主機只有一個地址,則伺服器預設繫結該地址;若主機有多個地址,則可以呼叫ServerSocket(int port, int backlog, InetAddress bindAddr)構造方法設定主機ip地址。

1.4、預設構造方法的作用

ServerSocket有一個不帶引數的預設構造方法。通過該方法建立的ServerSocket不與任何埠繫結,接下來還需要通過bind()方法與特定埠繫結。

這個預設構造方法的用途是,允許伺服器在繫結到特定埠之前,先設定ServerSocket的一些選項。因為一旦伺服器與特定埠繫結,有些選項就不能再改變了。

2、接收和關閉與客戶的連線

ServerSocket的accept()方法從連線請求佇列中取出一個客戶的連線請求,然後建立與客戶連線的Socket物件,並將它返回。如果佇列中沒有連線請求,accept()方法就會一直等待,直到接收到了連線請求才返回。

伺服器從Socket物件中獲得輸入流和輸出流

,就能與客戶交換資料。當伺服器正在進行傳送資料的操作時,如果客戶端斷開了連線,那麼伺服器端會丟擲一個IOException的子類SocketException異常:java.net.SocketException: Connection reset by peer。

3、關閉ServerSocket

ServerSocket的close()方法使伺服器釋放佔用的埠,並且斷開與所有客戶的連線。當一個伺服器程式執行結束時,即使沒有執行ServerSocket的close()方法,作業系統也會釋放這個伺服器佔用的埠。因此,伺服器程式並不一定要在結束之前執行ServerSocket的close()方法。

在某些情況下,如果希望及時釋放伺服器的埠,以便讓其他程式能佔用該埠,則可以顯式呼叫ServerSocket的close()方法。

ServerSocket的isClosed()方法判斷ServerSocket是否關閉,只有執行了ServerSocket的close()方法,isClosed()方法才返回true;否則,即使ServerSocket還沒有和特定埠繫結,isClosed()方法也會返回false。

ServerSocket的isBound()方法判斷ServerSocket是否已經與一個埠繫結,只要ServerSocket已經與一個埠繫結,即使它已經被關閉,isBound()方法也會返回true。

4、獲取ServerSocket的資訊

  • public InetAddress getInetAddress():獲取伺服器繫結的ip地址;
  • public int getLocalPort():獲取伺服器繫結的埠;

在構造ServerSocket時,如果把埠設為0,那麼將由作業系統為伺服器分配一個埠(稱為匿名埠),程式只要呼叫getLocalPort()方法就能獲知這個埠號。多數伺服器會監聽固定的埠,這樣才便於客戶程式訪問伺服器。匿名埠一般適用於伺服器與客戶之間的臨時通訊,通訊結束,就斷開連線,並且ServerSocket佔用的臨時埠也被釋放。

5、ServerSocket選項

ServerSocket有以下3個選項。

  • SO_TIMEOUT:表示等待客戶連線的超時時間。
  • SO_REUSEADDR:表示是否允許重用伺服器所繫結的地址。
  • SO_RCVBUF:表示接收資料的緩衝區的大小。

5.1、SO_TIMEOUT選項

  • 設定該選項:public void setSoTimeout(int timeout) throws SocketException
  • 讀取該選項:public int getSoTimeout () throws IOException

SO_TIMEOUT表示ServerSocket的accept()方法等待客戶連線的超時時間,以毫秒為單位。 如果SO_TIMEOUT的值為0,表示永遠不會超時,這是SO_TIMEOUT的預設值。

當伺服器執行ServerSocket的accept()方法時,如果連線請求佇列為空,伺服器就會一直等待,直到接收到了客戶連線才從accept()方法返回。如果設定了超時時間,那麼當伺服器等待的時間超過了超時時間,就會丟擲SocketTimeoutException,它是InterruptedException的子類。

5.2、SO_REUSEADDR選項

  • 設定該選項:public void setResuseAddress(boolean on) throws SocketException
    讀取該選項:public boolean getResuseAddress() throws SocketException

這個選項與Socket的SO_REUSEADDR選項相同,用於決定如果網路上仍然有資料向舊的ServerSocket傳輸資料,是否允許新的ServerSocket繫結到與舊的ServerSocket同樣的埠上。SO_REUSEADDR選項的預設值與作業系統有關,在某些作業系統中,允許重用埠,而在某些作業系統中不允許重用埠。

當ServerSocket關閉時,如果網路上還有傳送到這個ServerSocket的資料,這個ServerSocket不會立刻釋放本地埠,而是會等待一段時間,確保接收到了網路上傳送過來的延遲資料,然後再釋放埠

許多伺服器程式都使用固定的埠。當伺服器程式關閉後,有可能它的埠還會被佔用一段時間,如果此時立刻在同一個主機上重啟伺服器程式,由於埠已經被佔用,使得伺服器程式無法繫結到該埠,伺服器啟動失敗,並丟擲BindException。

為了確保一個程序關閉了ServerSocket後,即使作業系統還沒釋放埠,同一個主機上的其他程序還可以立刻重用該埠,可以呼叫ServerSocket.setResuseAddress(true)方法

5.3、SO_RCVBUF選項

  • 設定該選項:public void setReceiveBufferSize(int size) throws SocketException
  • 讀取該選項:public int getReceiveBufferSize() throws SocketException

SO_RCVBUF表示伺服器端的用於接收資料的緩衝區的大小,以位元組為單位。一般說來,傳輸大的連續的資料塊(基於HTTP或FTP協議的資料傳輸)可以使用較大的緩衝區,這可以減少傳輸資料的次數,從而提高傳輸資料的效率。而對於互動式的通訊(Telnet和網路遊戲),則應該採用小的緩衝區,確保能及時把小批量的資料傳送給對方。

5.4、設定連線時間、延遲和頻寬的相對重要性

public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

該方法的作用與Socket的setPerformancePreferences()方法的作用相同,用於設定連線時間、延遲和頻寬的相對重要性。

6、建立多執行緒伺服器

許多實際應用要求伺服器具有同時為多個客戶提供服務的能力。HTTP伺服器就是最明顯的例子。任何時刻,HTTP伺服器都可能接收到大量的客戶請求,每個客戶都希望能快速得到HTTP伺服器的響應。如果長時間讓客戶等待,會使網站失去信譽,從而降低訪問量。

可以用併發效能來衡量一個伺服器同時響應多個客戶的能力。一個具有好的併發效能的伺服器,必須符合兩個條件:

  • 能同時接收並處理多個客戶連線;
  • 對於每個客戶,都會迅速給予響應。
用多個執行緒來同時為多個客戶提供服務,這是提高伺服器的併發效能的最常用的手段。

以下將按照3中方式來實現EchoServer,它們都使用多執行緒。

  • 為每個客戶分配一個工作執行緒。
  • 使用執行緒池,由其中的工作執行緒來為客戶服務。
    可以利用JDK的Java類庫中現成的執行緒池,由它的工作執行緒來為客戶服務。

6.1、 為每個客戶分配一個執行緒

伺服器的主執行緒負責接收客戶的連線,每次接收到一個客戶連線,就會建立一個工作執行緒,由它負責與客戶的通訊。

public static void start(){
  try{
    ServerSocket serverSocket = new ServerSocket(PORT);
    System.out.println("server listen on port:" + PORT);
    while (true){
      try {
        Socket client = serverSocket.accept();
        System.out.println("receive client connect, localPort=" + client.getPort());
        //每次都new一個新的執行緒
        new Thread(new EchoServer.HandlerServer(client)).start();
      }catch (Exception e){
        System.out.println("client exception,e=" + e.getMessage());
      }
    }
  }catch(Exception e){
    System.out.println("server exception,e=" + e.getMessage());
  }
}

6.2、使用JDK類庫提供的執行緒池

以此,提供一個完整的例子:

在這裡插入程式碼片





參考:
《Socket和ServerSocket的簡單介紹及例子》

《ServerSocket詳解》