網絡編程之socket新解

  因爲工做並非很忙,閒暇之餘就讀了下tomcat的源代碼。我是從事java服務器開發工做的,大致的一些服務器線程模型我都是瞭解的。其大部分都是由一個線程調用監聽端口等待客戶端的連接,創建鏈接後再交由其餘的線程負責具體的網絡io操做。可tomcat竟然是用多個線程調用同一個ServerSocket實例的accept方法。我讀過mina也讀過netty的源碼,本身在大學時也寫過很多的基於socket通訊的程序,可是這種用法本身從未想過也從未見過。(恕本人咕嚕寡聞了,-_-|||)難免好奇,這麼作原來沒問題啊?可這麼作能有什麼好處嗎?java

  要明白這麼作的道理,恐怖不得不去搞清楚套接字的accept方法底層到底幹了什麼,與TCP的三步握手又是什麼關係。我查了一些資料大致把這些搞明白了,在這裏記錄下來以增強本身的認識,也同時共享給哪些跟我同樣對socket的認識有所誤差的人。tomcat

  1、首先說一下TCP三步握手的基本流程,以下圖:服務器

 

  首先,請求端(客戶端)發送一個包含SYN標誌的TCP報文,SYN即同步(Synchronize),同步報文會指明客戶端使用的端口以及TCP鏈接的初始序號;
  第二步,服務器在收到客戶端的SYN報文後,進入SYN-RECEVIED狀態並將這個尚未徹底創建起的鏈接放到半鏈接隊列。還要返回一個SYN+ACK的報文,表示客戶端的請求被接受,同時TCP序號被加一,ACK即確認(Acknowledgment)。
  第三步,客戶端也返回一個確認報文ACK給服務器端,一樣TCP序列號被加一,到此一個TCP鏈接完成。服務器端就會把此鏈接從半鏈接隊列移除放入到徹底鏈接隊列。

  注意說到的兩個隊列:半鏈接隊列和徹底鏈接隊列網絡

  2、socket操做和TCP的關係socket

  也許好多人都是認爲,當咱們調用ServerSocket的accept方法時,是在監聽等待客戶端的鏈接,當客戶端請求服務器時就創立鏈接並返回與客戶端通訊的socket。實際呢卻不是這樣子的。咱們來看一個現象tcp

    首先咱們啓動一個服務器程序,程序很簡單:代碼以下spa

 

public class Server {

    public static void main(String[] args) throws IOException {
        ServerSocket sSocket = new ServerSocket(3661, 2);//第二個參數的含義後邊會講解
        System.in.read();//防止程序退出
    }

}

 

  能夠看到,這裏咱們並無調用serversocket的accept方法。若是按上邊說的,accept方法是監聽端口等待客戶單的鏈接並完成鏈接的創建的話。咱們這個服務器程序根本沒法監聽端口並完成鏈接創建。接下來驗證一下,命令行

  首先運行服務程序程序,而後咱們在命令行下用netstat -an查看一下端口狀況:線程

 

      能夠看出端口依然被監聽了,並處於tcp三步握手的LISTEN狀態。接下來咱們鏈接試試能不能進行鏈接,啓動一個命令行窗口,執行:netty

  telnet 127.0.0.1 3661。不要把這個關啦,再啓動一個命令行窗口執行如下上邊的netstat -an命令:

 

 

  能夠看到,鏈接已成功創建了,TCP已經進入ESTABLISHED階段。看以看到,咱們不調用accept方法同樣能夠監聽端口並和客戶端創建鏈接。

由此咱們能夠證實accept方法並非監聽端口等待客戶端的鏈接並創建鏈接。那是什麼起到了監聽端口等待客戶單鏈接的做用的,經過看源碼咱們會發現構造方法調用了bind方法。

  看到這裏想必都能知道,其實在咱們調用accept返回一個socket時,tcp早已把三步握手的過程完成了,鏈接已經都創建好了。那咱們的accept具體幹什麼事呢?還記得上邊提到的徹底鏈接隊列吧。accept就是從徹底鏈接隊列取出鏈接並封裝成socket。ServerSocket的構造方法參數backlog就是指定這個隊列的大小。好比上邊服務器程序咱們制定了backlog的值爲2,當咱們用三個命令行分別調用telnet 127.0.0.1 3661時,咱們會發現第三個鏈接請求將是沒法鏈接。

相關文章
相關標籤/搜索