Netty入门(一) 简单介绍和实现多人聊天室功能

前置技术:需要了解NIO多路复用技术。

Netty相对于NIO做了什么

NIO 的类库和 API 繁杂, 使用麻烦: 需要熟练掌握Selector、 ServerSocketChannel、 SocketChannel、 ByteBuffer等。

开发工作量和难度都非常大: 例如客户端面临断线重连、 网络闪断、心跳处理、半包读写、 网络拥塞和异常流的处理等等。

Netty 对 JDK 自带的 NIO 的 API 进行了良好的封装,解决了上述问题。且Netty拥有高性能、 吞吐量更高,延迟更低,减少资源消耗,最小化不必要的内存复制等优点。

Netty 现在都在用的是4.x

核心总结:Netty框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来,让你可以专注业务的开发,而不需写一大堆类似NIO的网络处理操作。

Netty线程模型

模型解释

1) Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专门负责网络的读写,为什么分为两个,因为可以提高效率,多主多从的思想。

2) BossGroup和WorkerGroup类型都是NioEventLoopGroup

3) NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 , 每一个事件循环线程是NioEventLoop

4) 每个NioEventLoop都有一个selector , 用于监听注册在其上的socketChannel的网络通讯

5) 每个Boss NioEventLoop线程内部循环执行的步骤有 3 步

  • 处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
  • 将NioSocketChannel注册到某个worker NIOEventLoop上的selector
  • 处理任务队列的任务 , 即runAllTasks

6) 每个worker NIOEventLoop线程循环执行的步骤

  • 轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
  • 处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
  • runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入TaskQueue中慢慢处理,这样不影响数据在 pipeline 中的流动处理

7) 每个worker NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),管道中维护了很多 handler 处理器用来处理 channel 中的数据

Netty模块组件介绍

【Bootstrap、ServerBootstrap】:

Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

【Future、ChannelFuture】:

正如前面介绍,在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。

但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

【Channel】:

Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel 为用户提供:

1)当前网络连接的通道的状态(例如是否打开?是否已连接?)

2)网络连接的配置参数 (例如接收缓冲区大小)

3)提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。

4)调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。

5)支持关联 I/O 操作与对应的处理程序。

不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

下面是一些常用的 Channel 类型:

NioSocketChannel,异步的客户端 TCP Socket 连接。

NioServerSocketChannel,异步的服务器端 TCP Socket 连接。

NioDatagramChannel,异步的 UDP 连接。

NioSctpChannel,异步的客户端 Sctp 连接。

NioSctpServerChannel,异步的 Sctp 服务器端连接。

【Selector】:

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。

当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

【NioEventLoop】:

NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:

I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。

非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。

【NioEventLoopGroup】:

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

【ChannelHandler】:

ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。

ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:

ChannelInboundHandler 用于处理入站 I/O 事件。

ChannelOutboundHandler 用于处理出站 I/O 操作。

【ChannelHandlerContext】:

保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

【ChannelPipline】:

保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。

ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。

一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。

read事件(入站事件)和write事件(出站事件)在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

简单实战,多人聊天室功能

服务器端代码,这些必要的步骤都是Netty使用模板的例子,自己的业务需要自己去写handler实现。

public class ChatServer {
    public static void main(String[] args) throws Exception{
        //1.创建两个线程组bossgroup和workergroup
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(3);
        //线程数默认为cpu核数*2
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try{
            //2.创建服务端ServerBootstrap启动对象并填充相关参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workGroup)
                    //使用哪种通道实现,NioServerSocketChannel为异步的服务器端 TCP Socket 连接。
                    .channel(NioServerSocketChannel.class)
                    //设置连接队列长度
                    .option(ChannelOption.SO_BACKLOG,1024)
                    //添加拦截处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //一个channel对应一个pipeline,一个pipeline对应了一个handler链
                            socketChannel.pipeline().addLast("decoder", new StringDecoder());
                            socketChannel.pipeline().addLast("encoder", new StringEncoder());
                            socketChannel.pipeline().addLast(new ChatServerHandler());
                        }
                    });
            //3.绑定端口并启动
            ChannelFuture channelFuture = bootstrap.bind(9000).sync();
            System.out.println("服务端开启成功!");
            //4.对关闭通道事件进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            //关闭
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

服务器自定义handlder类,ChannelInboundHandler接口中还有很多个方法,提供了各种事件的回调和处理,只需要重写即可。SimpleChannelInboundHandler只是在其基础上封装了一层,只有一个read抽象方法需要实现

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //读取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.forEach(c-> {
            if (c == channel) {
                c.writeAndFlush("[自己]发送的消息:"+msg+"\n");
            }else {
                c.writeAndFlush("[客户端]"+channel.remoteAddress()+"发送的消息:"+msg+"\n");
                System.out.println("[客户端]"+channel.remoteAddress()+"上线了");
            }
        });

    }
    //活跃状态
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("[客户端]"+channel.remoteAddress()+"上线了");
        //先给所有连接发送消息
        channelGroup.writeAndFlush("[客户端]"+channel.remoteAddress()+"上线了");
        //再添加自己就不会给自己发了
        channelGroup.add(channel);
    }
    //非活跃状态
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("[客户端]"+channel.remoteAddress()+"下线了");
        //先给所有连接发送消息
        channelGroup.writeAndFlush("[客户端]"+channel.remoteAddress()+"下线了");

    }
    //关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }
}

客户端代码,这些必要的步骤都是Netty使用模板的例子,自己的业务需要自己去写handler实现。

public class ChatClient {
    public static void main(String[] args) throws Exception{
        //1.创建一个线程组
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            //2.创建客户端Bootstrap启动对象并填充相关参数
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    //使用哪种通道实现,NioSocketChannel为异步的客户端 TCP Socket 连接。
                    .channel(NioSocketChannel.class)
                    //添加拦截处理器
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //一个channel对应一个pipeline,一个pipeline对应了一个handler链
                            socketChannel.pipeline().addLast("decoder", new StringDecoder());
                            socketChannel.pipeline().addLast("encoder", new StringEncoder());
                            socketChannel.pipeline().addLast(new ChatClientHandler());
                        }
                    });
            //3.客户端连接服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000);
            Channel channel = channelFuture.channel();
            System.out.println("客户端连接成功");
            //客户端需要输入信息, 创建一个扫描器
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String msg = scanner.nextLine();
                //通过 channel 发送到服务器端
                channel.writeAndFlush(msg);
            }
            channelFuture.channel().closeFuture().sync();
        }finally {
            //关闭
            group.shutdownGracefully();
        }
    }
}

客户端自定义handler类。

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg.trim());
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值