溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Netty中的線程模型和實現Echo程序服務端

發布時間:2020-06-03 09:14:25 來源:億速云 閱讀:259 作者:Leah 欄目:編程語言

本文以Netty網絡編程框架為例,為大家分析網絡編程性能的瓶頸、Reactor 模式、Netty中的線程模型以及實現Echo程序服務端。閱讀完整文相信大家對Netty網絡編程框架有了一定的認識。

Netty 是一個高性能的網絡框架,應用非常普遍,目前在Java 領域,Netty 基本上成為網絡程序的標配了,Netty 框架功能豐富,也非常復雜。今天主要分析Netty 框架中的線程模型,而線程模型直接影響著網絡程序的性能。

在介紹Netty 的線程模型之前,我們首先搞清楚網絡編程性能的瓶頸在哪里,然后再看Netty 的線程模型是如何解決這個問題的。

網絡編程性能的瓶頸

傳統的BIO 編程模型里, 所有的read() 操作和 write() 操作都會阻塞當前線程的, 如果客戶端和服務端已經建立了一個連接,而遲遲不發送數據,那么服務端的 read() 操作會一直阻塞, 所以使用BIO 模型, 一般都會為每個socket 分配一個獨立的線程,這樣就不會因為線程阻塞在一個socket 上而影響對其他socket 的讀寫。

BIO 的線程模型如下圖所示:每個socket 對應一個獨立的線程。為了避免頻繁創建消耗線程,可以采用線程池,但是socket 和線程之間的對應關系不會變化。

BIO 這種線程模型,適用于socket 連接不是很多的場景。但是現在的互聯網場景,往往需要服務器能夠支撐十萬甚至百萬連接,而創建十萬甚至百萬連接顯然不現實,所以BIO 線程模型無法解決百萬連接的問題。如果仔細觀察,你會發現互聯網場景中,雖然連接很多,但是每個連接的請求并不頻繁,所以線程大部分時間都在等待I/O 就緒,也就是說線程大部分時間都阻塞在那里,這完全是浪費,如果我們能夠解決這個問題,那就不需要這么多線程了。

順著這個思路,我們可以將線程的模型優化為下圖這個樣子,用一個線程來處理多個連接,這樣利用率就上來了,同時所需要的線程數量也降下來了??墒鞘褂?BIO 相關的 API 是無法實現的, 為什么呢?因為 BIO 相關的 socket 讀寫操作都是阻塞式的,而一旦調用了阻塞式 API,在 I/O 就緒前,調用線程會一直阻塞,也就無法處理其他的 socket 連接了。

好在 Java 里還提供了非阻塞式(NIO)API, 利用非阻塞API 就能夠實現一個線程處理多個連接了。 那具體如何實現呢?現在普遍采用的都是Reactor 模式, 包括Netty 的實現,所以先讓我們了解以下 Reactor 模式。

Reactor 模式

下面是 Reactor 模式的類結構圖,其中 Handle 指的是 I/O 句柄,在 Java 網絡編程里,它本質上就是一個網絡連接。Event Handler 很容易理解,就是一個事件處理器,其中 handle_event() 方法處理 I/O 事件,也就是每個 Event Handler 處理一個 I/O Handle;get_handle() 方法可以返回這個 I/O 的 Handle。Synchronous Event Demultiplexer 可以理解為操作系統提供的 I/O 多路復用 API,例如 POSIX 標準里的 select() 以及 Linux 里面的 epoll()。

Reactor 模式的核心自然是 Reactor 這個類,其中 register_handler() 和 remove_handler() 這兩個方法可以注冊和刪除一個事件處理器;handle_events() 方式是核心,也是 Reactor 模式的發動機,這個方法的核心邏輯如下:首先通過同步事件多路選擇器提供的 select() 方法監聽網絡事件,當有網絡事件就緒后,就遍歷事件處理器來處理該網絡事件。由于網絡事件是源源不斷的,所以在主程序中啟動 Reactor 模式,需要以 while(true){} 的方式調用 handle_events() 方法。

void Reactor::handle_events(){
  //通過同步事件多路選擇器提供的
  //select()方法監聽網絡事件
  select(handlers);
  //處理網絡事件
  for(h in handlers){
    h.handle_event();
  }
}
// 在主程序中啟動事件循環
while (true) {
  handle_events();  

Netty 中的線程模型

Netty 的實現雖然參考了 Reactor 模式,但是并沒有完全照搬,Netty 中最核心的概念是事件循環(EventLoop),其實也就是 Reactor 模式中的 Reactor,負責監聽網絡事件并調用事件處理器進行處理。在 4.x 版本的 Netty 中,網絡連接和 EventLoop 是穩定的多對 1 關系,而 EventLoop 和 Java 線程是 1 對 1 關系,這里的穩定指的是關系一旦確定就不再發生變化。也就是說一個網絡連接只會對應唯一的一個 EventLoop,而一個 EventLoop 也只會對應到一個 Java 線程,所以一個網絡連接只會對應到一個 Java 線程。

一個網絡連接對應到一個 Java 線程上,有什么好處呢?最大的好處就是對于一個網絡連接的事件處理是單線程的,這樣就避免了各種并發問題。

Netty 中的線程模型可以參考下圖,這個圖和前面我們提到的理想的線程模型圖非常相似,核心目標都是用一個線程處理多個網絡連接。

Netty 中還有一個核心概念是 EventLoopGroup,顧名思義,一個 EventLoopGroup 由一組 EventLoop 組成。實際使用中,一般都會創建兩個 EventLoopGroup,一個稱為 bossGroup,一個稱為 workerGroup。為什么會有兩個 EventLoopGroup 呢?

這個和 socket 處理網絡請求的機制有關,socket 處理 TCP 網絡連接請求,是在一個獨立的 socket 中,每當有一個 TCP 連接成功建立,都會創建一個新的 socket,之后對 TCP 連接的讀寫都是由新創建處理的 socket 完成的。也就是說處理 TCP 連接請求和讀寫請求是通過兩個不同的 socket 完成的。上面我們在討論網絡請求的時候,為了簡化模型,只是討論了讀寫請求,而沒有討論連接請求。

在 Netty 中,bossGroup 就用來處理連接請求的,而 workerGroup 是用來處理讀寫請求的。bossGroup 處理完連接請求后,會將這個連接提交給 workerGroup 來處理, workerGroup 里面有多個 EventLoop,那新的連接會交給哪個 EventLoop 來處理呢?這就需要一個負載均衡算法,Netty 中目前使用的是輪詢算法。

用 Netty 實現 Echo 程序服務端

下面的示例代碼基于 Netty 實現了 echo 程序服務端:首先創建了一個事件處理器(等同于 Reactor 模式中的事件處理器),然后創建了 bossGroup 和 workerGroup,再之后創建并初始化了 ServerBootstrap,代碼還是很簡單的,不過有兩個地方需要注意一下。

第一個,如果 NettybossGroup 只監聽一個端口,那 bossGroup 只需要 1 個 EventLoop 就可以了,多了純屬浪費。

第二個,默認情況下,Netty 會創建“2*CPU 核數”個 EventLoop,由于網絡連接與 EventLoop 有穩定的關系,所以事件處理器在處理網絡事件的時候是不能有阻塞操作的,否則很容易導致請求大面積超時。如果實在無法避免使用阻塞操作,那可以通過線程池來異步處理。

//事件處理器
final EchoServerHandler serverHandler 
  = new EchoServerHandler();
//boss線程組  
EventLoopGroup bossGroup 
  = new NioEventLoopGroup(1); 
//worker線程組  
EventLoopGroup workerGroup 
  = new NioEventLoopGroup();
try {
  ServerBootstrap b = new ServerBootstrap();
  b.group(bossGroup, workerGroup)
   .channel(NioServerSocketChannel.class)
   .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch){
       ch.pipeline().addLast(serverHandler);
     }
    });
  //bind服務端端口  
  ChannelFuture f = b.bind(9090).sync();
  f.channel().closeFuture().sync();
} finally {
  //終止工作線程組
  workerGroup.shutdownGracefully();
  //終止boss線程組
  bossGroup.shutdownGracefully();
}

//socket連接處理器
class EchoServerHandler extends 
    ChannelInboundHandlerAdapter {
  //處理讀事件  
  @Override
  public void channelRead(
    ChannelHandlerContext ctx, Object msg){
      ctx.write(msg);
  }
  //處理讀完成事件
  @Override
  public void channelReadComplete(
    ChannelHandlerContext ctx){
      ctx.flush();
  }
  //處理異常事件
  @Override
  public void exceptionCaught(
    ChannelHandlerContext ctx,  Throwable cause) {
      cause.printStackTrace();
      ctx.close();
  }
}

看完上述內容,你們對Netty網絡編程框架有進一步的了解嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女