高性能NIO框架Netty入門篇


http://cxytiandi.com/blog/detail/17345

 

Netty介紹

Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。

也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的socket服務開發。

官網地址:http://netty.io/

使用場景

Netty之所以能成為主流的NIO框架,是因為它有下面的優點:

  • NIO的類庫和API使用難度較高,Netty進行了封裝,容易上手
  • 高性能,功能強大,支持多種編解碼功能,支持多種主流協議
  • 成熟,穩定,已經在多個大型框架中使用(dubbo,RocketMQ,Hadoop,mycat,Spring5)
  • …..

在幾年之前我上家公司用的是Mina來開發一個IM的系統,Mina也是一個很好的框架(http://mina.apache.org/)。
如今很多的框架都改成用Netty來做底層通訊了,我司現在還有一個代理框架用Mina寫的,等把Netty玩遛了可以重構了。

不知道大家看完了上面的介紹是不是已經知道Netty能用在什么場景了,下面我結合一個我之前做過的事情來進行詳細的說明,當然只是使用場景的一方面而已。

之前做抓取的時候,有一些小型的網站,頁面結構比較復雜,還需要登錄等操作,這種就不能用統一的抓取系統去抓取,只能通過寫腳本的方式針對具體的網站做抓取,抓取必備的一個條件就是代理IP,為了方便抓取,特意封裝了一個抓取的SDK,提供了抓取的方法,內置了切換代理。

我們有一個代理池服務,通過一個網址去獲取能使用的代理IP,在剛開始用的Http請求去獲取代理IP,由於抓取量比較大,通過Http請求去獲取代理IP效率不行,后面用Netty改造了獲取IP這部分,通過Netty來獲取數據,解決了實時獲取的性能問題。

通過長連接的方式,避免了Http請求每次都要建立連接帶來的性能消耗問題,通過二進制的數據傳輸減少網絡開銷,性能更高。

簡單入門

我們編寫一個服務端和客戶端,客戶端往服務端發送一條消息,消息傳輸先用字符串進行傳遞,服務端收到客戶端發送的消息,然后回復一條消息。

首先編寫服務端代碼:

  1. public class ImServer {
  2. public void run(int port) {
  3. EventLoopGroup bossGroup = new NioEventLoopGroup();
  4. EventLoopGroup workerGroup = new NioEventLoopGroup();
  5. ServerBootstrap bootstrap = new ServerBootstrap();
  6. bootstrap.group(bossGroup, workerGroup)
  7. .channel(NioServerSocketChannel.class)
  8. .childHandler(new ChannelInitializer<SocketChannel>() {
  9. @Override
  10. public void initChannel(SocketChannel ch) throws Exception {
  11. ch.pipeline().addLast("decoder", new StringDecoder());
  12. ch.pipeline().addLast("encoder", new StringEncoder());
  13. ch.pipeline().addLast(new ServerStringHandler());
  14. }
  15. })
  16. .option(ChannelOption.SO_BACKLOG, 128)
  17. .childOption(ChannelOption.SO_KEEPALIVE, true);
  18. try {
  19. ChannelFuture f = bootstrap.bind(port).sync();
  20. f.channel().closeFuture().sync();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } finally {
  24. workerGroup.shutdownGracefully();
  25. bossGroup.shutdownGracefully();
  26. }
  27. }
  28. }
  • 通過ServerBootstrap 進行服務的配置,和socket的參數可以通過ServerBootstrap進行設置。
  • 通過group方法關聯了兩個線程組,NioEventLoopGroup是用來處理I/O操作的線程池,第一個稱為“boss”,用來accept客戶端連接,第二個稱為“worker”,處理客戶端數據的讀寫操作。當然你也可以只用一個NioEventLoopGroup同時來處理連接和讀寫,bootstrap.group()方法支持一個參數。
  • channel指定NIO方式
  • childHandler用來配置具體的數據處理方式 ,可以指定編解碼器,處理數據的Handler
  • 綁定端口啟動服務

消息處理:

  1. /**
  2. * 消息處理
  3. * @author yinjihuan
  4. *
  5. */
  6. public class ServerStringHandler extends ChannelInboundHandlerAdapter {
  7. @Override
  8. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  9. System.err.println("server:" + msg.toString());
  10. ctx.writeAndFlush(msg.toString() + "你好");
  11. }
  12. @Override
  13. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
  14. cause.printStackTrace();
  15. ctx.close();
  16. }
  17. }

啟動服務,指定端口為2222:

  1. public static void main(String[] args) {
  2. int port = 2222;
  3. new Thread(() -> {
  4. new ImServer().run(port);
  5. }).start();
  6. }

編寫客戶端連接邏輯:

  1. public class ImConnection {
  2. private Channel channel;
  3. public Channel connect(String host, int port) {
  4. doConnect(host, port);
  5. return this.channel;
  6. }
  7. private void doConnect(String host, int port) {
  8. EventLoopGroup workerGroup = new NioEventLoopGroup();
  9. try {
  10. Bootstrap b = new Bootstrap();
  11. b.group(workerGroup);
  12. b.channel(NioSocketChannel.class);
  13. b.option(ChannelOption.SO_KEEPALIVE, true);
  14. b.handler(new ChannelInitializer<SocketChannel>() {
  15. @Override
  16. public void initChannel(SocketChannel ch) throws Exception {
  17. ch.pipeline().addLast("decoder", new StringDecoder());
  18. ch.pipeline().addLast("encoder", new StringEncoder());
  19. ch.pipeline().addLast(new ClientStringHandler());
  20. }
  21. });
  22. ChannelFuture f = b.connect(host, port).sync();
  23. channel = f.channel();
  24. } catch(Exception e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }

客戶端消息處理:

  1. /**
  2. * 當編解碼器為字符串時用來接收數據
  3. * @author yinjihuan
  4. *
  5. */
  6. public class ClientStringHandler extends ChannelInboundHandlerAdapter {
  7. @Override
  8. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  9. System.out.println("client:" + msg.toString());
  10. }
  11. @Override
  12. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
  13. cause.printStackTrace();
  14. ctx.close();
  15. }
  16. }

客戶端啟動入口,然后發送消息給服務端:

  1. public static void main(String[] args) {
  2. String host = "127.0.0.1";
  3. int port = 2222;
  4. Channel channel = new ImConnection().connect(host, port);
  5. channel.writeAndFlush("yinjihuan");
  6. }

測試步驟如下:

  1. 首先啟動服務端
  2. 啟動客戶端,發送消息
  3. 服務端收到消息,控制台有輸出server:yinjihuan
  4. 客戶端收到服務端回復的消息,控制台有輸出client:yinjihuan你好

源碼參考:https://github.com/yinjihuan/netty-im

更多技術分享請關注微信公眾號:猿天地


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM