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框架。

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