自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Netty 基石,Java NIO 核心知識(shí)

開(kāi)發(fā) 后端
在深入 Netty 之前,我覺(jué)得有必要先對(duì)齊一下 Java NIO 的基礎(chǔ)知識(shí),因?yàn)?Netty 對(duì)底層網(wǎng)絡(luò) I/O 的操作就是基于 Java NIO 的,所以有必要了解一下。

本文轉(zhuǎn)載自微信公眾號(hào)「yes的練級(jí)攻略」,作者是Yes呀 。轉(zhuǎn)載本文請(qǐng)聯(lián)系yes的練級(jí)攻略公眾號(hào)。

你好,我是yes。

在深入 Netty 之前,我覺(jué)得有必要先對(duì)齊一下 Java NIO 的基礎(chǔ)知識(shí),因?yàn)?Netty 對(duì)底層網(wǎng)絡(luò) I/O 的操作就是基于 Java NIO 的,所以有必要了解一下。

到時(shí)候看源碼,會(huì)有很多概念,例如 Channel、Selector、SelectionKey、Buffer 等等,這篇我們就來(lái)了解下這些名詞到底代表著什么,分別是什么意思。

關(guān)于 Java NIO 相關(guān)的核心,總的來(lái)看包含以下三點(diǎn),分別是:

  • Channel
  • Buffer
  • Selector

什么是 Channel

翻譯過(guò)來(lái)就是通道。

我們可以往通道里寫(xiě)數(shù)據(jù),也可以從通道里讀數(shù)據(jù),它是雙向的,而與之配套的是 Buffer,也就是你想要往一個(gè)通道里寫(xiě)數(shù)據(jù),必須要將數(shù)據(jù)寫(xiě)到一個(gè) Buffer 中,然后寫(xiě)到通道里。

從通道里讀數(shù)據(jù),必須將通道的數(shù)據(jù)先讀取到一個(gè) Buffer 中,然后再操作。

在 NIO 中 Channel 有多種類型:

  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel
  • FileChannel
  • SocketChannel

對(duì)標(biāo) Socket,我們可以直接將它當(dāng)做所建立的連接。

通過(guò) SocketChannel ,我們可以利用 TCP 協(xié)議進(jìn)行讀寫(xiě)網(wǎng)絡(luò)數(shù)據(jù)。

ServerSocketChannel

可以對(duì)標(biāo) ServerSocket,也就是服務(wù)端創(chuàng)建的 Socket。

它的作用就是監(jiān)聽(tīng)新建連的 TCP 連接,為新進(jìn)一個(gè)連接創(chuàng)建對(duì)應(yīng)的 SocketChannel。

之后,通過(guò)新建的 SocketChannel 就可以進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的讀寫(xiě),與對(duì)端交互。

可以看到它主要是用來(lái)接待新連接,這功能主要就是服務(wù)端做的,所以叫 ServerSocketChannel。

DatagramChannel

看到 Datagram 應(yīng)該就知道是 UDP 協(xié)議了,是無(wú)連接協(xié)議。

利用 DatagramChannel 可以直接通過(guò) UDP 進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的讀寫(xiě)。

FileChannel

文件通道,用來(lái)進(jìn)行文件的數(shù)據(jù)讀寫(xiě)。

我們?nèi)粘i_(kāi)發(fā)主要是基于 TCP 協(xié)議,所以我們把精力放在 SocketChannel 和 ServerSocketChannel 上即可。

我們?cè)倩剡^(guò)頭來(lái)繼續(xù)看看 SocketChannel 和 ServerSocketChannel。

SocketChannel 主要在兩個(gè)地方出現(xiàn):

  • 客戶端,客戶端創(chuàng)建一個(gè) SocketChannel 用于連接至遠(yuǎn)程的服務(wù)端。
  • 服務(wù)端,服務(wù)端利用 ServerSocketChannel 接收新連接之后,為其創(chuàng)建一個(gè) SocketChannel 。

隨后,客戶端和服務(wù)端就可以通過(guò)這兩個(gè) SocketChannel 相互發(fā)送和接收數(shù)據(jù)。

ServerSocketChannel 主要出現(xiàn)在一個(gè)地方:服務(wù)端。

服務(wù)端需要綁定一個(gè)端口,然后監(jiān)聽(tīng)新連接的到來(lái),這個(gè)活兒就由 ServerSocketChannel 來(lái)干。

服務(wù)端內(nèi)常常會(huì)利用一個(gè)線程,一個(gè)死循環(huán),不斷地接收新連接的到來(lái)。

  1. ServerSocketChannel serverSocketChannel  
  2.   = ServerSocketChannel.open(); 
  3.  ...... 
  4. while(true){ 
  5.   // 接收的新連接 
  6.   SocketChannel socketChannel = 
  7.       serverSocketChannel.accept(); 
  8.  ....... 

至此,想必你應(yīng)該清楚 ServerSocketChannel 和 SocketChannel 的區(qū)別和作用了。

Buffer

Buffer 說(shuō)白了就是內(nèi)存中可以讀寫(xiě)的一塊地方,叫緩沖區(qū),用于緩存數(shù)據(jù)。

其實(shí)還真沒(méi)啥好說(shuō)的,最多就講講 Java NIO Buffer 的 API。

但講 API 的太死板了,所以自己上網(wǎng)搜搜吧。我就告知一個(gè)結(jié)論,這個(gè) API 很不好用,稍微漏寫(xiě)了點(diǎn),就容易出 bug,而且還有很多優(yōu)化的之處,所以 Netty 沒(méi)用 Java NIO Buffer 而是自己實(shí)現(xiàn)了一個(gè) Buffer,叫 ByteBuf。

等我們之后分析 ByteBuf 的時(shí)候再來(lái)盤(pán)一盤(pán)?,F(xiàn)在你只需要知道 Buffer 主要用來(lái)緩存通道的讀寫(xiě)數(shù)據(jù)即可。

對(duì)了,看到這可能會(huì)有人提出疑問(wèn),為什么 Channel 必須和 Buffer 搭配使用?

其實(shí)網(wǎng)絡(luò)數(shù)據(jù)是面向字節(jié)的,但是我們讀寫(xiě)的數(shù)據(jù)往往是多字節(jié)的,假設(shè)不用 Buffer ,那我們就得一個(gè)字節(jié)一個(gè)字節(jié)的調(diào)用讀和調(diào)用寫(xiě),想想是不是很麻煩?

所以我們搞個(gè) Buffer,把數(shù)據(jù)攏一攏,這樣之后的調(diào)用才能更好地處理完整的數(shù)據(jù),方便異步的處理等等。

Selector

I/O多路復(fù)用的核心玩意。

一個(gè) Selector 上可以注冊(cè)多個(gè) Channel ,我們從上面得知一個(gè) Channel 就對(duì)應(yīng)了一個(gè)連接,因此一個(gè) Selector 可以管理多個(gè) Channel 。

具體管理什么?

當(dāng)任意 Channel 發(fā)生讀寫(xiě)事件的時(shí)候,通過(guò) Selector.select() 就可以捕捉到事件的發(fā)生,因此我們利用一個(gè)線程,死循環(huán)的調(diào)用 Selector.select(),這樣可以利用一個(gè)線程管理多個(gè)連接,減少了線程數(shù),減少了線程的上下文切換和節(jié)省了線程資源。

這就是 Selector 的核心功能,然后我們?cè)賮?lái)細(xì)說(shuō)具體是怎樣管理的。

首先,創(chuàng)建一個(gè) Selector。

  1. Selector selector = Selector.open(); 

然后,你需要將被管理的 Channel 注冊(cè)到 Selector 上,并聲明感興趣的事件。

  1. SelectionKey key = channel.register(selector, Selectionkey.OP_READ); 

事件一共有以上四種類型,注冊(cè)的時(shí)候可以同時(shí)對(duì)多種類型的事件感興趣,例如:

  1. SelectionKey key  
  2.   = channel.register(selector,  
  3.    Selectionkey.OP_READ | SelectionKey.OP_WRITE); 

這樣,當(dāng)這個(gè) Channel 發(fā)生讀或?qū)懯录?,我們調(diào)用 Selector.select() 就可以得知有事件發(fā)生。

具體 Selector.select() 有三個(gè)重載方法:

  • int selectNow(),不論是否有無(wú)事件發(fā)生,立即返回
  • int select(long timeout),至多阻塞 timeout 時(shí)間(或被喚醒),如果提早有事件發(fā)生,提早返回
  • int select(),一直阻塞著,直到有事件發(fā)生(或被喚醒)

返回值就是就緒的通道數(shù),一般判斷大于 0 即可進(jìn)行后續(xù)的操作。

后續(xù)的操作就是調(diào)用:

  1. Set selectedKeys = selector.selectedKeys(); 

獲得了一個(gè)類型為 Set 的 selectedKeys 集合,那這個(gè) selectedKeys 又是啥玩意?

我們來(lái)看一下它的方法和成員:

看到這些成員,其實(shí)我們就很清晰了,我們可以通過(guò) selectedKey 得知當(dāng)前發(fā)生的是什么事件,有 isAcceptable、isReadable 等等。

然后還能獲得對(duì)應(yīng)的 channel 進(jìn)行相應(yīng)的讀寫(xiě)操作,還有獲取 attachment 等等。

所以得到了 selectedKeys 就可以通過(guò)迭代器遍歷所有發(fā)生事件的連接,然后進(jìn)行操作。

大致使用的代碼如下所示:

  1. while(true) { 
  2.  int readyNum = selector.select(); 
  3.  if (readyNum == 0) { 
  4.     continue
  5.  } 
  6.  Set selectedKeys = selector.selectedKeys(); 
  7.  Iterator keyIterator = selectedKeys.iterator(); 
  8.  while(keyIterator.hasNext()) { 
  9.     SelectionKey key = keyIterator.next(); 
  10.     if(key.isAcceptable()) { 
  11.         // a connection was accepted by a ServerSocketChannel. 
  12.     } else if (key.isConnectable()) { 
  13.         // a connection was established with a remote server. 
  14.     } else if (key.isReadable()) { 
  15.         // a channel is ready for reading 
  16.     } else if (key.isWritable()) { 
  17.         // a channel is ready for writing 
  18.     } 
  19.     keyIterator.remove(); //執(zhí)行完畢之后,需要在循環(huán)內(nèi)移除自己 
  20.  } 

還有個(gè)方法就是 Selector.wakeup(),可以喚醒阻塞著的 Selector。

對(duì)了還有一點(diǎn)沒(méi)說(shuō),就是如果 Channel 要和 Selector 搭配,那它必須得是非阻塞的,即配置

  1. channel.configureBlocking(false); 

從上面的操作,我們可以得知 Selector 處理事件的時(shí)候必須快,如果長(zhǎng)時(shí)間處理某個(gè)事件,那么注冊(cè)到 Selector 上的其他連接的事件就不會(huì)被及時(shí)處理,造成客戶端阻塞。

至此,想必你應(yīng)該清晰 Selector 具體是如何管理這么多連接的了。

參考:https://ifeve.com/java-nio-all/

 

責(zé)任編輯:武曉燕 來(lái)源: yes的練級(jí)攻略
相關(guān)推薦

2021-09-27 08:56:44

NettyChannelHand架構(gòu)

2017-03-07 13:03:34

AndroidView知識(shí)問(wèn)答

2020-11-06 00:50:16

JavaClassLoaderJVM

2024-03-04 08:10:00

C#多線程語(yǔ)言

2024-07-29 11:21:24

2024-11-04 09:00:00

Java開(kāi)發(fā)

2021-12-30 08:17:27

Springboot數(shù)據(jù)訪問(wèn)DataSourceB

2025-01-07 14:10:46

SpringBoot開(kāi)發(fā)Java

2018-07-26 20:10:02

編程語(yǔ)言Java多線程

2020-10-10 19:37:27

BIO 、NIO 、A

2021-09-06 08:31:11

Kafka架構(gòu)主從架構(gòu)

2021-01-15 08:35:49

Zookeeper

2020-10-26 10:40:31

Axios前端攔截器

2021-01-06 13:52:19

zookeeper開(kāi)源分布式

2025-03-26 11:30:40

2022-04-16 16:52:24

Netty網(wǎng)絡(luò)服務(wù)器客戶端程序

2022-11-04 09:01:45

HashMap函數(shù)擴(kuò)容鏈

2024-04-23 14:25:16

Python備忘清單

2021-04-13 08:25:12

測(cè)試開(kāi)發(fā)Java注解Spring

2020-05-19 14:40:08

Linux互聯(lián)網(wǎng)核心
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)