NIO与Netty编程(三)之Netty编程

1、概述

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

       Netty是一个基于NIO的网络编程框架,使用Netty可以帮你快速、简单的开发出一个网络应用,相当于简化和流程化了NIO的开发过程。

       作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用。知名的ElasticSearch、Dubbo框架都使用Netty。

NIO与Netty编程(三)之Netty编程_第1张图片

2、Netty整体设计

2.1 线程模型

2.1.1 单线程模型

NIO与Netty编程(三)之Netty编程_第2张图片

       服务器端调用一个线程通过多路复用搞定所有的IO操作(包括连接,读,写等),编码简单,清晰明了。但是如果客户端连接数量较多,将无法支撑,我们前面的NIO案例就属于这种类型。

2.1.2 线程池模型

NIO与Netty编程(三)之Netty编程_第3张图片

       服务器端采用一个线程专门处理客户端连接请求,采用一个线程池负责IO 操作。在绝大多数场景下,该模型都能满足使用。

2.1.3 Netty 模型

NIO与Netty编程(三)之Netty编程_第4张图片

       比较类似于上面的线程池模型,Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理任务的线程,每个NioEventLoop 都有一个selector,用于监听绑定在其上的socket 网络通道。NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO 线程NioEventLoop 负责。

 一个NioEventLoopGroup 下包含多个NioEventLoop
 每个NioEventLoop 中包含有一个Selector,一个taskQueue
 每个NioEventLoop 的Selector 上可以注册监听多个NioChannel
 每个NioChannel 只会绑定在唯一的NioEventLoop 上
 每个NioChannel 都绑定有一个自己的ChannelPipeline

 

2.2 异步模型

FUTURE,CALLBACK和HANDLER

       Netty的异步模型是建立在future和callback之上的。callback(回调)我们都比较熟悉,Java中很多地方都用到了回调。这里重点要讲的future,future的核心思想是:假设有一个方法fun,计算过程可能非常耗时,一直等待方法fun执行完成再返回显然不合适。那么在执行f调用un方法的时候,立马返回一个future,后续可以通过future来监控fun方法的处理过程。

       在使用Netty进行编程时,拦截操作和转换出入站数据只需要您提供callback 或利用future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。Netty 框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来、解脱出来。

NIO与Netty编程(三)之Netty编程_第5张图片

3、核心API

3.1 ChannelHandler及其实现类

ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具体的业务逻辑。API 关系如下图所示:

NIO与Netty编程(三)之Netty编程_第6张图片

我们经常需要定义一个handler类去继承ChannelInboundHandlerAdapter,然后通过重写相应方法实现业务逻辑,我们接下来看看一般都需要重写哪些方法:
 public void channelActive(ChannelHandlerContext ctx),通道就绪事件
 public void channelRead(ChannelHandlerContext ctx, Object msg),通道读取数据事件
 public void channelReadComplete(ChannelHandlerContext ctx) ,数据读取完毕事件
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause),通道发生异常事件

 

3.2 Pipeline 和ChannelPipeline

       ChannelPipeline 是一个Handler 的集合,它负责处理和拦截inbound 或者outbound 的事件和操作,相当于一个贯穿Netty 的链。

NIO与Netty编程(三)之Netty编程_第7张图片

 ChannelPipeline addFirst(ChannelHandler... handlers),把一个业务处理类(handler)添加到链中的第一个位置
 ChannelPipeline addLast(ChannelHandler... handlers),把一个业务处理类(handler)添加到链中的最后一个位置

3.3 ChannelHandlerContext

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

 

3.4 ChannelOption

       Netty在创建了Channel实例后,一般都需要设置ChannelOption参数。ChannelOption是Socket的标准参数,而不是Netty独创的,常用的参数配置有:

3.4.1 ChannelOption.SO_BACKLOG

       对应TCP\IP协议的listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小。


3.4.2 ChannelOption.SO_KEEPALIVE

一直保持连接活动状态。

 

3.5 ChannelFuture

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

 

3.6 EventLoopGroup 和其实现类NioEventLoopGroup

       EventLoopGroup 是一组EventLoop 的抽象,Netty 为了更好的利用多核CPU 资源,一般会有多个EventLoop 同时工作,每个EventLoop 维护着一个Selector 实例。
       EventLoopGroup 提供next 接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在Netty 服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如:BossEventLoopGroup 和WorkerEventLoopGroup。
       通常一个服务端口即一个ServerSocketChannel 对应一个Selector 和一个EventLoop 线程。BossEventLoop 负责接收客户端的连接并将SocketChannel 交给WorkerEventLoopGroup 来进行IO 处理,如下图所示:

NIO与Netty编程(三)之Netty编程_第8张图片

        BossEventLoopGroup 通常是一个单线程的EventLoop,EventLoop 维护着一个注册了ServerSocketChannel 的Selector 实例,BossEventLoop 不断轮询Selector 将连接事件分离出来,通常是OP_ACCEPT 事件,然后将接收到的SocketChannel 交给WorkerEventLoopGroup,WorkerEventLoopGroup 会由next 选择其中一个EventLoopGroup 来将这个SocketChannel 注册到其维护的Selector 并对其后续的IO 事件进行处理。

常用的方法如下:

 public NioEventLoopGroup(),构造方法
 public Future shutdownGracefully(),断开连接,关闭线程

 

3.7 ServerBootstrap和Boostrap

       ServerBootstrap是Netty中的服务端启动助手,通过它可以完成服务器端的各种配置;Bootstrap 是Netty 中的客户端启动助手,通过它可以完成客户端的各种配置。常用方法如下:

 public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于服务器端,用来设置两个EventLoop
 public B group(EventLoopGroup group) ,该方法用于客户端,用来设置一个EventLoop
 public B channel(Class channelClass),该方法用来设置一个服务器端的通道实现
 public B option(ChannelOption option, T value),用来给ServerChannel 添加配置                                                     public ServerBootstrap childOption(ChannelOption childOption, T value),用来给接收到的通道添加配置
 public ServerBootstrap childHandler(ChannelHandler childHandler),该方法用来设置业务处理类(自定义的handler)             public ChannelFuture bind(int inetPort) ,该方法用于服务器端,用来设置占用的端口号
 public ChannelFuture connect(String inetHost, int inetPort) ,该方法用于客户端,用来连接服务器端

3.8 Unpooled 类

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

 

4、入门案例

4.1 创建maven工程

pom 文件中引入了netty 的坐标,采用4.1.15 版本:



    4.0.0

    com.zdw.netty
    Netty_Demo
    1.0-SNAPSHOT

    
        
            io.netty
            netty-all
            4.1.15.Final
        
    

    
        
            
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    3.2
                    
                        1.8
                        1.8
                        UTF-8
                        true
                    
                
            
        
    

4.2 服务器端

4.2.1 服务器业务处理类

package com.zdw.netty.demo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * Create By zdw on 2019/7/23
 * 自定义服务器端业务处理类
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //读取数据事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server:"+ctx);
        ByteBuf byteBuf = (ByteBuf)msg;
        System.out.println("客户端发来的消息:"+byteBuf.toString(CharsetUtil.UTF_8));
    }

    //数据读取完毕事件
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("我就是穷到没钱了啊",CharsetUtil.UTF_8));
        //将数据写到ChannelPipeline 中当前ChannelHandler 的下一个ChannelHandler 开始处理(出站)
    }

    //异常发生事件
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();//关闭上下文
    }
}

上述代码定义了一个服务器端业务处理类,继承ChannelInboundHandlerAdapter,并分别重写了三个方法。

4.2.2 服务器端

package com.zdw.netty.demo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Create By zdw on 2019/7/23
 * 服务端程序类
 */
public class NettyServer {
    public static void main(String[] args) throws Exception {
        //1、创建一个线程组用来处理网络事件(接受客户端连接)
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //2、创建一个线程组用来处理网络事件(处理通道 I/O 读写事件)
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        //3、创建服务端启动助手来配置参数
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup,workerGroup)//4、设置两个线程组
                        .channel(NioServerSocketChannel.class)//5、使用NioServerSocketChannel作为服务器端的通道实现
                        .option(ChannelOption.SO_BACKLOG,128)//6、设置线程队列中等待的个数
                        .childOption(ChannelOption.SO_KEEPALIVE,true)//7、保持活动连接状态
                        .childHandler(new ChannelInitializer() {//8、创建一个通道初始化对象
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                //9、往Pipeline 链中添加自定义的业务处理handler,NettyServerHandler服务器端自定义业务类
                                socketChannel.pipeline().addLast(new NettyServerHandler());
                                System.out.println("----------Server is Ready----------");
                            }
                        });
        //10.启动服务器端并绑定端口,等待接受客户端连接(非阻塞)
        ChannelFuture future = serverBootstrap.bind(9999).sync();
        System.out.println("===========Server is Starting============");

        //11、关闭通道,关闭线程池
        future.channel().closeFuture().sync();
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

 

4.3 客户端

4.3.1 客户端业务处理类

package com.zdw.netty.demo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * Create By zdw on 2019/7/23
 * 自定义客户端业务处理类
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    //通道就绪事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client:"+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("还钱给我啊", CharsetUtil.UTF_8));//写消息
    }

    //通道读取事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf)msg;
        System.out.println("服务端发送来的数据:"+byteBuf.toString(CharsetUtil.UTF_8));
    }

    //通道读取完毕事件
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    //异常发生事件
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 

4.3.2 客户端

package com.zdw.netty.demo;

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

/**
 * Create By zdw on 2019/7/23
 * 客户端程序
 */
public class NettyClient {
    public static void main(String[] args) throws Exception {
        //1、创建一个 EventLoopGroup 线程组
        EventLoopGroup group = new NioEventLoopGroup();
        //2、创建客户端启动助手
        Bootstrap bootstrap = new Bootstrap();
        //3、设置参数
        bootstrap.group(group)//3、设置EventLoopGroup线程组
                .channel(NioSocketChannel.class)//4、设置NioSocketChannel作为客户端通道实现
                .handler(new ChannelInitializer() {//5、创建一个通道初始化对象
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //6.往Pipeline 链中添加自定义的业务处理handler
                        socketChannel.pipeline().addLast(new NettyClientHandler());
                        System.out.println("-----------Client is Ready------------");
                    }
                });
        //7、启动客户端,等待连接上服务器端(非阻塞)
        ChannelFuture future = bootstrap.connect("127.0.0.1", 9999).sync();
        //8、等待连接关闭(非阻塞)
        future.channel().closeFuture().sync();
    }
}

测试效果:

 

5、网聊案例

接下来我们在入门案例的基础上再实现一个多人聊天案例,具体代码如下所示:

5.1 服务器端

5.1.1 服务器端自定义业务处理类

业务处理类通过继承SimpleChannelInboundHandler 类自定义了一个服务器端业务处理类,并在该类中重写了四个方法,当通道就绪时,输出在线;当通道未就绪时,输出下线;当通道发来数据时,读取数据;当通道出现异常时,关闭通道。

package com.zdw.netty.chat;

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

import java.util.ArrayList;
import java.util.List;

/**
 * Create By zdw on 2019/7/23
 * 网络聊天服务器端的业务处理类,注意继承的是  SimpleChannelInboundHandler,他的泛型代表的是消息的数据类型
 */
public class ChatServerHandler extends SimpleChannelInboundHandler {

    public static List channels = new ArrayList<>();//存储已经就绪的通道

    //通道就绪,相当于上线了
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();//得到通道
        channels.add(channel);//添加到集合中
        System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】用户在线");
    }

    //通道未就绪,相当于掉线了
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channels.remove(channel);//从集合中移除
        System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】用户掉线了");
    }

    //读取数据
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        Channel channel = channelHandlerContext.channel();//得到当前通道
        //把读取到的消息广播到其他就绪通道channels
        for(Channel targetChannel : channels){
            if(targetChannel != channel){//排除当前通道
                //把当前通道的消息  s 写给其他通道
                targetChannel.writeAndFlush("Server:用户:【"+channel.remoteAddress().toString().substring(1)+"】说:"+s+"\n");
            }
        }
    }

    //发生异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("Server:【"+channel.remoteAddress().toString().substring(1)+"】出了异常:"+cause.getMessage());
        ctx.close();
    }
}

5.1.2 服务器端主程序

       通过Netty 编写了一个服务器端程序,里面要特别注意的是:我们往Pipeline链中添加了处理字符串的编码器和解码器(这个编解码器的类型是根据我们的消息类型而定义的,Netty中还提供了Object的对象编解码器,需要注意的是:要在添加自定义的处理器之前就添加编解码器),它们加入到Pipeline 链中后会自动工作,使得我们在服务器端读写字符串数据时更加方便(不用人工处理ByteBuf)。 

package com.zdw.netty.chat;

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.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Create By zdw on 2019/7/23
 * 网络聊天的服务端
 */
public class ChatServer {
    private int port;//服务器监听的端口号
    public ChatServer(int port){
        this.port=port;
    }

    //任务方法
    public void run() {
        //创建两个线程组,用来执行客户端的连接和IO读写操作
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();//连接操作
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();//读写 IO操作
        try {
            //创建服务器端启动助手,用来设置启动参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//设置服务器端的通道实现为NioServerSocketChannel
                    .childHandler(new ChannelInitializer() {//设置通道的初始化实现对象
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();//得到pipeline链
                            pipeline.addLast("decoder",new StringDecoder());//往pipeline中添加一个字符串解码器
                            pipeline.addLast("encoder",new StringEncoder());//往pipeline中添加一个字符串编码器
                            pipeline.addLast("handler",new ChatServerHandler());//往pipeline链中添加一个自定义的业务处理器
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG,128)//设置线程队列中等待的个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true);//保持活动连接状态

            System.out.println("Netty Chat Server is Starting -------");
            //启动服务器端并绑定端口,等待接受客户端连接(非阻塞)
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            //关闭通道,
            channelFuture.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            System.out.println("Netty Chat Server is closed...............................");
        }
    }

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

5.2 客户端程序

5.2.1 客户端自定义业务处理器

通过继承SimpleChannelInboundHandler 自定义了一个客户端业务处理类,重写了一个方法用来读取服务器端发过来的数据。 

package com.zdw.netty.chat;

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

/**
 * Create By zdw on 2019/7/23
 * 网络聊天客户端的业务处理类,继承的是SimpleChannelInboundHandler类,String泛型代表的是消息类型
 */
public class ChatClientHandler extends SimpleChannelInboundHandler {
    //通道就绪事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("Client:【"+channel.localAddress().toString().substring(1)+"】用户上线了");
}

    //通道未就绪事件
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("Client:【"+channel.localAddress().toString().substring(1)+"】用户掉线了");
    }

    // 通道数据读取事件
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        //读取服务器端的数据消息,打印到控制台
        System.out.println(s.trim());
    }

    //异常发生事件
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

5.2.2 客户端主程序代码

通过Netty 编写了一个客户端程序,里面要特别注意的是:我们往Pipeline 链中添加了处理字符串的编码器和解码器,他们加入到Pipeline 链中后会自动工作,使得我们在客户端读写字符串数据时更加方便(不用人工处理ByteBuf)。 

package com.zdw.netty.chat;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

/**
 * Create By zdw on 2019/7/23
 * 网络聊天的客户端代码
 */
public class ChatClient {
    private final String host; //服务器端IP 地址
    private final int port; //服务器端端口号
    public ChatClient(String host,int port){
        this.host = host;
        this.port = port;
    }

    public void run(){
        //创建一个客户端的线程池组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端的启动助手,进行参数设置
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)//设置线程组
                    .channel(NioSocketChannel.class)//设置客户端的通道实现为NioSocketChannel
                    .handler(new ChannelInitializer() {//定义通道初始化器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();//得到pipeline链
                            pipeline.addLast("decoder",new StringDecoder());//往pipeline中添加字符串解码器
                            pipeline.addLast("encoder",new StringEncoder());//往pipeline中添加字符串编码器
                            pipeline.addLast("handler",new ChatClientHandler());//往pipeline中添加自定义的业务处理器
                        }
                    });
            //启动客户端,等待连接服务器端(非阻塞)
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            Channel channel = channelFuture.channel();
            System.out.println("--------Client:"+channel.localAddress().toString().substring(1)+"--连接成功---------");

            //控制台读取客户端手动录入的消息,然后发送给服务器端,服务器端进行消息的广播
            Scanner scanner = new Scanner(System.in);
            while(scanner.hasNextLine()){
                String msg = scanner.nextLine();//得到客户录入的消息
                channel.writeAndFlush(msg+"\r\n");//向服务器端发送数据
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }

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

 

5.3 测试效果

开启服务器端和两个客户端,进行测试,效果如下:

NIO与Netty编程(三)之Netty编程_第9张图片

NIO与Netty编程(三)之Netty编程_第10张图片

NIO与Netty编程(三)之Netty编程_第11张图片

 

6、编码和解码

6.1 概述

       我们在编写网络应用程序的时候要注意codec(编解码器),因为数据在网络中传输的都是二进制字节码数据,而我们拿到的目标数据往往不能是二进制字节码数据,因此在发送数据时就要编码,在接收数据时就需要进行解码。

       codec 的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据。
       其实Java 的序列化技术就可以作为codec 去使用,但是它的硬伤太多:
              1. 无法跨语言,这应该是Java 序列化最致命的问题了。
              2. 序列化后的体积太大,是二进制编码的5 倍多。
              3. 序列化性能太低。
由于Java 序列化技术硬伤太多,因此Netty 自身提供了一些codec,如下所示:
Netty 提供的解码器:
       1. StringDecoder, 对字符串数据进行解码
       2. ObjectDecoder,对Java 对象进行解码 ........

Netty 提供的编码器:
      1. StringEncoder,对字符串数据进行编码
      2. ObjectEncoder,对Java 对象进行编码........

      Netty 本身自带的ObjectDecoder 和ObjectEncoder 可以用来实现POJO 对象或各种业务对象的编码和解码,但其内部使用的仍是Java 序列化技术,所以我们不建议使用。因此对于POJO 对象或各种业务对象要实现编码和解码,我们需要更高效更强的技术。

 

6.2 Google 的Protobuf

Protobuf 是Google 发布的开源项目,全称Google Protocol Buffers,特点如下:
        支持跨平台、多语言(支持目前绝大多数语言,例如C++、C#、Java、python 等)
        高性能,高可靠性
        使用protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述,
然后通过protoc.exe 编译器根据.proto 自动生成.java 文件
目前在使用Netty 开发时,经常会结合Protobuf 作为codec (编解码器)去使用,具体用法如下所示。

6.3 演示Google 的Protobuf的用法

6.3.1 在上面的工程中添加如下的依赖


        
            com.google.protobuf
            protobuf-java
            3.6.1
        

6.3.2 编写proto 文件

假设我们要处理的数据是图书信息,那就需要为此编写proto 文件

syntax = "proto3";
   option java_outer_classname = "BookMessage";
   message Book{
       int32 id = 1;
       string name = 2;
   }

NIO与Netty编程(三)之Netty编程_第12张图片

 

6.3.3 通过protoc.exe 根据描述文件生成Java 类

把上面的Book.proto文件放到protoc工具的bin目录下,然后执行下面的命令,生成我们需要的java文件:

protoc --java_out=. Book.proto

NIO与Netty编程(三)之Netty编程_第13张图片

 

6.3.4 把生成的BookMessage.java 拷贝到自己的项目中

NIO与Netty编程(三)之Netty编程_第14张图片

这个类我们不要编辑它,直接拿着用即可,该类内部有一个内部类,这个内部类才是真正的POJO,一定要注意。

6.3.5 Netty 中去使用

1、在编写客户端程序时,要向Pipeline 链中添加ProtobufEncoder 编码器对象。

NIO与Netty编程(三)之Netty编程_第15张图片

2、在往服务器端发送图书(POJO)时就可以使用生成的BookMessage 类搞定,非常方便

NIO与Netty编程(三)之Netty编程_第16张图片

3、在编写服务器端程序时,要向Pipeline 链中添加ProtobufDecoder 解码器对象。

NIO与Netty编程(三)之Netty编程_第17张图片

4、在服务器端接收数据时,直接就可以把数据转换成POJO 使用,非常方便

NIO与Netty编程(三)之Netty编程_第18张图片

 

你可能感兴趣的:(Netty)