Netty簡介與入門

Netty簡介

Netty是由JBOSS提供的基於Java NIO的開源框架,Netty提供異步非阻塞、事件驅動、高性能、高可靠、高可定製性的網絡應用程序和工具,可用於開發服務端和客戶端。

netty 可以實現:

  • HTTP服務器
  • FTP服務器
  • UDP服務器
  • RPC服務器
  • WebSocket服務器
  • Redis的Proxy服務器
  • MySQL的Proxy服務器

Netty 功能特性如下:

  1. 傳輸服務:支持 BIO 和 NIO;
  2. 容器集成:支持 OSGI、JBossMC、Spring、Guice 容器;
  3. 協議支持:HTTP、Protobuf、二進制、文本、WebSocket 、MQTT等一系列常見協議都支持。還支持通過實行編碼解碼邏輯來實現自定義協議;
  4. Core 核心:可擴展事件模型、通用通信 API、支持零拷貝的 ByteBuf 緩衝對象。

不選擇java原生NIO編程的原因

  • 1、API複雜
  • 2、要對多線程很熟悉:因爲NIO涉及到Reactor模式
  • 3、高可用的話:需要處理斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁堵和異常碼流等問題
  • 4、JDK NIO的bug 例如: epoll bug,它會導致Selector 空輪詢,最終導致CPU 100%

爲什麼選擇Netty

  • API使用簡單,更容易上手,開發門檻低
  • 功能強大,預置了多種編解碼功能,支持多種主流協議
  • 定製能力高,可以通過ChannelHandler對通信框架進行靈活地拓展
  • 高性能,與目前多種NIO主流框架相比,Netty綜合性能最高
  • 高穩定性,解決了JDK NIO的BUG
  • 經歷了大規模的商業應用考驗,質量和可靠性都有很好的驗證

示例代碼

maven引用

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.39.Final</version>
</dependency>

服務器端代碼

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
 
import java.util.concurrent.TimeUnit;
/**
 * Created by lipine on 2019-12-05.
 */
public class Server {
 
    private int port;
 
    public Server(int port){this.port = port;}
 
    public void run()throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup(); //用來接收進來的連接
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //用來處理已經被接收的連接
 
        try {
            ServerBootstrap bootstrap = new ServerBootstrap(); //啓動NIO服務的輔助啓動類
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class) //服務端
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
 
                            //心跳機制 參數:1.讀空閒超時時間 2.寫空閒超時時間 3.所有類型的空閒超時時間(讀、寫) 4.時間單位
                            //在Handler需要實現userEventTriggered方法,在出現超時事件時會被觸發
                            socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 0, 0,TimeUnit.SECONDS));
                            //設置解碼器
                            socketChannel.pipeline().addLast("decoder", new ByteArrayDecoder());
                            socketChannel.pipeline().addLast("channelHandler", new ServerHandler());
                            //設置編碼器
                            socketChannel.pipeline().addLast("encoder",new ByteArrayEncoder());
 						 }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
 
            ChannelFuture cf = bootstrap.bind(port).sync(); //綁定端口,開始接收進來的連接
            cf.channel().closeFuture().sync(); //等待服務器socket關閉
 
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
 
    public static void main(String[] args)throws Exception{
        new Server(8081).run();
    }
}

服務端消息處理代碼

mport io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
 
import java.util.concurrent.atomic.AtomicInteger;
 
/**
 * Created by lipine on 2019-12-05.
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {
 
    private AtomicInteger channelCount = new AtomicInteger(0); //通道數量
 
    /**
     * 讀數據
     * @param ctx
     * @param msg
     * @throws Exception
     */
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("read channel=" + ctx.channel() + ", total channel=" + channelCount);
        try {
 
            byte[] bytes = (byte[])msg;
            System.out.println("hex message:"+BytesHexStrTranslate.bytesToHexString(bytes));
            System.out.println(new String(bytes));
 
            String returnMsg = "hi , Im is server...";
            Channel channel = ctx.channel();
            channel.writeAndFlush(returnMsg.getBytes());
        } finally {
            // 拋棄收到的數據
            ReferenceCountUtil.release(msg);
        }
    }
 
    /**
     * 心跳檢測的超時時會觸發
     * @param ctx
     * @param evt
     * @throws Exception
     */
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            if (e.state() == IdleState.READER_IDLE) {
                System.out.println("trigger channel =" + ctx.channel());
                ctx.close();  //如果超時,關閉這個通道
            }
        } else if (evt instanceof SslHandshakeCompletionEvent) {
            System.out.println("ssl handshake done");
            //super.userEventTriggered(ctx,evt);
        }
    }
 
    /**
     * 當通道活動時
     * @param ctx
     * @throws Exception
     */
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        channelCount.incrementAndGet();//當有新通道連接進來時,將通道數+1
        System.out.println("active channel=" + ctx.channel() + ", total channel=" + channelCount + ", id=" + ctx.channel().id().asShortText());
 	}
 
    /**
     * 當通道不活動時
     * @param ctx
     * @throws Exception
     */
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
       //當通道關閉時,將通道數-1
        ctx.close();
        channelCount.decrementAndGet();
        System.out.println("inactive channel,channel=" + ctx.channel() +", id=" + ctx.channel().id().asShortText());
    }
 
    /**
     * 異常獲取
     * @param ctx
     * @param cause
     * @throws Exception
     */
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exception channel=" + ctx.channel() + " cause=" + cause); //如果不活躍關閉此通道
        ctx.close();
    }
 
}

客戶端代碼

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
 
/**
 * Created by lipine on 2019-12-05.
 */
public class Client {
 
    private String host;
    private int port;
 	public Client(String host,int port){
        this.host = host;
        this.port = port;
    }
 	 public void run() throws Exception {
        // 配置客戶端NIO線程組
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 客戶端輔助啓動類 對客戶端配置
            Bootstrap b = new Bootstrap();
            b.group(group)//
                    .channel(NioSocketChannel.class)//
                    .option(ChannelOption.TCP_NODELAY, true)//
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast("decoder",new ByteArrayDecoder());//new MessageProtocolDecoder());
                            socketChannel.pipeline().addLast("channelHandler",new ClientHandler()); // 處理網絡IO
                            socketChannel.pipeline().addLast("encoder",new ByteArrayEncoder());//new MessageProtocolEncoder());
 					 }
                    });
            // 異步鏈接服務器 同步等待鏈接成功
            ChannelFuture f = b.connect(host, port).sync();
 		  // 等待鏈接關閉
            f.channel().closeFuture().sync();
 
        } finally {
            group.shutdownGracefully();
            System.out.println("client release resource...");
        }
    }
	public static void main(String[] args) throws Exception {
        new Client("127.0.0.1",8081).run();
    }
 }

客戶端消息處理代碼

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
 
/**
 * Created by lipine on 2019-12-05.
 */
public class ClientHandler  extends ChannelInboundHandlerAdapter {
 
    // 客戶端與服務端,連接成功的售後
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("active channel:" + ctx.channel());
        // 發送SmartCar協議的消息
        // 要發送的信息
        String data = "I am client ...";
        // 獲得要發送信息的字節數組
        byte[] content = data.getBytes();
        Channel channel = ctx.channel();
        channel.writeAndFlush(content);
    }
 
    // 只是讀數據,沒有寫數據的話
    // 需要自己手動的釋放的消息
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("read channel:" + ctx.channel());
        try {
            // 用於獲取客戶端發來的數據信息
            System.out.println("Client receive message:" + new String((byte[])msg));
 	} finally {
            ReferenceCountUtil.release(msg);
        }
    }
 	@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }
}

傳統HTTP服務器的實現

  • 1、創建一個ServerSocket,監聽並綁定一個端口
  • 2、一系列客戶端來請求這個端口
  • 3、服務器使用Accept,獲得一個來自客戶端的Socket連接對象
  • 4、啓動一個新線程處理連接
    • 4.1、讀Socket,得到字節流
    • 4.2、解碼協議,得到Http請求對象
    • 4.3、處理Http請求,得到一個結果,封裝成一個HttpResponse對象
    • 4.4、編碼協議,將結果序列化字節流 寫Socket,將字節流發給客戶端
  • 5、繼續循環步驟 3

HTTP服務器之所以稱爲HTTP服務器,是因爲編碼解碼協議是HTTP協議。如果協議是Redis協議,那它就成了Redis服務器,如果協議是WebSocket,那它就成了WebSocket服務器,等等。
使用Netty你就可以定製編解碼協議,實現自己的特定協議的服務器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章