Netty从入门到网络聊天室实战

Netty从入门到网络聊天室实战

1.了解Netty

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。![](https://img-blog.csdnimg.cn/img_convert/3f826eca56155c84c897a74dce9e79b9.png#align=left&display=inline&height=319&margin=[object Object]&originHeight=319&originWidth=672&size=0&status=done&style=none&width=672)

2.核心API

  • ChannelHandler 及其实现类

ChannelHandler 接口定义了许多事件处理的方法,通过重写这些方法去实现具体的业务逻辑,API关系如图:
![image.png](https://img-blog.csdnimg.cn/img_convert/c86141573e970f6c6a9d47a90c6f2e96.png#align=left&display=inline&height=377&margin=[object Object]&name=image.png&originHeight=377&originWidth=470&size=83580&status=done&style=none&width=470)
我们需要自定义一个Handler类去继承ChannelInboundHandlerAdapter ,然后通过重写相应的方法实现业务逻辑,需要重写的方法:

  1. public void channelActive ( ChannelHandlerContext ctx ) :通道就绪事件
  2. public void channelRead ( ChannelHandlerContext ctx , Object o) :通道读取数据事件
  3. public void channelReadComplete ( ChannelHandlerContext ctx ) :数据读取完毕事件
  4. public void exceptionCaught( ChannelHandlerContext ctx , Throwable cause ) :通道发生异常事件

  • Pipeline 和 ChannelPipeline

ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于一个贯穿 Netty 的链。
![image.png](https://img-blog.csdnimg.cn/img_convert/5c60f6785384d6be8766da18edb754f6.png#align=left&display=inline&height=300&margin=[object Object]&name=image.png&originHeight=300&originWidth=591&size=151448&status=done&style=none&width=591)
ChannelPipeline addFirst ( ChannelHandler… handlers) :把一个业务处理类添加到链的第一个位置
ChannelPipeline addLast ( ChannelHandler… handlers) :把一个业务处理类添加到链的最后一个位置

  • ChannelHandlerContext

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

  • ChannelOption

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

  • ChannelFuture

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

  • EventLoopGroup 和其实现类 NioEventLoopGroup

EventLoopGroup 是一组 EventLoop 的抽象,Netty 为了更好的利用多核 CPU 资源,一般会有多个EventLoop 同时工作,每一个EventLoop 维护一个 Selector 实例
EventLoopGroup 提供 next 接口,可以从组里面按照一定的规则获取其中一个 EventLoop 来处理任务。在 Netty 服务器编程中,一般需要提供两个 EventLoopGroup ,如 BossEventLoopGroup 和 WorkerEventLoopGroup 。
![image.png](https://img-blog.csdnimg.cn/img_convert/2418094a2c78f1f178cbbf342ed9c710.png#align=left&display=inline&height=287&margin=[object Object]&name=image.png&originHeight=574&originWidth=1110&size=166797&status=done&style=none&width=555)
BossEventLoopGroup 通常是一个单线程的 EventLoop ,EventLoop 维护着一个注册了 ServerSockerChannel 的Selector 实例,BossEventLoop 不断轮询 Selector 将连接事件分离出来,通常是 OP_ACCEPT 事件,然后将接收到的 SocketChannel 交给 WorkerEventLoopGroup ,WorkerEventLoopGroup 会由 next 选择其中一个 EventLoopGroup 来将这个 SocketChannel 注册到 Selector 并对其后续的 IO 事件进行处理。
public NioEventLoopGroup () :构造方法
public Future<?> shutdownGracefully () :断开连接,关闭

  • ServerBootstrap 和 Bootstrap

ServerBootstrap 是 Netty 中的服务器端启动类助手,通过它可以完成服务器端的各种配置
Bootstrap 是 Netty 中的客户端启动类助手,通过它可以完成客户端的各种配置
常用方法:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) :用于服务器端,设置两个 EventLoop
public Bootstrap group(EventLoopGroup parentGroup) :用于客户端,设置一个 EventLoop
public B channel(Class<? extends C> 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, nt inetPort) : 用于客户端,连接服务器端

  • Unpooled 类

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

网络聊天室案例

  1. 自定义一个服务器端业务处理类

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.ArrayList;
import java.util.List;

/**
 * @Desciption 服务端业务处理类
 * @Author yucanlian
 * @Date 2020-11-05-8:42
 */
public class NettyServerHandler extends SimpleChannelInboundHandler {
    
    

    public static List<Channel> channels = new ArrayList<>();

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        Channel incoming = ctx.channel();
        channels.add(incoming);
        System.out.println("[Server]:"+incoming.remoteAddress().toString().substring(1)+"在线");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        Channel incoming = ctx.channel();
        channels.remove(incoming);
        System.out.println("[Server]:"+incoming.remoteAddress().toString().substring(1)+"掉线");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
    
    
        Channel incoming = ctx.channel();
        for (Channel channel : channels){
    
    
            if (channel != incoming){
    
    
                channel.writeAndFlush("["+incoming.remoteAddress().toString().substring(1)+"]说:"+o.toString()+"\n");
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        Channel incoming = ctx.channel();
        System.out.println("[Server]:"+incoming.remoteAddress().toString().substring(1)+"异常");
        ctx.close();
    }
}
  1. 聊天服务器端
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;

/**
 * @Desciption
 * @Author yucanlian
 * @Date 2020-11-05-8:54
 */
public class NettyServer {
    
    
    private int port;

    public NettyServer(int port) {
    
    
        this.port = port;
    }

    public void run() throws Exception{
    
    
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
    
    
            ServerBootstrap server = new ServerBootstrap();
            server.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(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("handler",new NettyServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true);
            System.out.println("Netty Server start....");
            ChannelFuture future = server.bind(port).sync();
            future.channel().closeFuture().sync();
        }finally {
    
    
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("Netty Server end....");
        }
    }

    public static void main(String[] args) throws Exception {
    
    
        new NettyServer(9999).run();
    }
}
  1. 自定义客户端业务类
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * @Desciption 服务端业务处理类
 * @Author yucanlian
 * @Date 2020-11-05-8:42
 */
public class NettyClientHandler extends SimpleChannelInboundHandler {
    
    

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

}
  1. 聊天客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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;

/**
 * @Desciption
 * @Author yucanlian
 * @Date 2020-11-05-9:07
 */
public class NettyClient {
    
    

    private final String host;
    private final int port ;

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

    public void run() {
    
    
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            Bootstrap client = new Bootstrap();
            client.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("handler",new NettyClientHandler());
                        }
                    });

            System.out.println("-------Client is ready-------");

            Channel channel = client.connect(host, port).sync().channel();
            System.out.println("------"+channel.remoteAddress().toString().substring(1)+"-------");

            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()){
    
    
                String msg = scanner.nextLine();
                channel.writeAndFlush(msg+"\r\n");
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws InterruptedException {
    
    
        new NettyClient("127.0.0.1",9999).run();
    }
}
  1. 运行结果

![image.png](https://img-blog.csdnimg.cn/img_convert/57860b1dacefe196ff3b38e763bea06f.png#align=left&display=inline&height=104&margin=[object Object]&name=image.png&originHeight=207&originWidth=529&size=17258&status=done&style=none&width=264.5)
![image.png](https://img-blog.csdnimg.cn/img_convert/1018fb2a8124cc990b725c02a9f325ef.png#align=left&display=inline&height=119&margin=[object Object]&name=image.png&originHeight=238&originWidth=688&size=22534&status=done&style=none&width=344)
![image.png](https://img-blog.csdnimg.cn/img_convert/5fc35a77679daa6d2be1a7e7c04cd475.png#align=left&display=inline&height=110&margin=[object Object]&name=image.png&originHeight=220&originWidth=651&size=22413&status=done&style=none&width=325.5)

猜你喜欢

转载自blog.csdn.net/weixin_45636641/article/details/109521152