Netty总结

本文详细介绍了Netty的基础知识,包括Linux系统中的IO线程模型、Java NIO概述、Netty的使用以及半包/粘包问题的解决。此外,还探讨了Netty的序列化和反序列化,以及基于Netty构建自定义协议的方法。文章通过源码分析解释了Netty Server端的启动流程,揭示了其内部工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

📚Linux系统IO线程模型

在Linnux系统级别,提供了5种IO线程模型,分别是同步阻塞IO、同步非阻塞IO、多路服务用IO、信号驱动IO和异步IO。

在了解IO模型之前,我们要对用户空间和内核空间有一定的了解,在进行IO的时候,用户线程它不是直接操作的数据,而是把请求交给了操作系统,由操作系统与磁盘或网卡进行交互获取数据,之后,数据会被操作系统放入内核缓冲区,然后用户线程会从内核缓冲区服务数据到用户缓冲区。

对于同步阻塞IO,在数据被从内核缓冲区成功复制到用户空间缓冲区前,用户线程是被阻塞的,也就是说,直到数据被准备好以前,用户线程什么也做不了。

对于同步非阻塞线程,当数据读取请求交给操作系统后,用户线程就会返回,并不会一直阻塞,一般在这种情况下,需要不断轮询来确定数据是否已经在内核空间了,如何是,则会同步的将数据从内核空间复制到用户空间缓冲区。

以上两种IO模型,都是一个线程对应一个读写的,在并发量比较大的情况下,会造成线程数量暴增,而IO多路复用技术就是为了解决这个问题,在这种模型下,一个线程可以监听多个线程对应的IO数据是否已经就绪,如果就绪再进行后续处理,一般被用在网络IO上,而磁盘IO并不会用到这种技术。

对于信号驱动IO,可以与多路服务用IO进行对比着理解,多路复用IO在检查IO状态时是通过轮询的方式实现的,这种方式在被监听线程数量比较多时,效率比较低,而信号驱动IO指的是,不需要用户线程不断轮询,而是当数据被读取到内核缓冲区后,系统会自动通知用户线程,这样用户线程就能被动的知道哪些数据已经准备好了,从而避免不断轮询,目前Java NIO底层就是基于这种信号驱动IO实现的IO多路复用。

而异步IO把数据从内核缓冲区到用户缓冲区也进行了异步化,就是,当数据已经从内核缓冲区到用户缓冲区时,系统才会通知用户线程,这是真正意义的异步IO。

Java中的NIO是采用多路复用的IO模型,底层基于调用的是基于信号驱动实现的多路服务用IO,也就是我们常说的epoll。

📚Java NIO概述

Java目前支持的IO模型有BIO、NIO和AIO,NIO是在jdk1.4中被引入的,主要为了解决BIO服务无法应对高性能网络编程的问题,Java NIO底层基于epoll模型实现,编程模型中主要涉及Buffer、Channel和Selector等概念,其中Buffer是数据的载体在Channel中传递,Channel类似于BIO中流的概念,但Channel是全双工的,Selector是选择器,主要用于监听注册在他上面的Channel,Selector只适用于网络IO,对于文件IO,并没有Selector的概念。

下面看两个基于BIO和NIO开发的Demo

  • BIO
package test.netty.bio;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

public class TimeServer {

    public static void main(String[] args) throws Exception {

        int port = 8080;

        ServerSocket serverSocket = null ;
        try {
            serverSocket = new ServerSocket(port) ;
            System.out.println("Time server is started in port " + port);
            Socket socket = null ;
            while (true) {
                socket = serverSocket.accept();
                new Thread(new TimeServerHanler(socket)).start();
            }
        }finally {

        }

    }

    private static class TimeServerHanler implements Runnable {

        private Socket socket ;
        public TimeServerHanler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
                out = new PrintWriter(socket.getOutputStream());

                String currentTime ;
                String body ;

                //while (true) {
                    body = in.readLine() ;
                    /*if(Objects.isNull(body)) {
                        break;
                    }*/
                //}
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
                currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? dateFormat.format(new Date()) : "BAD ORDER";
                out.println(currentTime);
            }catch (Exception ex) {
                try {
                    in.close();
                    out.close();
                    socket.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



package test.netty.bio;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;

public class TimeClient {

    public static void main(String[] args) throws Exception {

        Socket socket = new Socket("127.0.0.1", 8080);
        //socket.bind(new InetSocketAddress(8080));

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
        PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
        printWriter.write("QUERY TIME ORDER");
        //printWriter.ne
        printWriter.flush();
        System.out.println("send order 2 server success.");
        String response = bufferedReader.readLine();
        System.out.println("time server response : " + response);
    }
}

  • NIO
package test.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class TimeServer2 {


    /**
     * NIO Server
     * 1. 打开ServerSocketChannel,监听客户端连接
     * 2. 绑定监听端口,设置为非阻塞
     * 3. 创建Reactor线程,创建多路复用器,并启动线程
     * 4. 将ServerSocketChannel注册到Reactor线程 多路复用器上,监听accept事件
     * 5. 多路复用器在while中无限循环 accept事件
     * 6. 拿到客户端连接对应的SocketChannel
     * 7. 将SocketChannel也设置为非阻塞模式
     * 8. 将SocketChannel注册到Reactor线程的Selector上,并监听read事件
     * 9. 读取数据到bytebuffer
     * 10. 把数据交给业务线程池进行处理
     * 11. 将encode后的pojo通过SocketChannel写回给客户端
     * */

    public static void main(String[] args) {
        MultiplexerTimeServer multiplexerTimeServer = new MultiplexerTimeServer(8080);
        //该线程为Reactor线程
        new Thread(multiplexerTimeServer,"MultiplexerServer").start();
    }


    private static class MultiplexerTimeServer implements Runnable {

        //Reactor Selector
        private Selector selector ;
        //Server Main Thread
        private ServerSocketChannel serverSocketChannel ;

        private volatile boolean stop;

        public MultiplexerTimeServer(int port) {
            init(port);
        }

        //init server
        public void init(int port) {
            try {
                /**
                 *
                 * */
                serverSocketChannel = ServerSocketChannel.open();
                selector = Selector.open();

                serverSocketChannel.configureBlocking(false);
                serverSocketChannel.bind(new InetSocketAddress(port), 1024);
                //监听连接事件
                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

                System.out.println("server initialize complete.");
            }catch (Exception ex) {
                System.out.println("init server error," + ex.getMessage());
                ex.printStackTrace();
            }
        }

        public void stop() {
            this.stop = true;
        }

        @Override
        public void run() {
            /**
             * 这里是reactor线程逻辑
             * 主要负责监听客户端连接事件
             * */
            while (!stop) {
                try {
                    selector.select(1000);
                    Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                    //这里拿到所有连接成功的客户端连接
                    Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
                    SelectionKey selectionKey = null ;
                    while (selectionKeyIterator.hasNext()) {
                        selectionKey = selectionKeyIterator.next();
                        selectionKeyIterator.remove();
                        try {
                            handleInput(selectionKey);
                        }catch (IOException ex) {
                            if (selectionKey != null) {
                                selectionKey.cancel();
                                if (selectionKey.channel() != null) {
                                    selectionKey.channel().close();
                                }
                            }
                        }

                    }
                }catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }

        //处理接入成功的请求
        private void handleInput(SelectionKey key) throws IOException {
            if(key.isValid()) {
                //判断是否为接入状态
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    //将对应的客户端连接重新注册到Selector上,此时监听READ事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }


                if(key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel)key.channel();

                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int readBytes = socketChannel.read(byteBuffer);
                    if(readBytes > 0) {
                        //复位读写位置
                        byteBuffer.flip();

                        byte[] bs = new byte[byteBuffer.remaining()];
                        byteBuffer.get(bs);

                        String body = new String(bs, "UTF-8");
                        System.out.println("Time server receive order:" + body);
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
                        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? dateFormat.format(new Date()) : "BAD ORDER";

                        //将响应写回到客户端
                        byte[] respbs = currentTime.getBytes();
                        ByteBuffer respBuffer = ByteBuffer.allocate(respbs.length);
                        respBuffer.put(respbs);
                        respBuffer.flip();
                        socketChannel.write(respBuffer);

                    }
                }

            }
        }
    }
}


package test.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class TimeClient2 {




    private static class TimeClientHandler implements Runnable {

        private String host;
        private int port;
        private Selector selector;
        private SocketChannel socketChannel;
        private boolean stop;

        public TimeClientHandler(String host, int port) {
            this.host = host;
            this.port = port;
            try {
                selector = Selector.open();
                socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(false);
            }catch (Exception ex) {
                ex.printStackTrace();
                System.exit(-1);
            }
        }

        @Override
        public void run() {
            try {
                doConnect();
            }catch (IOException ex) {
                ex.printStackTrace();
                System.exit(-1);
            }

            while (!stop) {
                try {
                    selector.select(1000);
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
                    SelectionKey selectionKey = null;
                    while (selectionKeyIterator.hasNext()) {
                        selectionKey = selectionKeyIterator.next();
                        selectionKeyIterator.remove();
                        try {
                            handleInput(selectionKey);
                        }catch (Exception ex) {
                            selectionKey.cancel();
                            if (selectionKey.channel() != null) {
                                selectionKey.channel().close();
                            }
                        }
                    }

                }catch (Exception ex) {

                }
            }
        }

        private void handleInput(SelectionKey key) throws IOException {
            if(key.isValid()) {
                SocketChannel socketChannel = (SocketChannel)key.channel();
                if(key.isConnectable()) {
                    if(socketChannel.finishConnect()) {
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        doWrite(socketChannel);
                    }else {
                        //连接失败
                        System.exit(-1);
                    }

                }

                if(key.isReadable()) {
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int readBs = socketChannel.read(byteBuffer);
                    if (readBs > 0) {
                        byte[] bs = new byte[byteBuffer.remaining()];
                        byteBuffer.get(bs);
                        String body = new String(bs, "UTF-8");
                        System.out.println("Now is " + body);
                        this.stop = true;
                    }else if (readBs < 0){
                        key.cancel();
                        socketChannel.close();
                    } else {
                        //ignore
                    }
                }
            }
        }

        //创建于服务端的连接
        private void doConnect() throws IOException  {
            if(socketChannel.connect(new InetSocketAddress(host,port))) {
                //连接成功,注册读事件
                socketChannel.register(selector, SelectionKey.OP_READ);
                //开始写
                doWrite(socketChannel);
            }else {
                //注册连接成功事件
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
            }
        }

        private void doWrite(SocketChannel socketChannel) throws IOException {
            byte[] req = "QUERY TIME ORDER".getBytes();
            ByteBuffer byteBuffer = ByteBuffer.allocate(req.length);
            byteBuffer.put(req);
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            if (!byteBuffer.hasRemaining()) {
                System.out.println("send command to server success.");
            }
        }
     }
}

从代码量上,可以很明显的看出,基于BIO实现的时间服务器要比基于NIO实现的时间服务器要简单的多,但是BIO无法应对大量的客户端连接,而NIO利用多路复用技术实现了一个reactor线程同时监听很多客户端连接,极大的避免了在大并发情况下服务器端线程数量暴增的问题。

那么,为什么还要使用netty呢,主要有以下几个原因

  • netty提供了简单的编程模型,解决了nio的易用性问题
  • netty解决了nio的很多内部bug,比如epoll空转问题
  • netty提供了很多通用组件来解决网络编程中的通用问题,例如半包/粘包问题

netty虽好,但也不是在所有场景下都有必要用,对于一些比较简单的应用程序,本身连接数量就很少,而须后续也不会再有所发展,这时候,反而BIO是更好的选择,原因在于可以大大减少编码的复杂度,相对于BIO来说,无论是NIO还是Netty,写起来都会复杂些。

📚Netty概述

netty是一个网络编程框架,主要用来编写Client-Server模式下的应用程序,netty基于java nio开发,实现了基于事件的IO模型,同时,基于屏蔽的大量java nio的复杂细节,解决了一些java nio的bug,提供了很多在网络编程过程中通用的组件,例如解决粘包/半包问题、序列化等等,netty是目前最流行的tcp通信框架。

先用netty来改造一下上面的TimeServer

package timeserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.text.SimpleDateFormat;
import java.util.Date;

public class NettyTimeServer {

    public static void main(String[] args)throws Exception {
        int port = 8080;
        new NettyTimeServer().bind(port);
    }

    public void bind(int port) throws Exception {
        //监听Accept实现的线程
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //监听Read事件及处理业务的线程
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024);
            //设置处理业务逻辑的Handler
            b.childHandler(new ChildChannelHandler());

            ChannelFuture future = b.bind(port).sync();
            future.channel().closeFuture().sync();
        }finally {
            //停掉线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

    private static class TimeServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //super.channelRead(ctx, msg);
            ByteBuf byteBuf = (ByteBuf)msg;
            byte[] bs = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bs);

            String body = new String(bs, "UTF-8");
            System.out.println("Time Server receive order:" + body);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? simpleDateFormat.format(new Date()) : "BAD ORDER";
            ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
            ctx.write(byteBuf);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            //super.channelReadComplete(ctx);
            ctx.flush();
        }

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


package timeserver;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyTimeClient {

    public static void main(String[] args) throws Exception {
        new NettyTimeClient().connect("127.0.0.1", 8080);
    }

    public void connect(String host, int port) throws Exception {

        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new TimeClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
        }
    }


    public static class TimeClientHandler extends ChannelInboundHandlerAdapter {

        private ByteBuf firstMsg;
        public TimeClientHandler() {
            byte[] bs = "QUERY TIME ORDER".getBytes();
            firstMsg = Unpooled.copiedBuffer(bs);
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            //链接成功后,发送消息到服务端
            ctx.write(firstMsg);
            ctx.flush();
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf byteBuf = (ByteBuf)msg;
            byte[] req = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(req);
            String body = new String(req, "UTF-8");
            System.out.println("Now is " + body);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("ex " + cause.getMessage());
            ctx.close();
        }
    }
}

📚半包/粘包问题

粘包/半包问题是tcp编程过程中必然会面临的一个问题,由于tcp协议中的数据是以码流的方式传输的,不包含任何的业务逻辑,在传输数据时,会根据tcp缓冲区的使用情况进行数据包的划分,这就造成了上层应用中的数据大的被拆分,小的被合并的结果,也就是粘包/拆包问题。一般解决粘包拆包问题的通用方案有三种

  • 根据特殊字符分隔数据包
  • 根据固定长度分隔数据包
  • 将消息分为消息头和消息体,在消息头中记录消息体的长度,这也是最常用的一种

针对这三种解决方案,netty内置了三个具体的实现来解决对应的问题,这三个实现分别是

  • DelimiterBasedFrameDecoder/LineBasedFrameDecoder
  • FixedLengthFrameDecoder
  • LengthFieldBasedFrameDecoder/LengthFieldPrepender

📚序列化和反序列化

序列化和反序列化经常用来表示把一个Java POJO转换成二进制字节数组和从二进制字节数组中解析出Java POJO对象,有时候也被称为编码和解码,实际上,在netty中有两次编码和解码

  • 客户端第一次是将POJO对象转换成二进制字节数组
  • 客户端第二次将字节数组在tcp协议层进行编码,也就是我我们前面说的粘包/半包的概念中涉及到的编码和解码
  • 服务端第一次按照tcp协议格式解析出对应的字节数组
  • 在将字节数组反序列化成POJO对象

netty对着两个步骤的抽象分别是

  • MessageToMessageDecoder/MessageToByteDecoder
  • MessageToMessageEncoder/MessageToByteEncoder

这里的序列化和反序列化对应的就是MessageToMessageEncoder和MessageToMessageDecoder,在java中,最常见的序列化方式是Serializable,但是这种序列化方式有很大的缺陷

  • 不能跨平台
  • 序列化后的码流太大
  • 性能比较差

业界有一些比较流程的开源序列化框架,例如avro、facebook的thrift、google的protobuf、messagepack等等,netty被用的最多的就是用于构建分布式系统的底层通信,所以一般都会采用这些性能比较好的、能够支持跨平台的序列化框架。

📚简述基于netty的自定义协议

协议一般分为两种,一种是共有协议,类似于http、ftp等,一种是私有协议,最典型的,我们在使用dubbo的时候用到的dubbo协议,这些协议都属于应用层协议,是用于满足特定业务需求而存在的。

私有协议包含一个自定义的协议栈,主要设计协议的数据结构,例如,可以让协议包含Header和Body两个部分,Header中包含一些公共信息,crcCode、length、sessionId、type、priority、attachment等,body存储真实的消息数据,通过length属性配合netty的LengthFieldBasedFrameDecoder很容易解决粘包/半包问题,crcCode可以理解为一个协议的魔法数字,用来标记当前数据包是指定的协议,sessionId表示当前链接为唯一标识,也可以标识节点的唯一标识,type标识数据的类型,例如心跳数据、业务数据、握手数据等,通过还可以区分不同来下的消息的流向,attachement是一个扩展属性,可以加一些附加的动态数据,格式为(size)(keylength)key(valuelength)value[…],消息体的格式为(length)message,之所以增加这么多length,主要是为了能正确的读取数据。

另外在实现私有协议时,还需要考虑其他的问题,例如IP白名单验证、重复连接验证、心跳保活、断线重连等。

对于IP白名单验证,我们可以在activeChannel()中发送连接握手消息,服务端在readChannel中读取消息,如果是连接握手消息的话,首先根据缓存判断是否已经创建过连接,如果已经连接过则返回-1,表示连接被拒绝,然后验证IP白名单,如果不在白名单中,则返回-1,拒绝连接;然后,客户端在readChannel中读取到握手响应消息,判断是否通过,如果没通过,则调用ctx.close()关闭连接,如果通过,则将消息透传到下一个Handler。

当客户端的处理心跳的Handler收到被握手认证的Handler透传过来的握手成功的消息后,启动一个定时任务,每5秒钟通过ctx向服务端发送一个心跳消息包,服务器在readChannel中判断,如果是心跳包,直接返回应答消息。

短信重连逻辑可以写在try {} finally{ 断线重连 } 。

📚netty server端的源码分析

netty server端主要包含两方面的工作,一是启动在指定端口上的监听,二是接收客户端连接并处理客户端数据。我们先回顾一下利用netty创建server端的过程

1. 创建ServerBootstrap启动辅助类
2. 设置Reactor主从线程池
3. 设置Server端channel类型,NioServerSocketChannel
4. 设置tcp参数,例如backlog
5. 设置childHandler,在ChannelPipeline中设置ChannelHandler链
6. 调用bind方法启动server在指定端口上的监听

那么,我们先来看一下,Reactor主从线程池的设置

ServerBootstrap.java
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { 
	super.group(parentGroup);   //设置master线程池
	this.childGroup = childGroup;  //设置slave线程组
}

AbstractBootstrap.java
public B group(EventLoopGroup group) { 
	this.group = group;
}

可以看到, master线程池被设置到了父类中,而slave线程池设置到了ServerBootstrap中,其实这也很好理解,因为父类抽象了处理Accept事件的核心逻辑,而对Accept事件的处理需要用到master线程池。

下面我们从bind方法入手,来看一下启动的整个流程

AbstractBootstrap.java
private ChannelFuture doBind(final SocketAddress localAddress) {
        //① 这里是一个重点
        final ChannelFuture regFuture = this.initAndRegister();
        .... 
        //②启动server在端口上的监听
        doBind0(regFuture, channel, localAddress, promise);
 }
 
 final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            /*
            	创建NioServerSocketChannel实例
            	这里的channelFactor是在我们调用ServerBootstrap.channl方法时被初始化的
            	public B channel(Class<? extends C> channelClass) {
        				return this.channelFactory((io.netty.channel.ChannelFactory)(new 			ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))));
   				 }
   				 可以看到用到的是ReflectiveChannelFactory,这个Factory会通过反射的方式调用NioServerSocketchannel的构造函数
   				 构造实例对象
            */
            channel = this.channelFactory.newChannel();
            //这里也是一个重点,主要完成的是对NioServerSocketChannel的初始化工作
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }
            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }
		/**
			重点来了,这里是将NioServerSocketChannel注册到EventLoop上,这里用到的EventLoop就是下面方法中ch.eventLoop()拿到的实例。
          	我们一点一点看,下看group(),这个方法最终会调用
          	public final EventLoopGroup group() {
        			return this.group;
    		}
    		返回的实际上就是我们在初始化ServerBootstrap中调用group时指定的bossGroup
    		register(channel)我们移步到下面,单独看一下
		*/
        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

//这个方法在父类中是抽象的,在子类ServerBootstrap中实现
void init(Channel channel) {
        setChannelOptions(channel, (Entry[])this.options0().entrySet().toArray(newOptionArray(0)), logger);
        //设置tcp连接参数
        setAttributes(channel, (Entry[])this.attrs0().entrySet().toArray(newAttrArray(0)));
        ChannelPipeline p = channel.pipeline();
        //这里需要注意以下,childHandler是我们制定的slave线程池
        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(0));
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(0));
        //这里开始初始化针对master的ChannelHandler
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                //这里实际上是获取我们在初始化时利用ServerBootstrap.handler()方法制定的ChannelHandler对象,
                //如果没有特殊需求,一般不会制定
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }
				/**
					这里是重点了
					先从NioServerSocketChannel中获取与之绑定的EventLoop,EventLoop可以理解为线程,EventLoopGroup可以理解为线程池
					在线程上绑定了一个任务,这个任务是在ChannelPipeline上绑定了一个handler
					这个handler的名称是ServerBootstrapAcceptor
					从名字上看,就是处理tcp的accept时间的,也就是reactor主从模式下master的任务
					至于ServerBootstrapAcceptor为什么会处理到accetp事件,还的回到上面的init方法中去看
				*/
                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }

//由于上面用到的EventLoopGroup类型为MultithreadEventLoopGroup,所以,我们直接看一下这个类的register方法
public ChannelFuture register(Channel channel) {
        return this.next().register(channel);
}
public EventLoop next() {
        return (EventLoop)super.next();
}
/**
	super.next(),这里用到了一个chooser,从名字上看是一个选择器,类型为EventExecutorChooser
	在netty中提供了两种类型的chooser,一种是GenericEventExecutorChooser,一种是PowerOfTwoEventExecutorChooser
	GenericEventExecutorChooser:
	public EventExecutor next() {
            return this.executors[Math.abs(this.idx.getAndIncrement() % this.executors.length)];
        }
     PowerOfTwoEventExecutorChooser:
     public EventExecutor next() {
            return this.executors[this.idx.getAndIncrement() & this.executors.length - 1];
     }

	可以看到,这两种类型的主要区别是获取EventExecutor实例的算法不同,第一个通过取余的方式来定位一个executor
	而第二个是通过位运算,也就是说,当EventLoopGroup中EventLoop的数量为2的整数次幂时,会用第二种方式来选择executor
	这种方式更加高效,与HashMap在初始化时将容量动态调整为2的整数次幂原理相同,都是为了提高运算速度。
*/
public EventExecutor next() {
        return this.chooser.next();
 }


然后再看一下register方法,这里的register方法是在SingleThreadEventLoop中
public ChannelFuture register(Channel channel) {
        return this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));
}

public ChannelFuture register(ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
}

这里,我们先看unsafe方法返回的是什么
public AbstractNioChannel.NioUnsafe unsafe() {
        return (AbstractNioChannel.NioUnsafe)super.unsafe();
}
AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) { 
	eventLoop.execute(new Runnable() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);
                            }
                        });
}
private void register0(ChannelPromise promise) { 
		AbstractChannel.this.doRegister();
		this.safeSetSuccess(promise);
        AbstractChannel.this.pipeline.fireChannelRegistered();
}
AbstractNioChannel.java
protected void doRegister() throws Exception { 
    这里就到了jdk的NIO代码了,
	this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
}

但是这里注册的时间类型是0,而不是Accept(16),实际上注册0时,简单的说就是能接收到所有事件类型,个人理解应该是
在服务端启动的时候防止有客户端连接被漏掉,在注册完成以后,会修改注册事件类型为accept。

在这里插入图片描述
到此,Netty的Server端启动流程就分析完了,实际上并不太复杂

  1. 初始化参数,例如bossGroup/workerGroup/NioServerSocketChannel/tcp参数/childHandler
  2. 调用bind开始启动流程
  3. 通过反射创建NioServerSocketChannel实例
  4. 初始化NioServerSocketChannel,例如初始化tcp参数,初始化ChannelHandler
  5. 在bossGroup中设置一个ServerBootstrapAcceptor,由bossGroup中的线程驱动,作用是将read事件分发给workerGroup
  6. 紧接着,就是注册NioServerSocketChannel,这一步实际上就做了一件事,在Selector上绑定accept事件
  7. 最后,调用doBind方法启动NioServerSocketChannel在指定端口上的监听
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值