Netty入门

410 阅读4分钟

Netty线程模型以及Reactor模式

Reactor设计模式:基于事件驱动的设计模式,在事件驱动的应用中,将一个或者多个客户的服务请求分离和调度。

Reactor单线程模型:一个NIO线程用来处理所有请求,队列过大,等待时间过长,导致大量的超时,而且万一这个线程挂了,整个服务器就崩了。所以这个模型使用的比较少。

Reactor多线程模型:一个Acceptor线程,一组NIO线程,一个Acceptor线程就是用来查看哪些客户端需要处理,然后分配给NIO线程进行处理。但是当Acceptor线程需要做复杂操作的时候也会有并发问题,比如需要让Acceptor线程进行认证操作。

Reactor主从线程模型:Acceptor不只是一个线程,而是一组线程,IO线程也是一组线程,两个线程池去处理接入连接和处理IO请求。Netty推荐使用的线程模型。

Netty案例 搭建Echo服务

什么是Echo服务:就是回显服务,可以用于调试和检测的服务,来检测线程模型的并发量。

服务端代码

package com.huahuo.netty.echo.server;
​
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
​
public class EchoServer {
    private final int port;
​
    private EchoServer(int port) {
        this.port = port;
    }
​
    public void run() {
        //前台巡逻线程组
        EventLoopGroup frontGroup = new NioEventLoopGroup();
        //工作线程组
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(frontGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new EchoServerHandler());
                    }
                });
        System.out.println("Echo 服务器启动中...");
        try {
            //绑定端口,同步等待
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //优雅退出
            workerGroup.shutdownGracefully();
            frontGroup.shutdownGracefully();
        }
    }
​
    public static void main(String[] args) {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }
        new EchoServer(port).run();
    }
}
​
package com.huahuo.netty.echo.server;
​
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;
​
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
​
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf data = (ByteBuf) msg;
        System.out.println("服务端接收到数据:"+data.toString(CharsetUtil.UTF_8));
​
        // 创建新的 ByteBuf,用来保存处理后的数据
        ByteBuf response = Unpooled.copiedBuffer("处理后的数据:" + data.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8);
​
        // 写回处理后的数据
        ctx.writeAndFlush(response);
    }
​
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("EchoServerHandler channelReadComplete");
    }
​
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
​
​

客户端代码

package com.huahuo.netty.echo.client;
​
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;
​
import java.net.InetSocketAddress;
​
public class EchoClient {
    private String host;
    private int port;
​
    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
​
    public void start() throws Exception {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //连接到服务器,connect异步连接,同步等待连接成功
            ChannelFuture channelFuture = bootstrap.connect().sync();
            //阻塞,直到客户端关闭。
            channelFuture.channel().closeFuture().sync();
        } finally {
            //优雅退出
            eventLoopGroup.shutdownGracefully();
        }
    }
​
    public static void main(String[] args) throws Exception {
        new EchoClient("127.0.0.1", 8080).start();
    }
}
package com.huahuo.netty.echo.client;
​
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
​
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
​
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("Client received:" + byteBuf.toString(CharsetUtil.UTF_8));
    }
​
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Active");
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello fireworks", CharsetUtil.UTF_8));
    }
​
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client read complete");
    }
​
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Echo服务流程分析

  1. EventLoop 和 EventLoopGroup

这就是相当于线程和线程组的关系。

  1. BootStrap

就是启动类,会自带一些配置

  1. Channel

就是一个到实体的连接的桥梁,生命周期,去配置生命周期会执行的函数

  1. ChannelHandler和ChannelPipline

就是用来配置Channel的处理类,pipline就是流水线

EventLoop和EventLoopGroup

EventLoop相当于一个线程,1个EventLoop可以服务多个Channel,1个Channel只有一个EventLoop,可以有多个EventLoop来优化性能,也就是EventLoopGroup。 EventLoopGroup负责分配EventLoop到新创建Channel。里面包含很多EventLoop。EventLoop会维护一个Selector模型,默认创建的线程数量是核数*2。

BootStrap

  • group:把两个线程池绑定进来,可以传入单个线程池,如果传入单个线程池,而且这个线程池数量指定为1,那就是单线程的reactor模型。如果不为1那就是多线程模型,传入两个线程池那就是主从线程模型了。
  • channel:用来设置IO通道的类型,一般设置为NIO

  • option:用来设置TCP连接的一些参数

    • SO_BACKLOG:存放已完成三次握手的请求的等待队列的长度

      • 半连接队列 tcp_max_syn_backlog
      • 全连接队列 net.core.somaxconn
    • TCP_NODELAY:如果要求实时性高,那就设置成true,他就关闭Nagle算法,就不会延迟发送了。

  • handler:用来设置处理器的

Channel

Channel:相当于一个通道

ChannelHandler:负责Channel的逻辑处理

ChannelPipeline:负责管理ChannelHandler的有序容器

Channel状态出现变化就会触发对应的事件