Netty编程

1 篇文章 0 订阅

Netty

概述

Netty是由JBOSS 提供的一个Java 开源框架。Netty提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。

模型和异步

在这里插入图片描述
比较类似于上面的线程池模型,Netty抽象出两组线程池,BossGroup专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道。 NioEventLoop内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop 负责。
模型说明
1、一个 NioEventLoopGroup 下包含多个 NioEventLoop
2、每个 NioEventLoop 中包含有一个Selector,一个 taskQueue
3、每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
4、每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
5、每个 NioChannel 都绑定有一个自己的 ChannelPipeline
异步说明
Netty 的异步模型是建立在 future 和 callback 的之上的。Future的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future 去监控方法 fun 的处理过程。
在使用 Netty 进行编程时,拦截操作和转换出入站数据只需要您提供 callback 或利用 future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。Netty 框 架的目标就是让你的业务逻辑从网络基础应用编码中分离出来、解脱出来。

核心API

ChannelHandler 及其实现类

ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具 体的业务逻辑。
在这里插入图片描述
我们经常去定义一个handler类去继承 ChannelInboundHandlerAdapter,然后通过 重写相应方法实现业务逻辑,一般重用的方法有:
1、public void channelActive(ChannelHandlerContext ctx),通道就绪事件
2、public void channelRead(ChannelHandlerContext ctx, Object msg),通道读取数据事件
3、public void channelReadComplete(ChannelHandlerContext ctx) ,数据读取完毕事件
4、public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause),通道发生异常事件

Pipeline 和 ChannelPipeline

ChannelPipeline 是一个Handler的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于一个贯穿 Netty 的链。
在这里插入图片描述
1、ChannelPipeline addFirst(ChannelHandler… handlers),把一个业务处理类(handler)添加到链中的第一个位置
2、ChannelPipeline addLast(ChannelHandler… handlers),把一个业务处理类(handler)添加到链中的最后一个位置

ChannelHandlerContext

这是事件处理器上下文对象 , Pipeline 链中的实际处理节点 。 每个处理节点 ChannelHandlerContext 中包含一个具体的事件处理器 ChannelHandler , 同时 ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler 进行调用。常用方法如下所示:
1、ChannelFuture close(),关闭通道
2、ChannelOutboundInvoker flush(),刷新
3、ChannelFuture writeAndFlush(Object msg) , 将数据写到 ChannelPipeline 中 当前 ChannelHandler 的下一个 ChannelHandler 开始处理(出站)

ChannelOption

Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数。ChannelOption 是 Socket 的标准参数,而非 Netty 独创的。常用的参数配置有: 1、ChannelOption.SO_BACKLOG 对应 TCP/IP 协议 listen 函数中的 backlog 参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定 了队列的大小。
2、 ChannelOption.SO_KEEPALIVE ,一直保持连接活动状态。

ChannelFuture

表示 Channel 中异步 I/O 操作的结果,在 Netty 中所有的 I/O 操作都是异步的,I/O 的调 用会直接返回,调用者并不能立刻获得结果,但是可以通过 ChannelFuture 来获取 I/O 操作的处理状态。 常用方法如下所示:
1、Channel channel(),返回当前正在进行 IO 操作的通道
2、ChannelFuture sync(),等待异步操作执行完毕

EventLoopGroup 和其实现类 NioEventLoopGroup

EventLoopGroup 是一组 EventLoop 的抽象,Netty 为了更好的利用多核 CPU 资源,一般 会有多个 EventLoop 同时工作,每个 EventLoop 维护着一个 Selector 实例。
EventLoopGroup 提供 next 接口,可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。在 Netty 服务器端编程中,我们一般都需要提供两个 EventLoopGroup,例如: BossEventLoopGroup 和 WorkerEventLoopGroup。一个负责处理接收请求,一个负责处理请求
通常一个服务端口即一个 ServerSocketChannel对应一个Selector 和一个EventLoop线程。 BossEventLoop 负责接收客户端的连接并将 SocketChannel 交给 WorkerEventLoopGroup 来进 行 IO 处理
1、public NioEventLoopGroup(),构造方法
2、public Future<?> shutdownGracefully(),断开连接,关闭线程

ServerBootStrap和BootStrap

ServerBootStrap是Netty的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中的客户端启动助手,通过它可以完成客户端的各种配置。常用方法如下 所示:
1、public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup), 该方法用于服务器端,用来设置两个 EventLoop 2、public B group(EventLoopGroup group) ,该方法用于客户端,用来设置一个 EventLoop
3、public B channel(Class<? extends C> channelClass),该方法用来设置一个服务器端的通道 实现
4、public B option(ChannelOption option, T value),用来给 ServerChannel 添加配置 public ServerBootstrap childOption(ChannelOption childOption, T value),用来给接 收到的通道添加配置
5、public ServerBootstrap childHandler(ChannelHandler childHandler),该方法用来设置业务 处理类(自定义的 handler)
6、public ChannelFuture bind(int inetPort) ,该方法用于服务器端,用来设置占用的端口号
7、public ChannelFuture connect(String inetHost, int inetPort) ,该方法用于客户端,用来连 接服务器端

Unpooled 类

这是 Netty 提供的一个专门用来操作缓冲区的工具类,常用方法如下所示:
1、public static ByteBuf copiedBuffer(CharSequence string, Charset charset),通过给定的数据 和字符编码返回一个 ByteBuf 对象(类似于 NIO 中的 ByteBuffer 对象)

聊天 案例

说明: 通常会编写四个类:客户端、服务端、客户端处理类、服务端处理类

加入pom文件

<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.45.Final</version>
</dependency>

聊天服务端

public class ChatServer {

    private int port;

    private ChatServer(int port) {
        this.port = port;
    }

    private void run(){
        //1、创建线程池,服务端创建连个线程池
        NioEventLoopGroup boosGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        //2、创建服务端启动对象
        ServerBootstrap bootstrap = new ServerBootstrap();
        //3、配置启动对象
        bootstrap.group(boosGroup,workerGroup) //4、添加线程池
                .channel(NioServerSocketChannel.class) //5、使用 NioServerSocketChannel 作为服务器 端通道实现
                .option(ChannelOption.SO_BACKLOG,128) //6、设置线程队列中等待连接的个数
                .childOption(ChannelOption.SO_KEEPALIVE,true) //7、保持活动状态连接
                .childHandler(new ChannelInitializer<SocketChannel>() { //8、创建一个通道初始化对象
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();//9、往 Pipeline 链中添加自定义的业务 处理 handler
                        //10、添加解码器
                        pipeline.addLast("decoder",new StringDecoder());
                        //11、添加编码器
                        pipeline.addLast("encoder",new StringEncoder());
                        //12、添加自定义的处理类
                        pipeline.addLast(new ChatServerHandler());
                    }
                });
        try{
            //13、启动服务器端并绑定端口,等待接受客户端连接(bind为非阻塞,sync为阻塞式的)
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            System.out.println("------Chat Server is ready------");
            //14、关闭通道(closeFuture为非阻塞,sync为阻塞式的)
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("------Chat Server is close------");
        }finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer(9999);
        chatServer.run();
    }
}

服务器自定义处理类

//此处的泛型String类和编码器和解码器有关
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    private static List<Channel> channelList = new ArrayList<>();

    //监控通道连接事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        String address = channel.remoteAddress().toString().substring(1);
        channelList.add(channel);
        System.out.println("[Server]:"+address+"上线了");
    }

    //监控通道断开连接事件
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        String address = channel.remoteAddress().toString().substring(1);
        channelList.remove(channel);
        System.out.println("[Server]:"+address+"下线了");
    }

    //接收数据事件
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        Channel nowChannel = channelHandlerContext.channel();
        for(Channel channel : channelList){
            if(channel != nowChannel){
                String address = channel.remoteAddress().toString().substring(1);
                channel.writeAndFlush("["+address+"]"+":说"+s+"\n");
            }
        }
    }
}

聊天客户端

public class ChatClient {

    private String host;

    private int port;

    public ChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run () {
        //初始化线程池
        NioEventLoopGroup group = new NioEventLoopGroup();
        //创建启动器
        Bootstrap bootstrap = new Bootstrap();
        //配置启动器
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //添加解码器
                        pipeline.addLast("decoder",new StringDecoder());
                        //添加编码器
                        pipeline.addLast("encoder",new StringEncoder());
                        //添加自定义的处理类
                        pipeline.addLast(new ChatClientHandler());
                    }
                });
        try{
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            Channel channel = channelFuture.channel();
            System.out.println(channel.localAddress().toString().substring(1)+":Client is Online");
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNextLine()){
                String s = scanner.nextLine();
                channel.writeAndFlush(s);
            }
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            System.out.println("连接异常");
        }

    }

    public static void main(String[] args) {
        ChatClient chatClient = new ChatClient("127.0.0.1",9999);
        chatClient.run();
    }
}

客户端处理器

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {

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

编码和解码

概述

我们在编写网络应用程序的时候需要注意 codec (编解码器),因为数据在网络中传输的都是二进制字节码数据,而我们拿到的目标数据往往不是字节码数据。因此在发送数据时就 需要编码,接收数据时就需要解码。
codec 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数 据转换成字节码数据,decoder 负责把字节码数据转换成业务数据。
其实 Java 的序列化技术就可以作为 codec 去使用,但是它的硬伤太多:

  1. 无法跨语言,这应该是 Java 序列化最致命的问题了。
  2. 序列化后的体积太大,是二进制编码的 5 倍多。
  3. 序列化性能太低。
    由于 Java 序列化技术硬伤太多,因此 Netty 自身提供了一些 codec,如下所示: Netty 提供的解码器:
  4. StringDecoder, 对字符串数据进行解码
  5. ObjectDecoder,对 Java 对象进行解码
    Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务 对象的编码和解码,但其内部使用的仍是 Java 序列化技术,所以我们不建议使用。

建议

1、使用Google 的 Protobuf
2、在传输的时候将传输内容转化为json字符串

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值