Java面試Socket編程常用參數(shù)設(shè)置源碼問題分析
引導(dǎo)語
Socket 中文翻譯叫套接字,可能很多工作四五年的同學(xué)都沒有用過這個(gè) API,但只要用到這個(gè) API 時(shí),必然是在重要的工程的核心代碼處。
大家平時(shí)基本都在用開源的各種 rpc 框架,比如說 Dubbo、gRPC、Spring Cloud 等等,很少需要手寫網(wǎng)絡(luò)調(diào)用,以下三小節(jié)可以幫助大家補(bǔ)充這塊的內(nèi)容,當(dāng)你真正需要的時(shí)候,可以作為手冊(cè)示例。
本文和《ServerSocket 源碼及面試題》一文主要說 Socket 和 ServerSocket 的源碼,《工作實(shí)戰(zhàn):Socket 結(jié)合線程池的使用》這章主要說兩個(gè) API 在實(shí)際工作中如何落地。
1、Socket 整體結(jié)構(gòu)
Socket 的結(jié)構(gòu)非常簡(jiǎn)單,Socket 就像一個(gè)殼一樣,將套接字初始化、創(chuàng)建連接等各種操作包裝了一下,其底層實(shí)現(xiàn)都是 SocketImpl 實(shí)現(xiàn)的,Socket 本身的業(yè)務(wù)邏輯非常簡(jiǎn)單。
Socket 的屬性不多,有套接字的狀態(tài),SocketImpl,讀寫的狀態(tài)等等,源碼如下圖:
套接字的狀態(tài)變更都是有對(duì)應(yīng)操作方法的,比如套接字新建(createImpl 方法)后,狀態(tài)就會(huì)更改成 created = true,連接(connect)之后,狀態(tài)更改成 connected = true 等等。
2、初始化
Socket 的構(gòu)造器比較多,可以分成兩大類:
指定代理類型(Proxy)創(chuàng)建套節(jié)點(diǎn),一共有三種類型為:DIRECT(直連)、HTTP(HTTP、FTP 高級(jí)協(xié)議的代理)、SOCKS(SOCKS 代理),三種不同的代碼方式對(duì)應(yīng)的 SocketImpl 不同,分別是:PlainSocketImpl、HttpConnectSocketImpl、SocksSocketImpl,除了類型之外 Proxy 還指定了地址和端口;
默認(rèn) SocksSocketImpl 創(chuàng)建,并且需要在構(gòu)造器中傳入地址和端口,源碼如下:
// 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。
我們一起看一下這個(gè)構(gòu)造器調(diào)用的 this 底層構(gòu)造器的源碼:
// stream 為 true 時(shí),表示為stream socket 流套接字,使用 TCP 協(xié)議,比較穩(wěn)定可靠,但占用資源多 // stream 為 false 時(shí),表示為datagram socket 數(shù)據(jù)報(bào)套接字,使用 UDP 協(xié)議,不穩(wěn)定,但占用資源少 private Socket(SocketAddress address, SocketAddress localAddr, boolean stream) throws IOException { setImpl(); // backward compatibility if (address == null) throw new NullPointerException(); try { // 創(chuàng)建 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; } }
從源碼中可以看出:
- 在構(gòu)造 Socket 的時(shí)候,你可以選擇 TCP 或 UDP,默認(rèn)是 TCP;
- 如果構(gòu)造 Socket 時(shí),傳入地址和端口,那么在構(gòu)造的時(shí)候,就會(huì)嘗試在此地址和端口上創(chuàng)建套接字;
- Socket 的無參構(gòu)造器只會(huì)初始化 SocksSocketImpl,并不會(huì)和當(dāng)前地址端口綁定,需要我們手動(dòng)的調(diào)用 connect 方法,才能使用當(dāng)前地址和端口;
- Socket 我們可以理解成網(wǎng)絡(luò)溝通的語言層次的抽象,底層網(wǎng)絡(luò)創(chuàng)建、連接和關(guān)閉,仍然是 TCP 或 UDP 本身網(wǎng)絡(luò)協(xié)議指定的標(biāo)準(zhǔn),Socket 只是使用 Java 語言做了一層封裝,從而讓我們更方便地使用。
3、connect 連接服務(wù)端
connect 方法主要用于 Socket 客戶端連接上服務(wù)端,如果底層是 TCP 層協(xié)議的話,就是通過三次握手和服務(wù)端建立連接,為客戶端和服務(wù)端之間的通信做好準(zhǔn)備,底層源碼如下:
public void connect(SocketAddress endpoint, int timeout) throws IOException { }
connect 方法要求有兩個(gè)入?yún)?,第一個(gè)入?yún)⑹?SocketAddress,表示服務(wù)端的地址,我們可以使用 InetSocketAddress 進(jìn)行初始化,比如:new InetSocketAddress(“www.wenhe.com”, 2000)。
第二入?yún)⑹浅瑫r(shí)時(shí)間的意思(單位毫秒),表示客戶端連接服務(wù)端的最大等待時(shí)間,如果超過當(dāng)前等待時(shí)間,仍然沒有成功建立連接,拋 SocketTimeoutException 異常,如果是 0 的話,表示無限等待。
4、Socket 常用設(shè)置參數(shù)
Socket 的常用設(shè)置參數(shù)在 SocketOptions 類中都可以找到,接下來我們來一一分析下,以下理解大多來自類注釋和網(wǎng)絡(luò)。
4.1、setTcpNoDelay
此方法是用來設(shè)置 TCP_NODELAY 屬性的,屬性的注釋是這樣的:此設(shè)置僅僅對(duì) TCP 生效,主要為了禁止使用 Nagle 算法,true 表示禁止使用,false 表示使用,默認(rèn)是 false。
對(duì)于 Nagle 算法,我們引用維基百科上的解釋:
納格算法是以減少數(shù)據(jù)包發(fā)送量來增進(jìn) [TCP/IP] 網(wǎng)絡(luò)的性能,它由約翰·納格任職于Ford Aerospace時(shí)命名。
納格的文件[注 1]描述了他所謂的“小數(shù)據(jù)包問題”-某個(gè)應(yīng)用程序不斷地提交小單位的數(shù)據(jù),且某些常只占1字節(jié)大小。因?yàn)門CP數(shù)據(jù)包具有40字節(jié)的標(biāo)頭信息(TCP與IPv4各占20字節(jié)),這導(dǎo)致了41字節(jié)大小的數(shù)據(jù)包只有1字節(jié)的可用信息,造成龐大的浪費(fèi)。這種狀況常常發(fā)生于Telnet工作階段-大部分的鍵盤操作會(huì)產(chǎn)生1字節(jié)的數(shù)據(jù)并馬上提交。更糟的是,在慢速的網(wǎng)絡(luò)連線下,這類的數(shù)據(jù)包會(huì)大量地在同一時(shí)點(diǎn)傳輸,造成壅塞碰撞。
納格算法的工作方式是合并(coalescing)一定數(shù)量的輸出數(shù)據(jù)后一次提交。特別的是,只要有已提交的數(shù)據(jù)包尚未確認(rèn),發(fā)送者會(huì)持續(xù)緩沖數(shù)據(jù)包,直到累積一定數(shù)量的數(shù)據(jù)才提交。
總結(jié)算法開啟關(guān)閉的場(chǎng)景:
如果 Nagle 算法關(guān)閉,對(duì)于小數(shù)據(jù)包,比如一次鼠標(biāo)移動(dòng),點(diǎn)擊,客戶端都會(huì)立馬和服務(wù)端交互,實(shí)時(shí)響應(yīng)度非常高,但頻繁的通信卻很占用不少網(wǎng)絡(luò)資源;如果 Nagle 算法開啟,算法會(huì)自動(dòng)合并小數(shù)據(jù)包,等到達(dá)到一定大?。∕SS)后,才會(huì)和服務(wù)端交互,優(yōu)點(diǎn)是減少了通信次數(shù),缺點(diǎn)是實(shí)時(shí)響應(yīng)度會(huì)低一些。
Socket 創(chuàng)建時(shí),默認(rèn)是開啟 Nagle 算法的,可以根據(jù)實(shí)時(shí)性要求來選擇是否關(guān)閉 Nagle 算法。
4.2、setSoLinger
setSoLinger 方法主要用來設(shè)置 SO_LINGER 屬性值的。
注釋上大概是這個(gè)意思:在我們調(diào)用 close 方法時(shí),默認(rèn)是直接返回的,但如果給 SO_LINGER 賦值,就會(huì)阻塞 close 方法,在 SO_LINGER 時(shí)間內(nèi),等待通信雙方發(fā)送數(shù)據(jù),如果時(shí)間過了,還未結(jié)束,將發(fā)送 TCP RST 強(qiáng)制關(guān)閉 TCP 。
我們看一下 setSoLinger 源碼:
// on 為 false,表示不啟用延時(shí)關(guān)閉,true 的話表示啟用延時(shí)關(guān)閉 // linger 為延時(shí)的時(shí)間,單位秒 public void setSoLinger(boolean on, int linger) throws SocketException { // 檢查是否已經(jīng)關(guān)閉 if (isClosed()) throw new SocketException("Socket is closed"); // 不啟用延時(shí)關(guān)閉 if (!on) { getImpl().setOption(SocketOptions.SO_LINGER, new Boolean(on)); // 啟用延時(shí)關(guān)閉,如果 linger 為 0,那么會(huì)立即關(guān)閉 // linger 最大為 65535 秒,約 18 小時(shí) } 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 方法主要使用設(shè)置 SO_OOBINLINE 屬性。
注釋上說:如果希望接受 TCP urgent data(TCP 緊急數(shù)據(jù))的話,可以開啟該選項(xiàng),默認(rèn)該選項(xiàng)是關(guān)閉的,我們可以通過 Socket#sendUrgentData 方法來發(fā)送緊急數(shù)據(jù)。
查詢了很多資料,都建議盡可能的去避免設(shè)置該值,禁止使用 TCP 緊急數(shù)據(jù)。
4.4、setSoTimeout
setSoTimeout 方法主要是用來設(shè)置 SO_TIMEOUT 屬性的。
注釋上說:用來設(shè)置阻塞操作的超時(shí)時(shí)間,阻塞操作主要有:
- ServerSocket.accept() 服務(wù)器等待客戶端的連接;
- SocketInputStream.read() 客戶端或服務(wù)端讀取輸入超時(shí);
- DatagramSocket.receive()。
我們必須在必須在阻塞操作之前設(shè)置該選項(xiàng), 如果時(shí)間到了,操作仍然在阻塞,會(huì)拋出 InterruptedIOException 異常(Socket 會(huì)拋出 SocketTimeoutException 異常,不同的套接字拋出的異??赡懿煌?。
對(duì)于 Socket 來說,超時(shí)時(shí)間如果設(shè)置成 0,表示沒有超時(shí)時(shí)間,阻塞時(shí)會(huì)無限等待。
4.5、setSendBufferSize
setSendBufferSize 方法主要用于設(shè)置 SO_SNDBUF 屬性的,入?yún)⑹?int 類型,表示設(shè)置發(fā)送端(輸出端)的緩沖區(qū)的大小,單位是字節(jié)。
入?yún)?size 必須大于 0,否則會(huì)拋出 IllegalArgumentException 異常。
一般我們都是采取默認(rèn)的,如果值設(shè)置太小,很有可能導(dǎo)致網(wǎng)絡(luò)交互過于頻繁,如果值設(shè)置太大,那么交互變少,實(shí)時(shí)性就會(huì)變低。
4.6、setReceiveBufferSize
setReceiveBufferSize 方法主要用來設(shè)置 SO_RCVBUF 屬性的,入?yún)⑹?int 類型,表示設(shè)置接收端的緩沖區(qū)的大小,單位是字節(jié)。
入?yún)?size 必須大于 0,否則會(huì)拋出 IllegalArgumentException 異常。
一般來說,在套接字建立連接之后,我們可以隨意修改窗口大小,但是當(dāng)窗口大小大于 64k 時(shí),需要注意:
必須在 Socket 連接客戶端之前設(shè)置緩沖值;必須在 ServerSocket 綁定本地地址之前設(shè)置緩沖值。
4.7、setKeepAlive
setKeepAlive 方法主要用來設(shè)置 SO_KEEPALIVE 屬性,主要是用來探測(cè)服務(wù)端的套接字是否還是存活狀態(tài),默認(rèn)設(shè)置是 false,不會(huì)觸發(fā)這個(gè)功能。
如果 SO_KEEPALIVE 開啟的話,TCP 自動(dòng)觸發(fā)功能:如果兩小時(shí)內(nèi),客戶端和服務(wù)端的套接字之間沒有任何通信,TCP 會(huì)自動(dòng)發(fā)送 keepalive 探測(cè)給對(duì)方,對(duì)方必須響應(yīng)這個(gè)探測(cè)(假設(shè)是客戶端發(fā)送給服務(wù)端),預(yù)測(cè)有三種情況:
服務(wù)端使用預(yù)期的 ACK 回復(fù),說明一切正常;服務(wù)端回復(fù) RST,表示服務(wù)端處于死機(jī)或者重啟狀態(tài),終止連接;沒有得到服務(wù)端的響應(yīng)(會(huì)嘗試多次),表示套接字已經(jīng)關(guān)閉了。
4.8、setReuseAddress
setReuseAddress 方法主要用來設(shè)置 SO_REUSEADDR 屬性,入?yún)⑹遣紶栔?,默認(rèn)是 false。
套接字在關(guān)閉之后,會(huì)等待一段時(shí)間之后才會(huì)真正的關(guān)閉,如果此時(shí)有新的套接字前來綁定同樣的地址和端口時(shí),如果 setReuseAddress 為 true 的話,就可以綁定成功,否則綁定失敗。
5、總結(jié)
如果平時(shí)一直在做業(yè)務(wù)代碼,Socket 可能用到的很少,但面試問到網(wǎng)絡(luò)協(xié)議時(shí),或者以后有機(jī)會(huì)做做中間件的時(shí)候,就會(huì)有大概率會(huì)接觸到 Socket,所以多學(xué)學(xué),作為知識(shí)儲(chǔ)備也蠻好的。
以上就是Java編程Socket結(jié)構(gòu)常用參數(shù)設(shè)置源碼及面試題的詳細(xì)內(nèi)容,更多關(guān)于Java編程Socket結(jié)構(gòu)常用參數(shù)面試的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
處理Log4j2不能打印行號(hào)的問題(AsyncLogger)
這篇文章主要介紹了處理Log4j2不能打印行號(hào)的問題(AsyncLogger),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12JDK1.8使用的垃圾回收器和執(zhí)行GC的時(shí)長(zhǎng)以及GC的頻率方式
這篇文章主要介紹了JDK1.8使用的垃圾回收器和執(zhí)行GC的時(shí)長(zhǎng)以及GC的頻率方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05Spring中AOP的切點(diǎn)、通知、切點(diǎn)表達(dá)式及知識(shí)要點(diǎn)整理
這篇文章主要介紹了Spring中AOP的切點(diǎn)、通知、切點(diǎn)表達(dá)式及知識(shí)要點(diǎn)整理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03java中form以post、get方式提交數(shù)據(jù)中文亂碼問題總結(jié)
這篇文章主要介紹了java中form以post、get方式提交數(shù)據(jù)中文亂碼問題總結(jié),需要的朋友可以參考下2014-10-10springboot打包不同環(huán)境配置以及shell腳本部署的方法
這篇文章主要給大家介紹了關(guān)于springboot打包不同環(huán)境配置以及shell腳本部署的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用springboot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03?Java圖形化界面編程實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要介紹了Java圖形化界面編程實(shí)現(xiàn)簡(jiǎn)單計(jì)算器,下面文章圍繞Java圖形化界面編程實(shí)現(xiàn)簡(jiǎn)單計(jì)算器的相關(guān)資料展開詳細(xì)內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-01-01Java設(shè)計(jì)模式之迭代模式(Iterator模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之迭代模式(Iterator模式)介紹,本文用一個(gè)老師點(diǎn)名的現(xiàn)象描述了迭代模式的使用,需要的朋友可以參考下2015-03-03@Autowired自動(dòng)裝配,@Bean注入@Primary,@Qualifier優(yōu)先級(jí)講解
這篇文章主要介紹了@Autowired自動(dòng)裝配,@Bean注入@Primary,@Qualifier優(yōu)先級(jí),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Docker 部署 SpringBoot 項(xiàng)目整合 Redis 鏡像做訪問計(jì)數(shù)示例代碼
這篇文章主要介紹了Docker 部署 SpringBoot 項(xiàng)目整合 Redis 鏡像做訪問計(jì)數(shù)Demo,本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01