Netty16——接收请求过程分析

 服务器启动后肯定是要接收客户端请求并返回客户端想要的数据的。那么Netty服务端在启动之后是如何接收客户端请求的呢?
 服务端启动之后,最终会注册一个 Accept 事件等待客户端的连接。NioServerSocketChannel 将自己注册到了 bossGroup线程池(reactor线程)上,也就是EventLoop(实际类型为NioEventLoop)上。在上一节《Netty启动过程分析》的末尾,我们有提到 NioEventLoop,其中有个run()方法,源码如下:

@Override
protected void run() {
    for (;;) {
        try {
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // fall through
                default:
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

 EventLoop 是一个死循环,在这个循环中做了 3 件事情:
  ①有条件的等待 NIO 事件;
  ②处理 NIO 事件;
  ③处理消息队列中的任务;
 上面的run()方法中调用了processSelectedKeys()方法,其源码如下:

private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

 接下来processSelectedKeysPlain()方法的源码:

private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
	...
    for (;;) {
        final SelectionKey k = i.next();
        final Object a = k.attachment();
        i.remove();
        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }
        ...
    }
}

 接着调用 processSelectedKey(k, (AbstractNioChannel) a)方法:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    ...
    try {
        ...
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

 当我们启动上一节的服务端,并监听端口8007,然后在浏览器中输入:http://localhost:8007时,就会和服务端建立连接,如果我们把断点打在 unsafe.read() 这一行时,会看到 readyOps 的值为16,即这是一个 Accept事件:
readyOps
 说明客户端(此时为浏览器)的请求已经进来了。
 这里的 unsafe 是 bossGroup 线程中 NioServerSocketChannel 的AbstractNioMessageChannel$NioMessageUnsafe 类型的对象。我们进入到其read()方法中:

@Override
public void read() {
    assert eventLoop().inEventLoop();
    final ChannelConfig config = config();
    final ChannelPipeline pipeline = pipeline();
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    allocHandle.reset(config);

    boolean closed = false;
    Throwable exception = null;
    try {
        try {
            do {
                int localRead = doReadMessages(readBuf);
                if (localRead == 0) {
                    break;
                }
                if (localRead < 0) {
                    closed = true;
                    break;
                }

                allocHandle.incMessagesRead(localRead);
            } while (allocHandle.continueReading());
        } catch (Throwable t) {
            exception = t;
        }

        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
        allocHandle.readComplete();
        pipeline.fireChannelReadComplete();

        if (exception != null) {
            closed = closeOnReadError(exception);

            pipeline.fireExceptionCaught(exception);
        }

        if (closed) {
            inputShutdown = true;
            if (isOpen()) {
                close(voidPromise());
            }
        }
    } finally {
        // Check if there is a readPending which was not processed yet.
        // This could be for two reasons:
        // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
        // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
        //
        // See https://github.com/netty/netty/issues/2254
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

 该方法主要做了以下几件事:
  ①检查该EventLoop线程是否是当前线程(assert eventLoop().inEventLoop()😉
  ②执行 doReadMessages(readBuf) 方法,其中的变量 readBuf 是一个List,也就是一个容器,该方法的作用是读取 bossGroup 线程中的 NioServerSocketChannel 接收到的请求,并把这些请求放进容器:

 @Override
 protected int doReadMessages(List<Object> buf) throws Exception {
     SocketChannel ch = SocketUtils.accept(javaChannel());
     try {
         if (ch != null) {
             buf.add(new NioSocketChannel(this, ch));
             return 1;
         }
     } catch (Throwable t) {
         logger.warn("Failed to create a new channel from an accepted socket.", t);
         try {
             ch.close();
         } catch (Throwable t2) {
             logger.warn("Failed to close a socket.", t2);
         }
     }
     return 0;
 }

   说明:该方法是通过 ServerSocket 的accept()方法获取到一个TCP连接,然后将其封装为Netty的NioSocketChannel对象,最后添加到容器中

public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
    try {
        return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
            @Override
            public SocketChannel run() throws IOException {
                return serverSocketChannel.accept();
            }
        });
    } catch (PrivilegedActionException e) {
        throw (IOException) e.getCause();
    }
}

  ③循环 readBuf 这个容器中的所有请求,执行 pipeline.fireChannelRead(readBuf.get(i))(即开始执行 在handler()方法中注册的handler的channelRead()),用于处理接收的这些请求或者其他事件,这其实相当于消费 readBuf 中注册的 NioSocketChannel,即处理所有新进来的连接,然后就开始执行管道中注册的 handler 的 channelRead()方法,需要强调的是,这里说的管道中注册的handler是通过handler()方法注册的handler而非通过childHandler()注册的handler

b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 100)
        .handler(new LoggingHandler(LogLevel.INFO))// 将会在每次客户端连接时调用
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                if (sslCtx != null) {
                    p.addLast(sslCtx.newHandler(ch.alloc()));
                }
                //p.addLast(new LoggingHandler(LogLevel.INFO));// 将会在每次客户端有事件发生时调用
                p.addLast(new EchoServerHandler());
            }
        });

   正如代码中的注释所表述的那样:通过handler()方法注册的handler在每次有客户端连接时会调用,通过childHandler()方法注册的handler则是在客户端连接建立成功之后有读写等操作时触发。
 服务端的代码中,我们只显示注册了一个LoggingHandler,其实Netty会帮我们注册另外几个handler,此例中,最终注册的handler有:Head、LoggingHandler、ServerBootstrapAcceptor、Tail。我们重点看下ServerBootstrapAcceptor:因为正是**ServerBootstrapAcceptor完成了将连接转交给workerGroup,同时为每个SocketChannel注册我们在childHandler()方法中指定的handler的工作**

public void channelRead(ChannelHandlerContext ctx, Object msg) {
     final Channel child = (Channel) msg;

     child.pipeline().addLast(childHandler);

     setChannelOptions(child, childOptions, logger);

     for (Entry<AttributeKey<?>, Object> e: childAttrs) {
         child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
     }

     try {
     	 // 将客户端连接注册到workerGroup的线程池中
         childGroup.register(child).addListener(new ChannelFutureListener() {
             @Override
             public void operationComplete(ChannelFuture future) throws Exception {
                 if (!future.isSuccess()) {
                     forceClose(child, future.cause());
                 }
             }
         });
     } catch (Throwable t) {
         forceClose(child, t);
     }
 }

 该方法做了以下几件事情:
  ①将msg强转为Channel,实际上是NioSocketChannel
  ②为①中的NioSocketChannel的pipeline添加handler,注意这里添加的handler是在childHandler()方法中指定的那些handler
  ③设置NioSocketChannel的各种属性
  ④将该NioSocketChannel注册到childGroup中的一个EventLoop上,并添加一个监听器,这个childGroup就是我们创建服务端时的workerGroup
 一路跟踪代码,会跟到AbstractNioChannel的doBeginRead()方法:

@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

 至此,针对这个客户端的连接就完成了,接下来就可以监听读事件了。
 总体流程:接收连接->创建一个新的NioSocketChannel->将NioSocketChannel注册到workerGroup的EventLoop->监听selector read事件。
 结合源代码来说,其流程主要有以下4步:
  ①服务端启动时创建的NioEventLoop的对象会通过死循环的方式轮询监听有没有新的客户端连接进来;
  ②监听到有新的客户端连接(即Accept事件)时,就创建一个NioSocketChannel,并将其置于一个容器中;
  ③遍历上述的容器,执行fireChannelRead()方法,使每一个新进来的客户端连接都经过我们在handler()方法中指定的handler的处理(即执行这些handler的channelRead()方法),需要特别注意的是,netty会隐式地为我们注册Head、ServerBootstrapAcceptor和Tail这三个handler,因此这三个handle地channelRead()方法也会得到执行
  ④在执行ServerBootstrapAcceptor地channelRead()方法时,会将②中创建的NioSocketChannel转交给workerGroup(将其注册到workerGroup中的EventLoop中,监听读写等事件),同时为这些NioSocketChannel绑定我们在childHandler()中指定的handler;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一个高性能、异步事件驱动的网络应用框架,特别适合于Java和Android平台。在Netty中,组播(Multicast)接收是指利用IP组播技术,在网络上发送单次消息给多个目标节点的功能。 要使用Netty实现组播接收,你需要做以下几个步骤: 1. **创建`DatagramPacket`和`DatagramChannel`**: Netty中的`io.netty.bootstrap.Bootstrap`用于初始化服务器端套接字,而`io.netty.channel.MulticastRecvConfig`用于配置组播接收参数。 ```java DatagramPacket packet = new DatagramPacket(buffer, address); DatagramChannel channel = bootstrap.child().option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.IP_MULTICAST_LOOP, true) // 允许组播报文被网卡再次转发到本地 .option(ChannelOption.IP_MULTICAST_TTL, ttl) // 设置组播包的TTL(时间到生存期) .option(ChannelOption.IP_MULTICAST_IF, ifAddress) // 设置接收接口 .channel(DatagramChannel.class) .connect(new InetSocketAddress(MULTICAST_IP, MULTICAST_PORT)) .sync(); ``` 2. **加入组播组**: 使用`joinGroup`方法连接到特定的组播组。 ```java InetAddress multicastGroup = InetAddress.getByName(MULTICAST_IP); channel.joinGroup(multicastGroup); ``` 3. **处理组播报文**: 当收到组播报文时,会触发`ChannelHandlerContext#read()`方法,你可以在这里处理数据。 ```java channel.pipeline().addLast(new MessageHandler(packet)); ``` 4. **退出组播组**: 当不再需要接收时,调用`leaveGroup`方法退出组播组。 ```java channel.leaveGroup(multicastGroup); channel.close(); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值