1. 網(wǎng)絡(luò)編程概念
首先注意呈驶, Socket不是Java中獨(dú)有的概念捂掰,而是一個(gè)語(yǔ)言無(wú)關(guān)標(biāo)準(zhǔn)羡鸥。 任何可以實(shí)現(xiàn)網(wǎng)絡(luò)編程的編程語(yǔ)言都有 Socket 。
1.1 什么是 Socket
??網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換鉴腻,這個(gè)連接的一端稱為一個(gè) socket迷扇。
??建立網(wǎng)絡(luò)通信連接至少要一個(gè)端口號(hào)百揭。socket 本質(zhì)是編程接口(API),對(duì) TCP/IP 的封裝蜓席, TCP/IP 也要提供可供程序員做網(wǎng)絡(luò)開(kāi)發(fā)所用的接口信峻,這就是 Socket 編程接口;HTTP 是轎車瓮床, 提供了封裝或者顯示數(shù)據(jù)的具體形式;Socket 是發(fā)動(dòng)機(jī)产镐,提供了網(wǎng)絡(luò)通信的能力隘庄。
?? Socket 的英文原義是“孔”或“插座”。作為 BSD UNIX 的進(jìn)程通信機(jī)制癣亚,取后一種意思丑掺。通常也稱作"套接字",用于描述 IP 地址和端口述雾,是一個(gè)通信鏈的句柄街州,可以用來(lái)實(shí)現(xiàn)不同虛 擬機(jī)或不同計(jì)算機(jī)之間的通信。在 Internet 上的主機(jī)一般運(yùn)行了多個(gè)服務(wù)軟件玻孟,同時(shí)提供幾 種服務(wù)唆缴。每種服務(wù)都打開(kāi)一個(gè) Socket,并綁定到一個(gè)端口上黍翎,不同的端口對(duì)應(yīng)于不同的服務(wù)面徽。 Socket 正如其英文原義那樣,像一個(gè)多孔插座匣掸。一臺(tái)主機(jī)猶如布滿各種插座的房間趟紊,每個(gè)插 座有一個(gè)編號(hào),有的插座提供 220 伏交流電碰酝, 有的提供 110 伏交流電霎匈,有的則提供有線電 視節(jié)目。 客戶軟件將插頭插到不同編號(hào)的插座送爸,就可以得到不同的服務(wù)铛嘱。
1.2 Socket 連接步驟
??根據(jù)連接啟動(dòng)的方式以及本地套接字要連接的目標(biāo),套接字之間的連接過(guò)程可以分為三 個(gè)步驟:服務(wù)器監(jiān)聽(tīng)碱璃,客戶端請(qǐng)求弄痹,連接確認(rèn)∏镀鳎【如果包含數(shù)據(jù)交互+斷開(kāi)連接肛真,那么一共是 五個(gè)步驟】
- 服務(wù)器監(jiān)聽(tīng):是服務(wù)器端套接字并不定位具體的客戶端套接字,而是處于等待連 接的狀態(tài)爽航,實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)蚓让。
- 客戶端請(qǐng)求:是指由客戶端的套接字提出連接請(qǐng)求乾忱,要連接的目標(biāo)是服務(wù)器端的 套接字。為此历极,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字窄瘟,指出服務(wù)器端套 接字的地址和端口號(hào),然后就向服務(wù)器端套接字提出連接請(qǐng)求趟卸。
- 連接確認(rèn):是指當(dāng)服務(wù)器端套接字監(jiān)聽(tīng)到或者說(shuō)接收到客戶端套接字的連接請(qǐng)求蹄葱, 它就響應(yīng)客戶端套接字的請(qǐng)求,建立一個(gè)新的線程锄列,把服務(wù)器端套接字的描述發(fā)給客戶端图云, 一旦客戶端確認(rèn)了此描述,連接就建立好了邻邮。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽(tīng)狀態(tài)竣况,繼續(xù)接 收其他客戶端套接字的連接請(qǐng)求。
image.png
具體參考TCP三次握手,與四次揮手
1.3 Java 中的 Socket
??在 java.net 包是網(wǎng)絡(luò)編程的基礎(chǔ)類庫(kù)筒严。其中 ServerSocket 和 Socket 是網(wǎng)絡(luò)編程的基礎(chǔ)類 型丹泉。ServerSocket 是服務(wù)端應(yīng)用類型。Socket 是建立連接的類型鸭蛙。當(dāng)連接建立成功后摹恨,服務(wù)器和客戶端都會(huì)有一個(gè) Socket 對(duì)象示例,可以通過(guò)這個(gè) Socket 對(duì)象示例娶视,完成會(huì)話的所有 操作睬塌。
??對(duì)于一個(gè)完整的網(wǎng)絡(luò)連接來(lái)說(shuō),Socket 是平等的歇万,沒(méi)有服務(wù)器客戶端分級(jí)情況揩晴。
2. 什么是同步和異步
??同步和異步是針對(duì)應(yīng)用程序和內(nèi)核的交互而言的,同步指的是用戶進(jìn)程觸發(fā) IO 操作并 等待或者輪詢的去查看 IO 操作是否就緒贪磺,而異步是指用戶進(jìn)程觸發(fā) IO 操作以后便開(kāi)始做自 己的事情硫兰,而當(dāng) IO 操作已經(jīng)完成的時(shí)候會(huì)得到 IO 完成的通知。
??以銀行取款為例:
??同步: 自己親自出馬持銀行卡到銀行取錢(使用同步 IO 時(shí)寒锚,Java 自己處理 IO 讀寫)劫映;
??異步 : 委托一小弟拿銀行卡到銀行取錢,然后給你(使用異步 IO 時(shí)刹前,Java 將 IO 讀寫 委托給 OS 處理泳赋,需要將數(shù)據(jù)緩沖區(qū)地址和大小傳給 OS(銀行卡和密碼),OS 需要支持異步 IO 操作 API)喇喉;
3. 什么是阻塞和非阻塞
??阻塞和非阻塞是針對(duì)于進(jìn)程在訪問(wèn)數(shù)據(jù)的時(shí)候祖今,根據(jù) IO 操作的就緒狀態(tài)來(lái)采取的不同 方式,說(shuō)白了是一種讀取或者寫入操作方法的實(shí)現(xiàn)方式,阻塞方式下讀取或者寫入函數(shù)將一 直等待千诬,而非阻塞方式下耍目,讀取或者寫入方法會(huì)立即返回一個(gè)狀態(tài)值。
??以銀行取款為例:
??阻塞 : ATM 排隊(duì)取款徐绑,你只能等待(使用阻塞 IO 時(shí)邪驮,Java 調(diào)用會(huì)一直阻塞到讀寫完 成才返回);
??非阻塞 : 柜臺(tái)取款傲茄,取個(gè)號(hào)毅访,然后坐在椅子上做其它事,等號(hào)廣播會(huì)通知你辦理盘榨,沒(méi) 到號(hào)你就不能去俺抽,你可以不斷問(wèn)大堂經(jīng)理排到了沒(méi)有,大堂經(jīng)理如果說(shuō)還沒(méi)到你就不能去(使 用非阻塞 IO 時(shí)较曼,如果不能讀寫 Java 調(diào)用會(huì)馬上返回,當(dāng) IO 事件分發(fā)器通知可讀寫時(shí)再繼 續(xù)進(jìn)行讀寫振愿,不斷循環(huán)直到讀寫完成)
4. BIO編程
??Blocking IO: 同步阻塞的編程方式捷犹。
??BIO 編程方式通常是在 JDK1.4 版本之前常用的編程方式。編程實(shí)現(xiàn)過(guò)程為:首先在服務(wù) 端啟動(dòng)一個(gè) ServerSocket 來(lái)監(jiān)聽(tīng)網(wǎng)絡(luò)請(qǐng)求冕末,客戶端啟動(dòng) Socket 發(fā)起網(wǎng)絡(luò)請(qǐng)求萍歉,默認(rèn)情況下 ServerSocket 回建立一個(gè)線程來(lái)處理此請(qǐng)求,如果服務(wù)端沒(méi)有線程可用档桃,客戶端則會(huì)阻塞等 待或遭到拒絕枪孩。
??且建立好的連接,在通訊過(guò)程中藻肄,是同步的蔑舞。在并發(fā)處理效率上比較低。大致結(jié)構(gòu)如下:
??同步并阻塞嘹屯,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程攻询,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就 需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開(kāi)銷州弟,當(dāng)然可 以通過(guò)線程池機(jī)制改善钧栖。
??BIO 方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高婆翔,并發(fā)局限于應(yīng)用中拯杠,JDK1.4 以前的唯一選擇,但程序直觀簡(jiǎn)單易理解啃奴。
??使用線程池機(jī)制改善后的 BIO 模型圖如下:
5. NIO 編程
??Unblocking IO(New IO): 同步非阻塞的編程方式潭陪。
??NIO 本身是基于事件驅(qū)動(dòng)思想來(lái)完成的,其主要想解決的是 BIO 的大并發(fā)問(wèn)題,NIO 基 于 Reactor畔咧,當(dāng) socket 有流可讀或可寫入 socket 時(shí)茎芭,操作系統(tǒng)會(huì)相應(yīng)的通知引用程序進(jìn)行處理,應(yīng)用再將流讀取到緩沖區(qū)或?qū)懭氩僮飨到y(tǒng)誓沸。也就是說(shuō)梅桩,這個(gè)時(shí)候,已經(jīng)不是一個(gè)連接就 要對(duì)應(yīng)一個(gè)處理線程了拜隧,而是有效的請(qǐng)求宿百,對(duì)應(yīng)一個(gè)線程,當(dāng)連接沒(méi)有數(shù)據(jù)時(shí)洪添,是沒(méi)有工作線程來(lái)處理的垦页。
??NIO 的最重要的地方是當(dāng)一個(gè)連接創(chuàng)建后,不需要對(duì)應(yīng)一個(gè)線程干奢,這個(gè)連接會(huì)被注冊(cè)到 多路復(fù)用器上面痊焊,所以所有的連接只需要一個(gè)線程就可以搞定,當(dāng)這個(gè)線程中的多路復(fù)用器 進(jìn)行輪詢的時(shí)候忿峻,發(fā)現(xiàn)連接上有請(qǐng)求的話薄啥,才開(kāi)啟一個(gè)線程進(jìn)行處理,也就是一個(gè)請(qǐng)求一個(gè)線程模式逛尚。
??在 NIO 的處理方式中垄惧,當(dāng)一個(gè)請(qǐng)求來(lái)的話,開(kāi)啟線程進(jìn)行處理绰寞,可能會(huì)等待后端應(yīng)用的 資源(JDBC 連接等)到逊,其實(shí)這個(gè)線程就被阻塞了,當(dāng)并發(fā)上來(lái)的話滤钱,還是會(huì)有 BIO 一樣的問(wèn)題觉壶。
??同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)通道件缸,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè) 到多路復(fù)用器上掰曾,多路復(fù)用器輪詢到連接有 I/O 請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。
??NIO 方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu)停团,比如聊天服務(wù)器旷坦,并發(fā)局 限于應(yīng)用中,編程復(fù)雜佑稠,JDK1.4 開(kāi)始支持秒梅。
Buffer:ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer;
Channel:SocketChannel,ServerSocketChannel;
Selector:Selector,AbstractSelector;
SelectionKey:OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT;
6. AIO 編程
??Asynchronous IO: 異步非阻塞的編程方式
??與 NIO 不同,當(dāng)進(jìn)行讀寫操作時(shí)舌胶,只須直接調(diào)用 API 的 read 或 write 方法即可捆蜀。這兩種方法均為異步的,對(duì)于讀操作而言,當(dāng)有流可讀取時(shí)辆它,操作系統(tǒng)會(huì)將可讀的流傳入 read 方法的緩沖區(qū)誊薄,并通知應(yīng)用程序;對(duì)于寫操作而言锰茉,當(dāng)操作系統(tǒng)將write方法傳遞的流寫入完 畢時(shí)呢蔫,操作系統(tǒng)主動(dòng)通知應(yīng)用程序。即可以理解為飒筑,read/write 方法都是異步的片吊,完成后會(huì) 主動(dòng)調(diào)用回調(diào)函數(shù)。在 JDK1.7 中协屡,這部分內(nèi)容被稱作 NIO.2俏脊,主要在 java.nio.channels 包下增加了下面四個(gè)異步通道:
??1.AsynchronousSocketChannel
??2.AsynchronousServerSocketChannel
??3.AsynchronousSocketChannel
??4.AsynchronousServerSocketChannel
??異步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求一個(gè)線程肤晓,客戶端的 I/O 請(qǐng)求都是由 OS 先完成了再通知服務(wù)器應(yīng)用去啟動(dòng)線程進(jìn)行處理爷贫。
??AIO 方式使用于連接數(shù)目多且連接比較長(zhǎng)(重操作)的架構(gòu),比如相冊(cè)服務(wù)器补憾,充分調(diào) 用 OS 參與并發(fā)操作漫萄,編程比較復(fù)雜,JDK7 開(kāi)始支持余蟹。