Netty学习总结

374 阅读4分钟

Netty相关

BIO 同步阻塞IO (Block IO)

一个连接一个线程 ,可以使用线程池改造 成伪异步I/O

一个acceptor线程负责监听客户端连接,然后单独起一个新的线程处理收发逻辑 伪异步IO利用了线程池的大小和消息队列机制,大大减少了内存溢出

NIO 同步非阻塞IO (No-Block IO)

一个线程可以处理多个客户端的IO事件。 采用selector的多路复用器,支持IO多路复用的有 selector 、pselect、poll、epoll。目前linux下使用的epoll的多路复用机制

AIO 异步非阻塞IO

摆脱了NIO的selector对注册的通道进行轮训,通过回调的方式,实现了异步读写,主动通知。

Netty的核心组件

*Buffer

了解NIO,要先来了解一下Buffer
Buffer是缓冲区的意思,也是一个对象,可以写入或者读取。
在NIO中所有的数据都是由缓冲区处理。
缓冲区实质上是一个数组,常用的有ByteBuffer CharBuffer 等等

* Channel

通道,传入和传出数据的载体。可以进行打开关闭操作 ,全双工。

下是常用的Channel:
-- EmbeddedChannel
-- LocalServerChannel
-- NioDatagramChannel
-- NioSctpChannel
-- NioSocketChannel

* Selector

selector 会不断的轮训注册在service上的Channel,如果存在读写事件,将被轮训出来,通过selectionKey获取就绪的集合操作。 改为epoll()轮训以后突破了最大连接句柄数的限制,意味着一个selector可以接入成千上万的客户端。

* ByteBuf

可以获取缓冲区的字节数,动态的创建byte数组,通过ByteBuf的readBytes方法将缓冲区的字节复制到新建的byte数组中。

* Future

netty中所有的I/O操作都是异步的,Netty 提供了ChannelFuture,用于在执行异步操作的时候使用。每个Netty的出站I/O操作都会返回一个ChannelFuture。ChannelFuture能够注册一个或者多个ChannelFutureListener 实例。监听器的回调方法operationComplete(),将会在对应的操作完成时被调用。

    //主线程组
    private final static EventLoopGroup bossGroup = new NioEventLoopGroup();
    //从线程组
    private final  static EventLoopGroup workerGroup = new NioEventLoopGroup();
    private static ServerBootstrap bootstrap;
    private static ChannelFuture future = null;

    public static void main(String[] args) {
        try {
            bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)//设置通道
                    .childHandler(new ServerChannelInitializer()); //设置字处理器
            //启动server ,同时设置启动方式为同步
            future = bootstrap.bind(8090).sync();
            //进行方法阻塞,等待服务器链路关闭以后,main函数才退出
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("Netty 启动错误:", e);
        } finally {
            //优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

连个线程组其实就是Reator线程组(响应式)
NioServerSocketChannel 对应与NIO库中的ServerSocketChannel

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    /**
     * 初始化方法
     */
    @Override
    protected void initChannel(SocketChannel channel) {
        //获取line
        ChannelPipeline pipeline = channel.pipeline();
        System.out.println("报告");
        System.out.println("信息:有一客户端链接到本服务端");
        System.out.println("IP:" + channel.localAddress().getHostName());
        System.out.println("Port:" + channel.localAddress().getPort());
        System.out.println("报告完毕");
        // 解码编码
//        pipeline.addLast(new StringDecoder(Charset.forName("GBK")));
//        pipeline.addLast(new StringEncoder(Charset.forName("GBK")));
        //通过管道添加 http处理器,当请求的服务端我们需要解码,响应到客户端做编码
        pipeline.addLast("HttpServerCodec",new HttpServerCodec());
        //添加自定义的handler
//        pipeline.addLast(new EchoServerHandler());
        pipeline.addLast(new CustomHandler());
    }
}
/**
 * 自定义的handler
 * SimpleChannelInboundHandler 入栈概念
 * ws用于专门处理传输text
 */
public class CustomHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    //管理所有的channel
    public static ChannelGroup groups  = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    /**
     * @param ctx 上下文对象
     * @param msg 消息
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String content  = msg.text();
        String id  = ctx.channel().id().asShortText();
        System.out.println("接收到的数据:"+content);
        sendMsg(LocalDateTime.now()+" <br/> 用户"+id+"说:"+content);
        //1、获取客户端发来的消息

        /**
         * 2、判断消息类型,根据不同的类型来处理不同的业务
         *  2.1 连接类型 当ws第一次open的时候,初始化channel,把channel和userid关联起来
         *      使用静态hashmap<String ,Channel>
         *  2.2 聊天类型 聊天记录保存到数据库(加密,乱码同时表计消息的签发状态)
         *  2.3 心跳类型
         */
    }

    /**
     * 把channel放到管理类
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel助手类添加");
        groups.add(ctx.channel());
        String id  = ctx.channel().id().asShortText();
        sendMsg(LocalDateTime.now()+"   <br/> 用户"+id+"进来了");
    }

    /**
     * 当出发的时候,会自动移除客户端的channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel助手类移除");
        System.out.println("当前channel的长id:"+ctx.channel().id().asLongText());
        System.out.println("当前channel的短id:"+ctx.channel().id().asShortText());
        String id  = ctx.channel().id().asShortText();
        sendMsg(LocalDateTime.now()+"   <br/> 用户"+id+"离开了");

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("Channel发生异常");
        //发生异常以后关闭连接(channel) 随后从groups中移除
        ctx.channel().close();
        groups.remove(ctx.channel());
    }

    public void  sendMsg(String msg){
        for (Channel c:groups) {
            c.writeAndFlush(new TextWebSocketFrame(msg));
        }
        //下面方式与上面一致,二选一 下面是使用组
//        groups.writeAndFlush(new TextWebSocketFrame("[服务器接收到的消息]"+ LocalDateTime.now()+"接收到的消息为:"+content));
    }

}