socket通信模型、socket中的accept()阻塞與read()阻塞


Socket整體流程

    Socket編程主要涉及到客戶端和服務端兩個方面,首先是在服務器端創建一個服務器套接字(ServerSocket),並把它附加到一個端口上,服務器從這個端口監聽連接。端口號的范圍是0到65536,但是0到1024是為特權服務保留的端口號,我們可以選擇任意一個當前沒有被其他進程使用的端口。

  客戶端請求與服務器進行連接的時候,根據服務器的域名或者IP地址,加上端口號,打開一個套接字。當服務器接受連接后,服務器和客戶端之間的通信就像輸入輸出流一樣進行操作。

  如果一個程序創建了一個socket,並讓其監聽80端口,其實是向TCP/IP協議棧聲明了其對80端口的占有。以后,所有目標是80端口的TCP數據包都會轉發給該程序(這里的程序,因為使用的是Socket編程接口,所以首先由Socket層來處理)。所謂accept函數,其實抽象的是TCP的連接建立過程。  accept函數返回的新socket其實指代的是本次創建的連接,而一個連接是包括兩部分信息的,一個是源IP和源端口,另一個是宿IP和宿端口。所以,accept可以產生多個不同的socket,而這些socket里包含的宿IP和宿端口是不變的,變化的只是源IP和源端口。這樣的話,這些socket宿端口就可以都是80,而Socket層還是能根據源/宿對來准確地分辨出IP包和socket的歸屬關系,從而完成對TCP/IP協議的操作封裝。TCP/IP只是一個協議棧,就像操作系統的運行機制一樣,必須要具體實現,同時還要提供對外的操作接口。就像操作系統會提供標准的編程接口,比如  Win32編程接口一樣,TCP/IP也必須對外提供編程接口,這就是Socket編程接口。

 

客戶端上的使用

  getInputStream()方法可以得到一個輸入流,客戶端的Socket對象上的getInputStream方法得到輸入流其實就是從服務器端發回的數據。
  getOutputStream()方法得到的是一個輸出流,客戶端的Socket對象上的getOutputStream方法得到的輸出流其實就是發送給服務器端的數據。

服務器端上的使用

  getInputStream()方法得到的是一個輸入流,服務端的Socket對象上的getInputStream方法得到的輸入流其實就是從客戶端發送給服務器端的數據流。

  getOutputStream()方法得到的是一個輸出流,服務端的Socket對象上的getOutputStream方法得到的輸出流其實就是發送給客戶端的數據。

 

ServerSocket類的accept()阻塞

ServerSocket的accept()方法是偵聽並接受到此套接字的連接,就是一直等待連接,此方法在連接傳入之前一直阻塞(即后面的代碼不會往下執行)。直到接受到有socket的連接,然后創建並返回新的Socket對象。

read()阻塞

從socket上讀取對端發過來的數據一般有兩種方法: 

1)按照字節流讀取

2)按照字符流讀取

 

這段代碼執行以后會發現read()方法發生了阻塞,經過查找資料發現:

read() 是一個阻塞函數,如果客戶端沒有聲明斷開outputStream那么它就會認為客戶端仍舊可能發送數據,所以就會一直阻塞而不是返回-1,所以System.out.println("服務器");這行代碼在連接斷開之前就一直不會執行,因為在while ((len = is.read(buf)) != -1) 這里阻塞了。

像read()這種阻塞讀取函數還有BufferedReader類種的 readLine()、DataInputStream種的readUTF()等。


這個特性使得編程非常方便也很高效。 
但是這樣也有一個問題,就是如何讓程序從這兩個方法的阻塞調用中返回。

 總結一下,有這么幾個方法:

 1、發送完后調用Socket的shutdownOutput()方法關閉輸出流,這樣對端的輸入流上的read操作就會返回-1。  注意不能調用socket.getInputStream().close()。這樣會導致socket被關閉。 

       當然如果不需要繼續在socket上進行讀操作,也可以直接關閉socket。  但是這個方法不能用於通信雙方需要多次交互的情況。

1 os.write("sender say hello socket".getBytes());
2 os.flush();
3 client.shutdownOutput();  //調用shutdown 通知對端請求完畢

 

       這個解決方案缺點非常明顯,socket任意一端都依賴於對方調用shutdownOutput()來完成read返回 -1,如果任意一方沒有執行shutdown函數那么就會出現問題。所以一般我們都會在socket請求時設置連接的超時時間 socket.setSoTimeout(5000);以防止長時間沒有響應造成系統癱瘓。

1 while (true) {
2      server = serverSocket.accept();
3      System.out.println("server socket is start……");
4      server.setSoTimeout(5000);
5      .....
6  }

 

參考文章:

https://blog.csdn.net/anthony_ju/article/details/82192135

https://www.cnblogs.com/swordfall/p/10781281.html

https://www.cnblogs.com/gaoqiri/p/10055610.html

https://blog.csdn.net/yanchuang1/article/details/48049259


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM