歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 網絡編程之Socket新解

網絡編程之Socket新解

日期:2017/3/1 9:31:13   编辑:Linux編程

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

  要明白這麼做的道理,恐怖不得不去搞清楚套接字的accept方法底層到底干了什麼,與TCP的三步握手又是什麼關系。我查了一些資料大體把這些搞明白了,在這裡記錄下來以加強自己的認識,也同時共享給哪些跟我一樣對socket的認識有所偏差的人。

  一、首先說一下TCP三步握手的基本流程,如下圖:

  首先,請求端(客戶端)發送一個包含SYN標志的TCP報文,SYN即同步(Synchronize),同步報文會指明客戶端使用的端口以及TCP連接的初始序號;

  第二步,服務器在收到客戶端的SYN報文後,進入SYN-RECEVIED狀態並將這個還沒有完全建立起的連接放到半連接隊列。還要返回一個SYN+ACK的報文,表示客戶端的請求被接受,同時TCP序號被加一,ACK即確認(Acknowledgment)。

  第三步,客戶端也返回一個確認報文ACK給服務器端,同樣TCP序列號被加一,到此一個TCP連接完成。服務器端就會把此連接從半連接隊列移除放入到完全連接隊列。

  注意說到的兩個隊列:半連接隊列和完全連接隊列

  二、socket操作和TCP的關系

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

首先我們啟動一個服務器程序,程序很簡單:代碼如下

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狀態。接下來我們連接試試能不能進行連接,啟動一個命令行窗口,執行:

  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時,我們會發現第三個連接請求將是無法連接。

Copyright © Linux教程網 All Rights Reserved