# 網絡IO模型變化
涉及到網絡模型有如下幾個名稱概念:
`同步、異步、阻塞、非阻塞`。
- 同步:應用程序自己完成讀寫操作。
- 異步:內核完成讀寫操作,就好像程序沒有訪問IO,而是直接訪問了內核緩沖區。
- 阻塞:調用阻塞方法時一定會拿到返回值。
- 非阻塞:調用非阻塞方法時可能拿不到返回值。
由這些可以組成多種不同的網絡模型:
- 同步阻塞:BIO
- 同步非阻塞:NIO、多路復用
- 異步非阻塞:AIO
- 異步阻塞:沒有意義!
使用命令:
~~~
strace -off -o out cmd
~~~
可以用于最終每個線程的系統調用情況,輸出為out文件。
C10K問題:
單機是否能夠支持1萬個鏈接?
下面對C10K問題進行性能壓測:
## BIO
代碼:
~~~
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket();
server.bind(new InetSocketAddress( 9090), BACK_LOG);
server.setReceiveBufferSize(RECEIVE_BUFFER);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("server up use 9090!");
while (true) {
try {
System.in.read(); //分水嶺:
Socket client = server.accept();
System.out.println("client port: " + client.getPort());
new Thread(
() -> {
while (true) {
try {
InputStream in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
char[] data = new char[1024];
int num = reader.read(data);
if (num > 0) {
System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
} else if (num == 0) {
System.out.println("client readed nothing!");
continue;
} else {
System.out.println("client readed -1...");
client.close();
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
).start();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
~~~
在上面服務端的代碼在運行過程中,while死循環一直在等待連接,當發生accept(系統調用)方法時,每次都新建了一個線程進行處理,同時操作內核會克隆一個內核級的線程。當然可以采用池化的技術增加線程的利用率。這也是BIO慢的原因。整個BIO的弊端就是因為accept、read、write阻塞,而且這個阻塞還是因為內核提供的API是阻塞的才會造成的。
## NIO
NIO有兩個含義:一個是在java.nio包中表示new IO;一個是在操作系統中表示NONBlocking,非阻塞的意思。
~~~
List<SockerChannel> list = new ArrayList<>();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 設置為非阻塞
while (true) {
accept();
if == null
for (SocketChannel c : list) {
.. 處理讀寫
}
}
~~~
假設采用11萬連接的客戶端去進行壓測,會=發現建立連接的速度為BIO快很多。但是發現到后面越來越慢了,因為每次List的遍歷的次數會越來越多。最后可能出現如下問題`Too many open files`的問題。
NIO的優勢:
1. 通過一個或者多個的線程,來解決N個客戶端的連接處理。
NIO的問題:
1. 雖然是單線程的,但是每次循環都需要O(n)的系統調用的成本,詢問是否有數據。但是實際場景中真的有數據的連接并不多,所以有很多系統調用是浪費的。系統調用accept、recv等都需要做保護現場等工作,比較費時。注意并不是服務端代碼調用read系統調用的問題,而是很多read系統調用沒有數據的問題。
## 多路復用器
多路復用器指的是多個客戶端復用一次系統調用,只用一次系統調用就能知道各個連接的IO狀態,進程由程序進一步控制對有狀態的IO進行讀寫操作。
Linux中提供了三種多路復用器:**select、poll、epoll**。Java中的Selector.open會根據不同的操作系統選擇不同的多路復用器,可以在虛擬機參數中進行配置,在Linux系統中默認是調用epoll。
其實無論是NIO還是多路復用,都是需要遍歷所有的IO詢問到狀態的。只不過NIO的這個遍歷**每次**都需要用戶態到內核態的切換,而多路復用器的遍歷過程只觸發了**一次**用戶態到內核態的系統調用,把很多的fd傳遞給內核,內核遍歷這些fd,并修改狀態,之后返回給用戶態,用戶態再根據這些問題具體處理。
**多路復用器也是運行在同步非阻塞模型!!!異步Linux還不完善。**
### select方法
Linux系統中最早的一個多路復用器、其能夠接收的文件描述符有限制,受參數FD_SETSIZE的限制,為1024。
select方法每次在發生系統調用的時候,會復制一份所有的文件描述符fds,傳遞給內核,內核遍歷fds后修改狀態后返回給用戶態。用戶態再次遍歷fds,然后處理有狀態的fd。可以發現這個過程雖然只有一次系統調用,但是需要2次的全量遍歷fds的過程。
### poll方法
poll方法是對select的優化,與select的主要區別是沒有FD_SETSIZE的限制,兩者的工作模式比較像。
### epoll方法
最新的一個多路復用器,也是使用得最多的一個多路復用器。
包括三個過程:
1. epoll\_create:創建一個新的epoll實例,并返回一個文件描述符指向這個epoll實例。即在內核中開辟一塊空間,并返回一個fd指向該內核空間。這塊空間里存放了一顆紅黑樹,也稱為epoll\_fd。
2. epoll\_ctl:可以看成是控制epoll\_fd這塊區域的操作,例如往里面添加fd或者刪除fd。
3. epoll\_wait:epoll\_wait在等待一個鏈表,這個鏈表中的是由紅黑樹中發生事件的節點遷移過去的,由中斷進行處理。如果沒有事件的會阻塞線程進行等待。
因此每次在系統調用的時候拿到鏈表即可,鏈表中的節點都表示有狀態的節點,因此不用全量的遍歷整個fds。
## Reactor模式
對多路復用的一層封裝,表示對應事件的意思,當有一個事件來的時候,Reactor就會對其作出反應。Reactor模式也叫作**Disapatcher**分發模式,I/O 多路復?監 聽事件,收到事件后,根據事件類型分配(Dispatch)給某個進程 / 線程進行處理。例如Netty中的EventLoopGroup。其中:
- Reactor 負責監聽和分發事件,事件類型包含連接事件、讀寫事件;
- 處理資源池負責處理事件,如 read -> 業務邏輯 -> send。
**可能理解成Boss和Worker會比較好理解一點。**
Reactor一般可以分為如下幾種:
- 單個Reactor,單工作線程:
:-: 
- 單個Reactor,多個工作線程:
:-: 
- 多個Reactor,多個工作線程:
:-: 
**Netty就是使用這種。**
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper