關於java socket

來源:互聯網
上載者:User

標籤:

1. 關於new Socket()中參數的理解

Server端:

  • 調用ServerSocket serverSocket = new ServerSocket(1287,2);後Server端開啟了指定的連接埠1287,並綁定了PID 5449。

 

Client端:

  • 調用Socket socket = new Socket(remoteAddress, 1287);後,Client端會將Client的PID綁定到一個隨機未使用的連接埠上;

 

  • 調用Socket socket = new Socket(remoteAddress, 1287, localAddress, 1288);後,Client端會將Client的PID綁定到指定的連接埠1288上;

 

2. 關於backlog,connectiontimeout,SO_TIMEOUT參數的理解2.1. 測試過程及現象

經過測試發現,測試程式在Windows上和在Linux上的表現不同。

2.1.1. Linux平台上(Server和Client都在Linux上)

在eclipse中先啟動Server,然後啟動第1個Client C1,此時C1正常串連到Server並返回:

 

Server端也列印出了相應的資訊:

 

啟動第2個Client C2,此時C2一直等待Server的“hi”回應,Server端的排隊數為1;

 

啟動第3個Client C3,此時C3一直等待Server的“hi”回應,Server端的排隊數為2;

 

啟動第4個Client C4,此時C4一直等待Server的“hi”回應,Server端的排隊數為3;

 

啟動第5個Client C5,此時C5一直等待Server的“hi”回應,Server端的排隊數為4;

 

啟動第6個Client C6,此時C6一直等待Server的“hi”回應,Server端的排隊數為5;

 

等待大約100s之後,C5和C6都返回了報錯資訊:(Client端均沒有設定socket.setSoTimeout(5000);)

 

而C2、C3和C4依然處於等待狀態,沒有任何輸出:

 

此時Server端的排隊數為3,Server端的設定(ServerSocket serverSocket = new ServerSocket(1287,2))的排隊數21。事實上,經過進一步的測試,Server端設定的排隊數為1或3時,Server端的實際排隊數都比所設定的值多1。

2.1.2. Windows平台上(Server和Client都在Windows上)

在cmd中先啟動Server,然後啟動第1個Client C1,此時C1正常串連到Server並返回:

 

Server端也列印出了相應的資訊:

 

啟動第2個Client C2,此時C2一直等待Server的“hi”回應,Server端的排隊數為1;

 

啟動第3個Client C3,此時C3一直等待Server的“hi”回應,Server端的排隊數為2;

 

啟動第4個Client C4,此時C4立刻返回了報錯資訊:(Client端均沒有設定socket.setSoTimeout(5000);)

 

而C2、C3依然處於等待狀態,沒有任何輸出:

 

此時Server端的排隊數為2,Server端的設定(ServerSocket serverSocket = new ServerSocket(1287,2))的排隊數2相等。事實上,經過進一步的測試,Server端設定的排隊數為1或3時,Server端的實際排隊數都與所設定的值相等。

2.2. Client串連Server的過程

(1).   Server端初始化ServerSocket並監聽連接埠

ServerSocket serverSocket = new ServerSocket(1287,2);

Socket socket = serverSocket.accept(); // 此時若無Client端串連上來,即Server端的等待隊列為空白,那麼Server端在此阻塞

(2).   Client端初始化Socket,並串連Server

Socket socket = new Socket ();

socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);

(socket.setSoTimeout(5000);)

(3).   Server端accept()調用從等待隊列中取出Client,並返回

Socket socket = serverSocket.accept();

(4).   Server端通過返回的Socket與Client端互動

2.3. 猜測結論

根據以上現象猜測,所謂“排隊隊列”應該是在TCP層控制的,具體如:

 

【解釋圖中的要素】

串連隊列(connection隊列):所有的用戶端TCP串連請求,首先進入串連隊列,進行三向交握,三向交握成功標誌著串連建立(established),串連建立後用戶端請求被從隊列中取出並進入ACCEPT隊列。三向交握建立串連的過程一般很快,區域網路中1ms以內。

connectiontimeout是指從進入串連隊列、進行三向交握、建立串連到從隊列中被取出這段時間的逾時時間,一般這個不會逾時。connectiontimeout可以在用戶端通過socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);來設定。

ACCEPT隊列:串連建立後,用戶端請求被放入ACCEPT隊列,等待服務端應用調用serverSocket.accept(),從而將用戶端請求從ACCEPT隊列中取出並與其互動。當然,若服務端應用調用Socket socket = serverSocket.accept();時ACCPET隊列為空白,那麼服務端應用在此阻塞,直到ACCEPT隊列不為空白。

ACCEPT隊列的最大排隊數backlog控制著這個隊列最多能存放多少個用戶端請求,當用戶端請求個數大於backlog時將拒絕為多餘的用戶端服務。服務端應用可以通過調用ServerSocket serverSocket = new ServerSocket(1287,2);來設定backlog。

在ACCEPT隊列中,每個用戶端請求都有一個SO_TIMEOUT的參數,用來控制該用戶端在這段時間內的逾時——從進入ACCEPT隊列開始到從中取出、服務端accept()返回、服務端向socket寫入回複資料、用戶端收到服務端的回複。如果用戶端在SO_TIMEOUT時間內沒有收到來自服務端的回複,則用戶端在is.readLine()報Read timed out異常,如。在用戶端可以通過調用socket.setSoTimeout(5000);來設定SO_TIMEOUT。

 

port-PID映射表:用戶端請求從ACCEPT隊列中被取出後,需要交給服務端應用,因此需要知道應該交給哪個服務端應用,它的PID是多少,所以需要一個類似port-PID映射表的映射關係。當服務端應用(PID=1234)啟動時,調用ServerSocket serverSocket = new ServerSocket(1287,2);時,意味著該服務綁定了1287這個連接埠,此時應在port-PID映射表中添加一條記錄“port:1287, PID=1234”。這樣,當用戶端請求被從port1287的ACCEPT隊列中取出時,就知道應該交給PID為1234的服務端應用。

【描述C1-C6串連Server的過程】

在服務端,當應用程式層應用1調用ServerSocket serverSocket = new ServerSocket(1287,2);時,TCP層在port-PID映射表中加入一條記錄“port1,PID1”以示綁定;並且TCP層為port1開闢新的空間,用來儲存port、串連隊列和ACCEPT隊列,並設定ACCPET隊列的backlog。然後應用1調用Socket socket = serverSocket.accept();來監聽ACCEPT隊列。

當C1調用socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);發起請求時,C1會路由到服務端機器,從物理層、鏈路層、IP層到達TCP層。到達後首先進入串連隊列1,設定connectiontimeout,進行三向交握,成功後進入ACCEPT隊列,並調用socket.setSoTimeout(5000);設定SO_TIMEOUT。由於應用1正阻塞在accept(),所以C1被從排隊隊列中取出,然後查詢port-PID映射表,決定PID1,從而將C1發送給PID1進程應用1,然後應用1的accpet()返回並產生Socket來與C1互動。

當C2調用socket.connect(new InetSocketAddress(InetAddress.getByName("22.11.143.60"),1287), 1000);時,C2在串連隊列中進行三向交握後進入ACCEPT隊列,並調用socket.setSoTimeout(5000);設定SO_TIMEOUT。由於此時應用1沒有調用accept(),所以C2在排隊隊列中等待。C3、C4、C5、C6重複此過程。若排隊隊列中的元素個數大於最大排隊數backlog1,那麼TCP拒絕為多餘的Client服務。

【解釋測試現象】

因此,下面可以解釋測試現象(ps. 在測試中Client端均沒有設定socket.setSoTimeout(5000);)

l  若設定backlog1 = 2,那麼,在Linux環境下C5和C6會被拒絕,ACCPET隊列長度為3,比backlog1大1;而在windows環境下C4、C5和C6會被拒絕,ACCEPT隊列長度為2,等於backlog1。這可能是由於不同的作業系統對TCP的參數設定不同導致。

l  在不同的作業系統下,串連被拒絕時的報錯Exception也不同。windows下報connnection refused異常,異常位置在socket.connect(***);,這是由於串連被拒絕,或者說沒有串連上;而Linux下報connection reset異常,異常位置在is.readLine()(這個異常一般是由於一端socket被關閉,而另一端仍在進行讀寫操作引起的),這是可能是由於服務端斷開多於backlog+1的串連而用戶端仍在readLine()導致。這可能是由於不同作業系統對TCP的實現不同導致。

l  在測試過程中Client端只設定了connectiontimeout,沒有設定SO_TIMEOUT。在Linux環境下C5和C6會在約100s後被拒絕,而在windows環境下C4、C5和C6會立刻被拒絕。這個應該和connectiontimeout無關。由於用戶端都沒有設定SO_TIMEOUT,所以估計跟這個參數也無關。應該除了connectiontimeoutSO_TIMEOUT以外還有一個作業系統預設的參數來控制是100s還是立刻,目前尚不清楚。

l  如果在Client端設定了SO_TIMEOUT,例如為5s,那麼在Linux上的現象是C2及其之後的Client會在串連上服務端等待其回複5s後報Read timed out,異常位置在is.readLine();在windows上的現象是C4及其之後的Client依然立即報Connection refused異常,異常位置在socket.connect(***),而C2和C3是在串連上服務端等待其回複5s後報Read timed out異常,異常位置在is.readLine()。

如果在Client端設定了SO_TIMEOUT,例如為110s,那麼在Linux上的現象是C5及其之後的Client會在100s後報Connection reset異常,異常位置在is.readLine(),而C2、C3和C4會在110s後報Read timed out異常,異常位置在is.readLine();在windows上的現象是C4及其之後的Client依然立即報Connection refused異常,異常位置在socket.connect(***),而C2和C3是在串連上服務端等待其回複110s後報Read timed out異常,異常位置在is.readLine()。

這就是SO_TIMEOUT這個參數的含義。兩個作業系統在這一點上的理解是一致的。

3. 附件

測試使用的代碼:

 Client.java

 1 package socket; 2  3 import java.io.*; 4 import java.net.*; 5 import java.util.Date; 6  7 public class Client { 8  9     /**10      * @param args11      */12     public static void main(String[] args) {13         // TODO Auto-generated method stub14         try {15             System.out.println(new Date());16             InetAddress remoteAddress = InetAddress.getByName("127.0.0.1");17 //            InetAddress localAddress = InetAddress.getByName("127.0.0.1");18 //            Socket socket = new Socket(remoteAddress, 1287, localAddress, 1288);19 //            Socket socket = new Socket(remoteAddress, 1287);20             Socket socket = new Socket ();21             socket.connect(new InetSocketAddress(remoteAddress,1287), 1000);22             socket.setSoTimeout(5000);23             System.out.println(new Date());24             PrintWriter os = new PrintWriter(socket.getOutputStream());25             BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));26             String msg = "hello";27             os.println(msg);28             os.flush();29             System.out.println("Client: " + msg);30 //            Thread.sleep(1000);31             System.out.println("Server: " + is.readLine());32             System.out.println(new Date());33             Thread.sleep(1000000);34             System.out.println("end!");35             os.close();36             is.close();37             socket.close();38         } catch (UnknownHostException e) {39             // TODO Auto-generated catch block40             System.out.println(new Date());41             e.printStackTrace();42         } catch (IOException e) {43             // TODO Auto-generated catch block44             System.out.println(new Date());45             e.printStackTrace();46         }47         catch (InterruptedException e) {48             // TODO Auto-generated catch block49             System.out.println(new Date());50             e.printStackTrace();51         }52         53     }54 55 }

Server.java

 1 package socket; 2  3 import java.io.*; 4 import java.net.*; 5  6 public class Server { 7  8     /** 9      * @param args10      */11     public static void main(String[] args) {12         // TODO Auto-generated method stub13         try {14             ServerSocket serverSocket = new ServerSocket(1287,2);15             Socket socket = serverSocket.accept();16             17             PrintWriter os = new PrintWriter(socket.getOutputStream());18             BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));19             while (true){20                 System.out.println("Client: " + is.readLine());21                 String msg = "hi";22                 os.println(msg);23                 os.flush();24                 System.out.println("Server: " + msg);25             }26 //            os.close();27 //            is.close();28 //            socket.close();29 //            serverSocket.close();30         } catch (IOException e) {31             // TODO Auto-generated catch block32             e.printStackTrace();33         }34 //        catch (InterruptedException e) {35 //            // TODO Auto-generated catch block36 //            e.printStackTrace();37 //        }38 39     }40 41 }

 

關於java socket

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.