一 基礎(chǔ)
1.1 概述
1 網(wǎng)絡(luò)分層中的IOS七層模型至非、TCP/IP協(xié)議族、TCP糠聪、IP荒椭,這些有什么不同
2 TCP協(xié)議通信過程中的三次握手和四次揮手到底是什么流程?
1.2 軟件架構(gòu)
在生活中舰蟆,我們經(jīng)常用QQ趣惠、微信、百度云盤身害、Goole味悄、IE瀏覽器、火狐瀏覽器......塌鸯∈躺可以總結(jié)為兩大類,c/s(客戶端/服務(wù)端)和b/s(瀏覽器/服務(wù)器)結(jié)構(gòu)丙猬。這兩種架構(gòu)是兩臺計算機(jī)通過某中協(xié)議來網(wǎng)絡(luò)中進(jìn)行通信涨颜。
1.3 基本概念(通信協(xié)議/ip/端口)
通信協(xié)議
就是定義了在網(wǎng)絡(luò)中計算機(jī)之間進(jìn)行通信的一種規(guī)則。因為電腦是由許許多多加的廠商來做的茧球,如果傳出的數(shù)據(jù)格式不一樣庭瑰、傳輸數(shù)據(jù)格式、解析數(shù)據(jù)的格式不一樣抢埋,就沒法實現(xiàn)所有電腦通信弹灭。ip
在整個網(wǎng)絡(luò)中,電腦的唯一標(biāo)識揪垄。ip分為IPV4和IPV6,ipv4占用4個字節(jié)鲤屡,ipv6占用16個字節(jié)。目前使用較多的還是ipv4福侈。端口
在計算機(jī)中酒来,進(jìn)程的唯一標(biāo)識。端口號是用兩個字節(jié)表示肪凛,取值范圍為0-65535,0-1023基本為系統(tǒng)端口堰汉,我們寫的程序端口號應(yīng)在1024以上辽社。
二 計算機(jī)通信分層
2.1 七成模型、TCP/IP協(xié)議族翘鸭、TCP滴铅、IP這些是不是同一個東西?
1 ISO國際標(biāo)準(zhǔn)話組織在研究網(wǎng)絡(luò)通信就乓,建立了OSI模型(開放系統(tǒng)互聯(lián)參考模型)汉匙。即為標(biāo)準(zhǔn)的7層架構(gòu)。(理論上分層模型)
2 TCP/IP協(xié)議族生蚁,最早由美國國防部的ARPA網(wǎng)項目噩翠,也被DoD模型。(實踐過程中的模型)
3 TCP邦投,IP這是網(wǎng)絡(luò)分層中具體的協(xié)議伤锚。
2.2 訪問淘寶經(jīng)歷了網(wǎng)絡(luò)流程
1 我們在瀏覽器輸入淘寶的網(wǎng)址。
2 (本機(jī))瀏覽器將請求發(fā)送志衣,應(yīng)用層-》傳輸層-》網(wǎng)絡(luò)層-》數(shù)據(jù)鏈路層
3 (網(wǎng)絡(luò)傳輸過程)-》到路由器-》交換機(jī)
4 (淘寶服務(wù)器)-》到達(dá)淘寶服務(wù)器-》鏈路層-》網(wǎng)絡(luò)層-》傳輸層-》應(yīng)用層屯援,獲取數(shù)據(jù)
5 (響應(yīng)數(shù)據(jù))-》然后再原路返回。
三 網(wǎng)絡(luò)傳輸層解析
3.1 Socket是什么念脯?
Socket是位于應(yīng)用層和傳輸層的一個抽象層狞洋。提供了一套接口來調(diào)用TCP/IP協(xié)議的API。
3.2 Socket通信流程
3.3 網(wǎng)絡(luò)傳輸層的TCP協(xié)議詳解
概述
TCP是Transmission COntrol Protocol的簡稱绿店,中文名也叫做傳輸控制協(xié)議吉懊。它具有的特性如下。
a 數(shù)據(jù)傳輸前必須要建立連接惯吕,數(shù)據(jù)傳輸完惕它,必須釋放連接怕午。
b 傳輸?shù)臄?shù)據(jù)無差錯废登,不丟失,不重復(fù)郁惜,且順序和源數(shù)據(jù)一致堡距。
c 在傳輸?shù)倪^程中,數(shù)據(jù)拆分為不同的段兆蕉,也就是segment羽戒。
d 效率低,因為是面向連接的協(xié)議虎韵,通信之前必須要建立連接易稠。-
TCP首部進(jìn)行詳解
image
源端口和目的端口:數(shù)據(jù)從哪個進(jìn)程來到哪個進(jìn)程去。
序號和確認(rèn)號:TCP可靠傳輸?shù)年P(guān)鍵部分包蓝。序號是本報文段發(fā)送的數(shù)據(jù)組的第一個字節(jié)的序號驶社。在TCP傳輸流中,每個字節(jié)一個序號。
URG:表示本報文段中發(fā)送的數(shù)據(jù)是否包含緊急數(shù)據(jù)吁讨。URG=1壮池,表示有緊急數(shù)據(jù)。后面的緊急指針字段
只有當(dāng)URG=1時才有效份乒。
ACK:表示是否前面的確認(rèn)號字段是否有效恕汇。ACK=1,表示有效或辖。只有當(dāng)ACK=1時瘾英,前面的確認(rèn)號字段才
有效。TCP規(guī)定孝凌,連接建立后方咆,ACK必須為1。
PSH:告訴對方收到該報文段后是否應(yīng)該立即把數(shù)據(jù)推送給上層蟀架。如果為1瓣赂,則表示對方應(yīng)當(dāng)立即把數(shù)據(jù)
提交給上層,而不是緩存起來片拍。
RST:只有當(dāng)RST=1時才有用煌集。如果你收到一個RST=1的報文,說明你與主機(jī)的連接出現(xiàn)了嚴(yán)重錯誤(如
主機(jī)崩潰)捌省,必須釋放連接苫纤,然后再重新建立連接「倩海或者說明你上次發(fā)送給主機(jī)的數(shù)據(jù)有問題卷拘,主機(jī)拒絕
響應(yīng)。
SYN:在建立連接時使用祝高,用來同步序號栗弟。當(dāng)SYN=1,ACK=0時工闺,表示這是一個請求建立連接的報文段乍赫;
當(dāng)SYN=1,ACK=1時陆蟆,表示對方同意建立連接雷厂。SYN=1,說明這是一個請求建立連接或同意建立連接的
報文叠殷。只有在前兩次握手中SYN才置為1改鲫。
FIN:標(biāo)記數(shù)據(jù)是否發(fā)送完畢。如果FIN=1,就相當(dāng)于告訴對方:“我的數(shù)據(jù)已經(jīng)發(fā)送完畢像棘,你可以釋放
連接了纫塌。
窗口:滑動窗口大小,用來告知發(fā)送端接受端的緩存大小讲弄,以此控制發(fā)送端發(fā)送數(shù)據(jù)的速率措左,從而
達(dá)到流量控制。
選項和填充:最常見的可選字段是最長報文大小避除,又稱為MSS(Maximum Segment Size)怎披,每個連接
方通常都在通信的第一個報文段(為建立連接而設(shè)置SYN標(biāo)志為1的那個段)中指明這個選項,它表示本
端所能接受的最大報文段的長度瓶摆。選項長度不一定是32位的整數(shù)倍凉逛,所以要加填充位,即在這個字段中
加入額外的零群井,以保證TCP頭是32的整數(shù)倍
數(shù)據(jù)部分: TCP 報文段中的數(shù)據(jù)部分是可選的状飞。在一個連接建立和一個連接終止時,雙方交換的報文段
僅有 TCP 首部书斜。如果一方?jīng)]有數(shù)據(jù)要發(fā)送诬辈,也使用沒有任何數(shù)據(jù)的首部來確認(rèn)收到的數(shù)據(jù)。在處理超時
的許多情況中荐吉,也會發(fā)送不帶任何數(shù)據(jù)的報文段焙糟。
四 TCP斷開和連接的原理刨析(三次握手和四次揮手)
4.1 數(shù)據(jù)在傳遞過程中,字段解釋
SYN(synchronous建立連接) 請求建立連接样屠,并在其序列號字段進(jìn)行序列號的初始值設(shè)定穿撮。建立連接,設(shè)置為1痪欲。
ACK(acknowledgement 確認(rèn)) 確認(rèn)號是否有效悦穿,一般置為1
PSH(push傳送) 提示接受端應(yīng)用程序立即從TCP緩沖區(qū)把數(shù)據(jù)讀走。
FIN(finish結(jié)束) 希望斷開連接业踢。
RST(reset重置) 對方要求重新建立連接栗柒,復(fù)位。
URG(urgent緊急) 緊急指針是否有效陨亡。為2傍衡,表示某一位被優(yōu)先處理深员。
4.2 BIO
4.2.1 BIO代碼實現(xiàn)
//服務(wù)端代碼
public class ServerSocket {
public static void main(String[] args) throws Exception {
//創(chuàng)建ServerSocket對象负蠕,用于客戶端的連接
java.net.ServerSocket serverSocket = new java.net.ServerSocket(8989);
//定義輸入流對象讀取數(shù)據(jù)
byte[] bytes = new byte[1024];
try {
while (true) {
System.out.println("服務(wù)端發(fā)生阻塞,等待連接....");
//調(diào)用accept方法監(jiān)聽客戶端倦畅,阻塞方法
Socket accept = serverSocket.accept();
//調(diào)用Socket對象的方法獲取輸入流對象
InputStream inputStream = ((Socket) accept).getInputStream();
System.out.println("服務(wù)端發(fā)生阻塞遮糖,等待接收數(shù)據(jù)....");
int read = inputStream.read(bytes);
System.out.println(new String(bytes, 0, read));
//關(guān)閉資源
accept.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
}
}
}
//客戶端代碼
public class ClientSocket {
public static void main(String[] args) throws Exception {
//創(chuàng)建Socket對象,與服務(wù)端Socket建立連接
Socket socket=new Socket("127.0.0.1",8989);
//獲取輸出流對象
OutputStream outputStream=socket.getOutputStream();
System.out.println("客戶端阻塞叠赐,接收鍵盤輸入....");
//接收鍵盤輸入欲账,模擬延遲消息發(fā)送
Scanner scanner=new Scanner(System.in);
String scannerString=scanner.next();
outputStream.write(scannerString.getBytes());
System.out.println("客戶端錄入完成....");
//使用輸出流對象寫入數(shù)據(jù)
// outputStream.write("itheima-TCP".getBytes());
//釋放資源
socket.close();
}
}
4.2.2 BIO中API講解
- Socket
a 構(gòu)造方法
Socket():無參構(gòu)造方法屡江。
Socket(InetAddress address,int port):創(chuàng)建一個流套接字并將其連接到指定 IP 地址的指定端口。
Socket(InetAddress address,int port,InetAddress localAddr,int localPort):創(chuàng)建一個套接字并將其連接到指定遠(yuǎn)程地址上的指定遠(yuǎn)程端口赛不。
Socket(String host,int port):創(chuàng)建一個流套接字并將其連接到指定主機(jī)上的指定端口惩嘉。
Socket(String host,int port,InetAddress localAddr,int localPort):創(chuàng)建一個套接字并將其連接到指定遠(yuǎn)程地址上的指定遠(yuǎn)程端口。Socket 會通過調(diào)用 bind() 函數(shù)來綁定提供的本地地址及端口踢故。
b 普通方法
void bind(SocketAddress bindpoint):將套接字綁定到本地地址文黎。
void close():關(guān)閉此套接字。
void connect(SocketAddress endpoint):將此套接字連接到服務(wù)器殿较。
InetAddress getInetAddress():返回套接字的連接地址耸峭。
InetAddress getLocalAddress():獲取套接字綁定的本地地址。
InputStream getInputStream():返回此套接字的輸入流淋纲。
OutputStream getOutputStream():返回此套接字的輸出流劳闹。
SocketAddress getLocalSocketAddress():返回此套接字綁定的端點地址,如果尚未綁定則返回null洽瞬。
SocketAddress getRemoteSocketAddress():返回此套接字的連接的端點地址本涕,如果尚未連接則返回 null。
int getLoacalPort():返回此套接字綁定的本地端口伙窃。
intgetPort():返回此套接字連接的遠(yuǎn)程端口
- WebSocket
a 構(gòu)造方法
ServerSocket():無參構(gòu)造方法偏友。
ServerSocket(int port):創(chuàng)建綁定到特定端口的服務(wù)器套接字。
ServerSocket(int port,int backlog):使用指定的 backlog 創(chuàng)建服務(wù)器套接字并將其綁定到指定的本地端口对供。
ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口位他、監(jiān)聽 backlog 和要綁定到本地的 IP 地址創(chuàng)建服務(wù)器
b 普通方法
Server accept():監(jiān)聽并接收到此套接字的連接。
void bind(SocketAddress endpoint):將 ServerSocket 綁定到指定地址(IP 地址和端口號)产场。
void close():關(guān)閉此套接字鹅髓。
InetAddress getInetAddress():返回此服務(wù)器套接字的本地地址。
int getLocalPort():返回此套接字監(jiān)聽的端口京景。
SocketAddress getLocalSoclcetAddress():返回此套接字綁定的端口的地址窿冯,如果尚未綁定則返回 null。
int getReceiveBufferSize():獲取此 ServerSocket 的 SO_RCVBUF 選項的值确徙,該值是從ServerSocket 接收的套接字的建議緩沖區(qū)大小醒串。
accept()方法會返回一個和客戶端Socket對象相連的Socket對象。使用Socket的getOutputStream可以向客戶端發(fā)送信息鄙皇。使getIutputStreamke可以獲取客戶端傳過來數(shù)據(jù)芜赌。
4.3 TCP協(xié)議中,建立連接三次握手
-
簡述
在tcp協(xié)議中伴逸,雙方建立連接的時候是需要三次握手缠沈。這個連接建立需要一方主動打開,另外一方被動打開的。下圖為建立連接圖解洲愤。
image -
網(wǎng)絡(luò)請求建立連接,經(jīng)歷三次握手流程
image
a 第一次握手
image
在第一次"握手"時颓芭,客戶端向服務(wù)端發(fā)送SYN標(biāo)志位,目的是與服務(wù)端建立連接柬赐。Seq代表sequence亡问,number(發(fā)送數(shù)據(jù)流序號), 例如:Seq的值是5,說明在數(shù)據(jù)流中曾經(jīng)一共發(fā)送了 1, 2, 3,4 這4次數(shù)據(jù)肛宋。而在本次"握手"中, Seq的值是0玛界,代表客戶端曾經(jīng)沒有給服務(wù)端發(fā)送數(shù)據(jù)。另外Len=0也可以看出來是沒有數(shù)據(jù)可供發(fā)送的悼吱,客戶端僅僅發(fā)送一個SYN標(biāo)志位到服端代表要進(jìn)行連接慎框。
b 第二次握手
第二次"握手"時,服務(wù)端向客戶端發(fā)送 SYN ACK 標(biāo)志位后添,其中ACK標(biāo)志位表示是對收到的數(shù)據(jù)包的確認(rèn)笨枯,說明服務(wù)端接收到了客戶端的連接。ACK的值是1遇西,表示服務(wù)端期待下一次從客戶端發(fā)送數(shù)據(jù)流的序列號是1馅精,而Seq=0代表服務(wù)端曾經(jīng)并沒有給客戶端發(fā)送數(shù)據(jù),而本次也沒有發(fā)送數(shù)據(jù)粱檀,因為Len=0也證明了這一點洲敢。
c 第三次握手
第三次“握手”時,客戶端向服務(wù)端發(fā)送的ACK標(biāo)志位為1, Seq的值是1茄蚯。Seq=l代表這正是服務(wù)端所期望的Ack=1压彭。Len=0說明客戶端這次還是沒有向服務(wù)端傳遞數(shù)據(jù),而客戶端向服務(wù)端發(fā)送ACK 標(biāo)志位為1的信息渗常,說明客戶端期待服務(wù)端下一次傳送的Seq的值是1壮不。
- 為什么要進(jìn)行三次握手
為了防止服務(wù)器端開啟一些無用的連接,增加服務(wù)器開銷皱碘。以及防止已失效的連接請求報文段突然又傳送到了服務(wù)端询一,因而產(chǎn)生錯誤。
4.4 TCP協(xié)議中癌椿,連接斷開時候(四次揮手)
-
四次揮手
即TCP連接的釋放(解除)健蕊。連接的釋放必須是一方主動釋放,另一方被動釋放踢俄。以下為客戶端主動發(fā)起釋放連接的圖解:
image
簡述流程
a 客戶端到服務(wù)端缩功,我要關(guān)了。
b 服務(wù)端到客戶端褪贵,好的掂之,我收到了。
c 服務(wù)端到客戶端脆丁,我也關(guān)了世舰。
d 客戶端到服務(wù)端,好的槽卫,收到跟压。 -
四次揮手執(zhí)行流程
image
a 第一次揮手
在第一次"揮手"時,客戶端到服務(wù)器發(fā)送標(biāo)志位FIN ACK,告知服務(wù)端客戶端關(guān)閉了歼培。Seq=1表示本次數(shù)據(jù)流的序號為1震蒋,Ack=1表示客戶端期望服務(wù)端下一次發(fā)送的數(shù)據(jù)流的序號為1。len=0躲庄,說明沒有數(shù)據(jù)傳輸?shù)椒?wù)端查剖。
b 第二次揮手
在第二次"揮手"時,服務(wù)端向客戶端發(fā)送標(biāo)志位ACK,Seq=1代表的正是客戶端想看的Ack=1。Ack=2表示服務(wù)端期望下一次客戶端發(fā)送的數(shù)據(jù)流的序號為2噪窘。len=0,說明沒有數(shù)據(jù)傳輸?shù)娇蛻舳恕?/p>
c 第三次揮手
在第三次"揮手"時,服務(wù)端向客戶端發(fā)送標(biāo)志位FIN ACK,告知客戶端服務(wù)端關(guān)閉了笋庄。Seq=1代表的正是客戶端想看的Ack=1。Ack=2表示服務(wù)端期望下一次客戶端發(fā)送的數(shù)據(jù)流的序號為2倔监。len=0,說明沒有數(shù)據(jù)傳輸?shù)娇蛻舳恕?/p>
d 第四次揮手
在第四次"揮手"時,客戶端向服務(wù)端發(fā)送標(biāo)志位ACK直砂,告知服務(wù)端客戶端已經(jīng)收到服務(wù)端關(guān)閉信息。Seq=2
代表的正是服務(wù)端想看的Ack=2浩习,ACK=2表示客戶端期望下一次服務(wù)端發(fā)送的數(shù)據(jù)流的序號為2静暂。
注意 BIO存在問題
1、客戶端已經(jīng)連接服務(wù)端谱秽,尚未發(fā)送數(shù)據(jù)洽蛀,read阻塞
2、新的客戶端無法正常連接
解決辦法
1疟赊、線程解決(mysql客戶端連接服務(wù)器)
2辱士、線程池解決(線程池泄露)
3、NIO解決
4听绳、websocket
五 NIO編程
5.1 概述
NIO又稱為非阻塞IO,是JDK1.4提出的新的IO模型
5.2 組件詳細(xì)介紹
5.2.1 Buffer(緩沖區(qū))
概述
按照物理區(qū)分為:直接緩沖區(qū)和堆字節(jié)緩沖區(qū)颂碘。
Buffer模式:寫模式和讀模式緩沖區(qū)執(zhí)行原理
a 三個屬性(Buffer的三個屬性)
capacity(容量)、position(位置)椅挣、limit(限制)
b 寫模式
capacity:數(shù)組中可以存儲元素的個數(shù)
position:下一次可插入元素位置头岔,默認(rèn)值為0,每添加一個元素都向后移動一位鼠证,最大值:capacity - 1
limit:在寫的模式下峡竣,limit表示第一個不可寫的位置(默認(rèn)第一個不可寫的位置,應(yīng)該是數(shù)組容量值得下一個位置量九,即默認(rèn)值為capacity)
c 讀模式
capacity:數(shù)組中可以存儲元素的格數(shù)适掰。
position:Buffer由寫模式變化為讀模式颂碧,position會從置0,在進(jìn)行讀取數(shù)據(jù)時类浪,position向前移動到下一個可讀的位置载城。
limit:第一個不可讀位置,當(dāng)寫模式切換到讀模式费就,limit設(shè)置寫模式下的position值诉瓦。即能讀到之氣那所有寫入的數(shù)據(jù)。
5.2.2 Channnl(通道)
1 概述
類似于流進(jìn)行數(shù)據(jù)傳輸力细,但是和流不同睬澡。流是單向的,大部分功能比較單一眠蚂,要么進(jìn)行讀要么進(jìn)行寫煞聪。
通道的使用必須要結(jié)合Buffer。
5.2.3 Selector(選擇器)
-
概述
每一個通道都存在一個線程對其處理逝慧。在高并發(fā)情況下米绕,就會存在很多通道,就會創(chuàng)建很多線程對象馋艺,造成內(nèi)存占用率升高栅干,增加cpu在多個線程之間切換的時間。因此不使用高并發(fā)場景下捐祠。
image -
NIO使用通道的改良
我一個線程處理多個任務(wù)通道的任務(wù)的機(jī)制碱鳞,在NIO中成為多路復(fù)用。使用后IO復(fù)用后踱蛀,只需一個線程能對多個通道進(jìn)行處理窿给,對于高并發(fā)的業(yè)務(wù)場景有優(yōu)勢。補(bǔ)充如下:
線程數(shù)隨著通道多少進(jìn)行動態(tài)的增減來進(jìn)行適配率拒。多路復(fù)用的核心目的使用最少的線程數(shù)去操作更多的通道崩泡。創(chuàng)建線程的個數(shù)根據(jù)通道個數(shù)來決定。每注冊1023個通道就創(chuàng)建一個線程
image
5.2.4 NIO實例
public class SocketNioServer {
public static void main(String[] args) throws Exception {
//定義通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//綁定地址
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8080));
//設(shè)置為非阻塞模式
serverSocketChannel.configureBlocking(false);
//開啟一個選擇器
Selector selector = Selector.open();
//注冊
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
System.out.println("等待連接猬膨,阻塞中.....");
int count = selector.select();
if (count != 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍歷集合
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//客戶端已經(jīng)連接角撞,尚未發(fā)送數(shù)據(jù)
if (selectionKey.isAcceptable()) {
System.out.println("客戶端已經(jīng)連接,尚未發(fā)送數(shù)據(jù)....");
//獲取通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = ssc.accept();
//設(shè)置非阻塞
socketChannel.configureBlocking(false);
//注冊到選擇器
socketChannel.register(selector, SelectionKey.OP_READ);
}
//任務(wù)就緒
else if (selectionKey.isReadable()) {
System.out.println("客戶端成功發(fā)送數(shù)據(jù)");
//獲取通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//讀取信息
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(buffer);
while (read > 0) {
//切換緩沖區(qū)的模式
buffer.flip();
System.out.println(new String(buffer.array(), 0, read));
//清除緩沖區(qū)
buffer.clear();
read = socketChannel.read(buffer);
}
socketChannel.close();//釋放資源
}
iterator.remove();
}
}
}
}
}