<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 45 Socket 源碼及面試題 ## 引導語 Socket 中文翻譯叫套接字,可能很多工作四五年的同學都沒有用過這個 API,但只要用到這個 API 時,必然是在重要的工程的核心代碼處。 大家平時基本都在用開源的各種 rpc 框架,比如說 Dubbo、gRPC、Spring Cloud 等等,很少需要手寫網絡調用,以下三小節可以幫助大家補充這塊的內容,當你真正需要的時候,可以作為手冊示例。 本文和《ServerSocket 源碼及面試題》一文主要說 Socket 和 ServerSocket 的源碼,《工作實戰:Socket 結合線程池的使用》這章主要說兩個 API 在實際工作中如何落地。 ### 1 Socket 整體結構 Socket 的結構非常簡單,Socket 就像一個殼一樣,將套接字初始化、創建連接等各種操作包裝了一下,其底層實現都是 SocketImpl 實現的,Socket 本身的業務邏輯非常簡單。 Socket 的屬性不多,有套接字的狀態,SocketImpl,讀寫的狀態等等,源碼如下圖: ![](https://img.kancloud.cn/fe/e1/fee1366a61edd2e9106ffa1c2003f044_496x264.jpg) 套接字的狀態變更都是有對應操作方法的,比如套接字新建(createImpl 方法)后,狀態就會更改成 created = true,連接(connect)之后,狀態更改成 connected = true 等等。 ### 2 初始化 Socket 的構造器比較多,可以分成兩大類: 1. 指定代理類型(Proxy)創建套節點,一共有三種類型為:DIRECT(直連)、HTTP(HTTP、FTP 高級協議的代理)、SOCKS(SOCKS 代理),三種不同的代碼方式對應的 SocketImpl 不同,分別是:PlainSocketImpl、HttpConnectSocketImpl、SocksSocketImpl,除了類型之外 Proxy 還指定了地址和端口; 2. 默認 SocksSocketImpl 創建,并且需要在構造器中傳入地址和端口,源碼如下: ``` // address 代表IP地址,port 表示套接字的端口 // address 我們一般使用 InetSocketAddress,InetSocketAddress 有 ip+port、域名+port、InetAddress 等初始化方式 public Socket(InetAddress address, int port) throws IOException { this(address != null ? new InetSocketAddress(address, port) : null, (SocketAddress) null, true); } ``` 這里的 address 可以是 ip 地址或者域名,比如說 127.0.0.1 或者 www.wenhe.com。 我們一起看一下這個構造器調用的 this 底層構造器的源碼: ``` // stream 為 true 時,表示為stream socket 流套接字,使用 TCP 協議,比較穩定可靠,但占用資源多 // stream 為 false 時,表示為datagram socket 數據報套接字,使用 UDP 協議,不穩定,但占用資源少 private Socket(SocketAddress address, SocketAddress localAddr, boolean stream) throws IOException { setImpl(); // backward compatibility if (address == null) throw new NullPointerException(); try { // 創建 socket createImpl(stream); // 如果 ip 地址不為空,綁定地址 if (localAddr != null) // create、bind、connect 也是 native 方法 bind(localAddr); connect(address); } catch (IOException | IllegalArgumentException | SecurityException e) { try { close(); } catch (IOException ce) { e.addSuppressed(ce); } throw e; } } ``` 從源碼中可以看出: 1. 在構造 Socket 的時候,你可以選擇 TCP 或 UDP,默認是 TCP; 2. 如果構造 Socket 時,傳入地址和端口,那么在構造的時候,就會嘗試在此地址和端口上創建套接字; 3. Socket 的無參構造器只會初始化 SocksSocketImpl,并不會和當前地址端口綁定,需要我們手動的調用 connect 方法,才能使用當前地址和端口; 4. Socket 我們可以理解成網絡溝通的語言層次的抽象,底層網絡創建、連接和關閉,仍然是 TCP 或 UDP 本身網絡協議指定的標準,Socket 只是使用 Java 語言做了一層封裝,從而讓我們更方便地使用。 ### 3 connect 連接服務端 connect 方法主要用于 Socket 客戶端連接上服務端,如果底層是 TCP 層協議的話,就是通過三次握手和服務端建立連接,為客戶端和服務端之間的通信做好準備,底層源碼如下: ``` public void connect(SocketAddress endpoint, int timeout) throws IOException { } ``` connect 方法要求有兩個入參,第一個入參是 SocketAddress,表示服務端的地址,我們可以使用 InetSocketAddress 進行初始化,比如:new InetSocketAddress(“www.wenhe.com”, 2000)。 第二入參是超時時間的意思(單位毫秒),表示客戶端連接服務端的最大等待時間,如果超過當前等待時間,仍然沒有成功建立連接,拋 SocketTimeoutException 異常,如果是 0 的話,表示無限等待。 ### 4 Socket 常用設置參數 Socket 的常用設置參數在 SocketOptions 類中都可以找到,接下來我們來一一分析下,以下理解大多來自類注釋和網絡。 #### 4.1 setTcpNoDelay 此方法是用來設置 TCP_NODELAY 屬性的,屬性的注釋是這樣的:此設置僅僅對 TCP 生效,主要為了禁止使用 Nagle 算法,true 表示禁止使用,false 表示使用,默認是 false。 對于 Nagle 算法,我們引用維基百科上的解釋: 納格算法是以減少數據包發送量來增進 [TCP/IP] 網絡的性能,它由約翰·納格任職于Ford Aerospace時命名。 納格的文件[注 1]描述了他所謂的“小數據包問題”-某個應用程序不斷地提交小單位的數據,且某些常只占1字節大小。因為TCP數據包具有40字節的標頭信息(TCP與 IPv4 各占20字節),這導致了41字節大小的數據包只有1字節的可用信息,造成龐大的浪費。這種狀況常常發生于Telnet工作階段-大部分的鍵盤操作會產生1字節的數據并馬上提交。更糟的是,在慢速的網絡連線下,這類的數據包會大量地在同一時點傳輸,造成壅塞碰撞。 納格算法的工作方式是合并(coalescing)一定數量的輸出數據后一次提交。特別的是,只要有已提交的數據包尚未確認,發送者會持續緩沖數據包,直到累積一定數量的數據才提交。 總結算法開啟關閉的場景: 1. 如果 Nagle 算法關閉,對于小數據包,比如一次鼠標移動,點擊,客戶端都會立馬和服務端交互,實時響應度非常高,但頻繁的通信卻很占用不少網絡資源; 2. 如果 Nagle 算法開啟,算法會自動合并小數據包,等到達到一定大小(MSS)后,才會和服務端交互,優點是減少了通信次數,缺點是實時響應度會低一些。 Socket 創建時,默認是開啟 Nagle 算法的,可以根據實時性要求來選擇是否關閉 Nagle 算法。 #### 4.2 setSoLinger setSoLinger 方法主要用來設置 SO_LINGER 屬性值的。 注釋上大概是這個意思:在我們調用 close 方法時,默認是直接返回的,但如果給 SOLINGER 賦值,就會阻塞 close 方法,在 SOLINGER 時間內,等待通信雙方發送數據,如果時間過了,還未結束,將發送 TCP RST 強制關閉 TCP 。 我們看一下 setSoLinger 源碼: ``` // on 為 false,表示不啟用延時關閉,true 的話表示啟用延時關閉 // linger 為延時的時間,單位秒 public void setSoLinger(boolean on, int linger) throws SocketException { // 檢查是否已經關閉 if (isClosed()) throw new SocketException("Socket is closed"); // 不啟用延時關閉 if (!on) { getImpl().setOption(SocketOptions.SO_LINGER, new Boolean(on)); // 啟用延時關閉,如果 linger 為 0,那么會立即關閉 // linger 最大為 65535 秒,約 18 小時 } else { if (linger < 0) { throw new IllegalArgumentException("invalid value for SO_LINGER"); } if (linger > 65535) linger = 65535; getImpl().setOption(SocketOptions.SO_LINGER, new Integer(linger)); } } ``` #### 4.3 setOOBInline setOOBInline 方法主要使用設置 SO_OOBINLINE 屬性。 注釋上說:如果希望接受 TCP urgent data(TCP 緊急數據)的話,可以開啟該選項,默認該選項是關閉的,我們可以通過 Socket#sendUrgentData 方法來發送緊急數據。 查詢了很多資料,都建議盡可能的去避免設置該值,禁止使用 TCP 緊急數據。 #### 4.4 setSoTimeout setSoTimeout 方法主要是用來設置 SO_TIMEOUT 屬性的。 注釋上說:用來設置阻塞操作的超時時間,阻塞操作主要有: 1. ServerSocket.accept() 服務器等待客戶端的連接; 2. SocketInputStream.read() 客戶端或服務端讀取輸入超時; 3. DatagramSocket.receive()。 我們必須在必須在阻塞操作之前設置該選項, 如果時間到了,操作仍然在阻塞,會拋出 InterruptedIOException 異常(Socket 會拋出 SocketTimeoutException 異常,不同的套接字拋出的異常可能不同)。 對于 Socket 來說,超時時間如果設置成 0,表示沒有超時時間,阻塞時會無限等待。 #### 4.5 setSendBufferSize setSendBufferSize 方法主要用于設置 SO_SNDBUF 屬性的,入參是 int 類型,表示設置發送端(輸出端)的緩沖區的大小,單位是字節。 入參 size 必須大于 0,否則會拋出 IllegalArgumentException 異常。 一般我們都是采取默認的,如果值設置太小,很有可能導致網絡交互過于頻繁,如果值設置太大,那么交互變少,實時性就會變低。 #### 4.6 setReceiveBufferSize setReceiveBufferSize 方法主要用來設置 SO_RCVBUF 屬性的,入參是 int 類型,表示設置接收端的緩沖區的大小,單位是字節。 入參 size 必須大于 0,否則會拋出 IllegalArgumentException 異常。 一般來說,在套接字建立連接之后,我們可以隨意修改窗口大小,但是當窗口大小大于 64k 時,需要注意: 1. 必須在 Socket 連接客戶端之前設置緩沖值; 2. 必須在 ServerSocket 綁定本地地址之前設置緩沖值。 #### 4.7 setKeepAlive setKeepAlive 方法主要用來設置 SO_KEEPALIVE 屬性,主要是用來探測服務端的套接字是否還是存活狀態,默認設置是 false,不會觸發這個功能。 如果 SO_KEEPALIVE 開啟的話,TCP 自動觸發功能:如果兩小時內,客戶端和服務端的套接字之間沒有任何通信,TCP 會自動發送 keepalive 探測給對方,對方必須響應這個探測(假設是客戶端發送給服務端),預測有三種情況: 1. 服務端使用預期的 ACK 回復,說明一切正常; 2. 服務端回復 RST,表示服務端處于死機或者重啟狀態,終止連接; 3. 沒有得到服務端的響應(會嘗試多次),表示套接字已經關閉了。 #### 4.8 setReuseAddress setReuseAddress 方法主要用來設置 SO_REUSEADDR 屬性,入參是布爾值,默認是 false。 套接字在關閉之后,會等待一段時間之后才會真正的關閉,如果此時有新的套接字前來綁定同樣的地址和端口時,如果 setReuseAddress 為 true 的話,就可以綁定成功,否則綁定失敗。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看