netty入門篇

先說好,這裏所說的入門篇,並不是教你們如何入門,而是在我研讀部分源碼後,寫了一個基本的demo,實現了客戶端和服務端的交互,在放出我寫的源碼之前,寫簡單介紹下netty中的一些核心概念和核心類。

NIO模型

NIO是相對於BIO的一個概念,BIO是阻塞IO,不管進行accept、connect、read、write操作都可能導致阻塞。NIO就是大家常說的非阻塞模型,但是我感覺還是採用多路複用的思想來理解更合適。

如果每一個連接都關聯一個線程,那麼一個線程很容易由於當前連接沒有就緒的事件而阻塞。但是呢,如果一個線程能夠同時監控多個連接的事件,那麼只要有一個連接就緒了,當前線程就能就行相應的事件操作,但是監控的所有連接都沒有就緒事件,當前線程也只能空轉了。
nettry
上面這張圖是nettry服務端的最常用的io模型,主線程進行accept操作,當收到新的連接請求時,會將新來的連接交給子線程來進行io操作。


事件驅動模型

這個詞還是常聽說的,在io中,有幾個事件,accept事件、connect事件、read事件、write事件,當這些事件發生時,纔會驅動io線程去做事情,不會阻塞於單一的連接上。


下面介紹一些核心類,netty中的核心組件非常多,下面就抽一些常用到的類來簡單介紹下,後面就直接放出我的demo了,以後會根據netty中的細節寫一些東西。

Channel

Channel是一個接口,這個會映射到Socket上,看到Channel,大家一定要想到傳統編程中的Socket。

一些常用的實現類:

  • NioSocketChannel

類似於Socket

  • NioServerSocketChannel

類似於ServerSocket

  • EpollSocketChannel

和NioSocketChannel類似,只是採用了epoll模式的io,具有更高的性能。

  • EpollServerSocketChannel

類似於NioServerSocketChannel,只是採用了epoll模式的io,具有更高的性能。

上面也就說了幾種常見的,netty包中定義了非常多的Channel的抽象類和實現類。

EventLoopGroup

是一個接口,可以註冊Channel,在之後的事件循環中,可以查看哪些Channel上有事件就緒了,對於一個EventLoop通常就是用一個線程處理內部的select操作,當然可以指定多個EventLoop,每一個EventLoop處理一部分Channel集合。

一些常見的實現類:

  • NioEventLoopGroup
  • NioEventLoop
  • EpollEventLoopGroup
  • EpollEventLoop

ServerBootStrap

這個類是服務端的啓動入口

BootStrap

這個類是客戶端的啓動入口

ChannelHandler

這個接口的實現是我們最需要關注的,其實我們進行netty開發,最主要的就是寫一堆Handler,不管是編碼器、解碼器都是一個個Handler。

主要的子類包含如下:

  • ChannelInboundHandler
  • ChannelOutboundHandler
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandler
  • ChannelInitializer
  • SimpleChannelInboundHandler

包含Inbound的類是入棧事件處理的handler,比如讀數據、accept、connect。
包含Outbound的類是出棧事件處理的handler,比如寫數據。


上面就是netty中一些核心的東西,netty還包含了很多自定義的編碼器、解碼器、基於不同協議實現的handler等等。

下面就主要上我的demo了。

我這個代碼就是在客戶端定義一個枚舉值,包含人的7大情緒,然後把數據傳到服務端,服務端解析出對應的枚舉值,根據枚舉值的定義反饋給客戶端一個心靈雞湯的描述。

包含如下幾個類:

  • Client : 客戶端的啓動入口
  • EmotionClientHandler:客戶端的handler
  • EmotionDecoder:一個解碼器
  • EmotionEncoder:一個編碼器
  • EmotionEnums:一個枚舉值類,客戶端將枚舉值類傳給服務端
  • EmotionServerHandler:服務端的handler
  • Invoker:服務端收到客戶端的數據後,進行處理的類
  • Server:服務端的啓動入口

Client

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @date 2019-12-7 下午 7:43
 **/
public class Client {

    private String ip;

    private int port;

    public Client(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    new Client("127.0.0.1", 8889).run();
                }catch (Exception e) {}
            }).start();
        }
    }

    public void run() throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup(2);
        try {
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new EmotionEncoder());
                            pipeline.addLast(new EmotionDecoder());
                            pipeline.addLast(new EmotionClientHandler());
                        }
                    })
                    .option(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture future = bootstrap.connect(ip, port).sync();
            future.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

EmotionClientHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Random;

/**
 * @date 2019-12-7 下午 5:04
 **/
public class EmotionClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel active");
        new Thread(() -> {
            EmotionEnums[] emotionEnums = EmotionEnums.values();
            Random random = new Random();
            while (true) {
                try {
                    EmotionEnums emotion = emotionEnums[random.nextInt(emotionEnums.length)];
                    ctx.writeAndFlush(emotion);
                    Thread.sleep(1000);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("來自於心靈雞湯的反饋信息:" + msg);
    }
}

EmotionDecoder

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * @date 2019-12-7 下午 3:31
 **/
public class EmotionDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if(in.readableBytes() < 6) {
            return;
        }
        in.markReaderIndex();
        Short high = in.getUnsignedByte(0);
        Short low = in.getUnsignedByte(1);
        if(high.byteValue() != (byte)0xBA || low.byteValue() != (byte)0xBE) {
            throw new RuntimeException("數據格式不合法");
        }
        in.skipBytes(2);
        int msgType = in.readByte();
        int remainLen = in.readInt();
        if(remainLen > in.readableBytes()) {
            in.resetReaderIndex();
            return;
        }
        if(msgType == 1) {
            int type = in.readInt();
            byte[] mBytes = new byte[in.readInt()];
            in.readBytes(mBytes);
            String msg = new String(mBytes);
            EmotionEnums emotionEnums = EmotionEnums.get(type, msg);
            if (emotionEnums != null) {
                out.add(emotionEnums);
            }
        } else if(msgType == 2) {
            byte[] mBytes = new byte[remainLen];
            in.readBytes(mBytes);
            out.add(new String(mBytes));
        } else {
            throw new RuntimeException("數據格式不對");
        }
    }
}

EmotionEncoder

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * @date 2019-12-7 下午 3:32
 *
 * magic: 0xBABE
 **/
public class EmotionEncoder extends MessageToByteEncoder {

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        if(msg instanceof EmotionEnums) {
            EmotionEnums baseMsg = (EmotionEnums)msg;
            int type = baseMsg.getType();
            String m = baseMsg.getMsg();
            // 先寫魔數0xBABE
            out.writeByte((byte)0xBA);
            out.writeByte((byte)0xBE);
            out.writeByte(1);
            out.writeInt(0);
            // 寫枚舉類型值
            out.writeInt(type);
            byte[] mByte = m.getBytes("utf-8");
            // 寫描述值的長度
            out.writeInt(mByte.length);
            // 寫描述值的字節數組
            out.writeBytes(mByte);
            out.setInt(3, 4 + mByte.length);
        } else if(msg instanceof String) {
           String baseMsg = (String)msg;
           out.writeBytes(new byte[] {(byte)0xBA, (byte)0xBE});
           out.writeByte(2);
           byte[] mBytes = baseMsg.getBytes("utf-8");
           out.writeInt(mBytes.length);
           out.writeBytes(mBytes);
        } else {
            System.out.println("無效的數據類型");
        }
    }
}

EmotionEnums

import java.util.HashMap;
import java.util.Map;

/**
 * @date 2019-12-7 下午 3:33
 * 人的七大情緒的枚舉
 **/
public enum EmotionEnums {

    HAPPY(1, "喜"),
    ANGRY(2, "怒"),
    WORRIED(3, "憂"),
    THOUGHTFUL(4, "思"),
    SAD(5, "悲"),
    FEARFUL(6, "恐"),
    FRIGHTENED(7, "驚");

    private EmotionEnums(int type, String msg) {
        this.type = type;
        this.msg = msg;
    }

    private static final Map<String, EmotionEnums> map = new HashMap<>();

    static {
        for(EmotionEnums e : EmotionEnums.values()) {
            map.put(e.type + "_" + e.msg, e);
        }
    }

    private int type;

    private String msg;

    public int getType() {
        return type;
    }

    public String getMsg() {
        return msg;
    }

    public static EmotionEnums get(int type, String msg) {
        return map.get(type + "_" + msg);
    }
}

EmotionServerHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @date 2019-12-7 下午 4:53
 **/
public class EmotionServerHandler extends SimpleChannelInboundHandler<EmotionEnums> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, EmotionEnums msg) throws Exception {
        String response = Invoker.invoke(msg);
        ctx.writeAndFlush(response);
    }
}

Invoker

/**
 * @date 2019-12-7 下午 4:55
 **/
public class Invoker {

    public static String invoke(EmotionEnums emotionEnums) {
        if(emotionEnums == EmotionEnums.HAPPY) {
            return "人生苦短,請快樂的生活吧,沒有什麼坎是過不去的!";
        } else if (emotionEnums == EmotionEnums.ANGRY) {
            return "不要生氣,會長皺紋的";
        } else if(emotionEnums == EmotionEnums.FEARFUL) {
            return "請克服內心的恐懼,無懼未來";
        } else if(emotionEnums == EmotionEnums.FRIGHTENED) {
            return "對不起呀,嚇到你了";
        } else if(emotionEnums == EmotionEnums.SAD) {
            return "男兒有淚不輕彈,只是未到傷心處";
        } else if(emotionEnums == EmotionEnums.THOUGHTFUL) {
            return "多思考,才能更快的成長";
        } else if(emotionEnums == EmotionEnums.WORRIED) {
            return "沒什麼好擔心的,船到橋頭自然直";
        }
        return "";
    }
}

Server

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * @date 2019-12-7 下午 5:20
 **/
public class Server {

    private int port;

    public Server(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        new Server(8889).run();
    }

    public void run() throws Exception {
        ServerBootstrap bootstrap = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new EmotionEncoder());
                            pipeline.addLast(new EmotionDecoder());
                            pipeline.addLast(new EmotionServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture future = bootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

返回結果截圖如下:
結果

簡單的netty代碼還是很好實現的,如果真的想用netty實現比較有用的功能,獲取可以參考dubbo、jsf、各種基於netty實現的rpc框架。

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