netty nio处理

深入解读Netty中的NIO:原理、架构与实现详解 Java NIO(New I/O)是一组新的Java I/O库,它与传统的Java I/O(即流式I/O)相比,提供了更高效的数据读写操作。Buffers:缓冲区是一个容器对象,包含要读写的数据。常见的缓冲区类型包括ByteBuffer、CharBuffer、IntBuffer等。Channels:通道是用于读写数据的抽象,与流类似,但通道是双向的,可以同时读写。Selectors:选择器用于监听多个通道的事件(如连接到达、数据可读等),实现非阻塞的多路复用I/O。 阅读详情

netty的nio处理NioWorerk类。

NioWorker类实现了 Runnable 接口。

register方法用于 open 一个 Selector。

    void register(NioSocketChannel channel, ChannelFuture future) {

        boolean server = !(channel instanceof NioClientSocketChannel);
        Runnable registerTask = new RegisterTask(channel, future, server);
        Selector selector;

        synchronized (startStopLock) {
            
            if (!started) {
                // Open a selector if this worker didn't start yet.
                try {
                    this.selector = selector = Selector.open();
                } catch (Throwable t) {
                    throw new ChannelException(
                            "Failed to create a selector.", t);
                }

                // Start the worker thread with the new Selector.
                String threadName =
                    (server ? "New I/O server worker #"
                            : "New I/O client worker #") + bossId + '-' + id;

                boolean success = false;
                try {
                	// 把 executor 放到了一个 ThreadLocal 里
                    DeadLockProofWorker.start(
                            executor, new ThreadRenamingRunnable(this, threadName));
                    success = true;
                } finally {
                    if (!success) {
                        // Release the Selector if the execution fails.
                        try {
                            selector.close();
                        } catch (Throwable t) {
                            logger.warn("Failed to close a selector.", t);
                        }
                        this.selector = selector = null;
                        // The method will return to the caller at this point.
                    }
                }
            } else {
                // Use the existing selector if this worker has been started.
                selector = this.selector;
            }

            assert selector != null && selector.isOpen();

            started = true;
            boolean offered = registerTaskQueue.offer(registerTask);
            assert offered;
        }

        if (wakenUp.compareAndSet(false, true)) {
            selector.wakeup();
        }
    }

run方法里,会处理 ready 的 channel。

@Override
    public void run() {
        thread = Thread.currentThread();

        boolean shutdown = false;
        Selector selector = this.selector;
        // 死循环 selector
        for (;;) {
            wakenUp.set(false);

            if (CONSTRAINT_LEVEL != 0) {
                selectorGuard.writeLock().lock();
                    // This empty synchronization block prevents the selector
                    // from acquiring its lock.
                selectorGuard.writeLock().unlock();
            }

            try {

            	// 查询已经 ready 的 channel,超时时间 500ms
                SelectorUtil.select(selector);

                // 'wakenUp.compareAndSet(false, true)' is always evaluated
                // before calling 'selector.wakeup()' to reduce the wake-up
                // overhead. (Selector.wakeup() is an expensive operation.)
                //
                // However, there is a race condition in this approach.
                // The race condition is triggered when 'wakenUp' is set to
                // true too early.
                //
                // 'wakenUp' is set to true too early if:
                // 1) Selector is waken up between 'wakenUp.set(false)' and
                //    'selector.select(...)'. (BAD)
                // 2) Selector is waken up between 'selector.select(...)' and
                //    'if (wakenUp.get()) { ... }'. (OK)
                //
                // In the first case, 'wakenUp' is set to true and the
                // following 'selector.select(...)' will wake up immediately.
                // Until 'wakenUp' is set to false again in the next round,
                // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
                // any attempt to wake up the Selector will fail, too, causing
                // the following 'selector.select(...)' call to block
                // unnecessarily.
                //
                // To fix this problem, we wake up the selector again if wakenUp
                // is true immediately after selector.select(...).
                // It is inefficient in that it wakes up the selector for both
                // the first case (BAD - wake-up required) and the second case
                // (OK - no wake-up required).

                if (wakenUp.get()) {
                    selector.wakeup();
                }

                cancelledKeys = 0;
                // 处理已经注册的任务
                processRegisterTaskQueue();
                // 处理写任务
                processWriteTaskQueue();
                processSelectedKeys(selector.selectedKeys());

                // Exit the loop when there's nothing to handle.
                // The shutdown flag is used to delay the shutdown of this
                // loop to avoid excessive Selector creation when
                // connections are registered in a one-by-one manner instead of
                // concurrent manner.
                if (selector.keys().isEmpty()) {
                    if (shutdown ||
                        executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) {

                        synchronized (startStopLock) {
                            if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
                                started = false;
                                try {
                                    selector.close();
                                } catch (IOException e) {
                                    logger.warn(
                                            "Failed to close a selector.", e);
                                } finally {
                                    this.selector = null;
                                }
                                break;
                            } else {
                                shutdown = false;
                            }
                        }
                    } else {
                        // Give one more second.
                        shutdown = true;
                    }
                } else {
                    shutdown = false;
                }
            } catch (Throwable t) {
                logger.warn(
                        "Unexpected exception in the selector loop.", t);

                // Prevent possible consecutive immediate failures that lead to
                // excessive CPU consumption.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
    }

这里面有三个方法。

// 处理已经注册的任务
processRegisterTaskQueue();
// 处理写任务
processWriteTaskQueue();
processSelectedKeys(selector.selectedKeys());

主要看

processSelectedKeys(selector.selectedKeys());

该方法里处理 read 和 write 的事件。

    private void processSelectedKeys(Set<SelectionKey> selectedKeys) throws IOException {
        for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
            SelectionKey k = i.next();
            i.remove();
            try {
                int readyOps = k.readyOps();
                if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
                    if (!read(k)) {
                        // Connection already closed - no need to handle write.
                        continue;
                    }
                }
                if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                    writeFromSelectorLoop(k);
                }
            } catch (CancelledKeyException e) {
                close(k);
            }

            if (cleanUpCancelledKeys()) {
                break; // break the loop to avoid ConcurrentModificationException
            }
        }
    }

在该方法里面,有 read 和 writeFromSelectorLoop 方法。
read方法里。

private boolean read(SelectionKey k) {
        final SocketChannel ch = (SocketChannel) k.channel();
        final NioSocketChannel channel = (NioSocketChannel) k.attachment();

        final ReceiveBufferSizePredictor predictor =
            channel.getConfig().getReceiveBufferSizePredictor();
        final int predictedRecvBufSize = predictor.nextReceiveBufferSize();

        int ret = 0;
        int readBytes = 0;
        boolean failure = true;

        ByteBuffer bb = recvBufferPool.acquire(predictedRecvBufSize);
        try {
            while ((ret = ch.read(bb)) > 0) {
                readBytes += ret;
                if (!bb.hasRemaining()) {
                    break;
                }
            }
            failure = false;
        } catch (ClosedChannelException e) {
            // Can happen, and does not need a user attention.
        } catch (Throwable t) {
            fireExceptionCaught(channel, t);
        }

        if (readBytes > 0) {
            bb.flip();

            final ChannelBufferFactory bufferFactory =
                channel.getConfig().getBufferFactory();
            final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes);
            buffer.setBytes(0, bb);
            buffer.writerIndex(readBytes);

            recvBufferPool.release(bb);

            // Update the predictor.
            predictor.previousReceiveBufferSize(readBytes);

            // Fire the event.
            // 经由 pipeline 处理
            fireMessageReceived(channel, buffer);
        } else {
            recvBufferPool.release(bb);
        }

        if (ret < 0 || failure) {
            k.cancel(); // Some JDK implementations run into an infinite loop without this.
            close(channel, succeededFuture(channel));
            return false;
        }

        return true;
    }

其中 fireMessageReceived(channel, buffer); 方法,会处理 pipeline 里的 upstream 事件。

/**
     * Sends a {@code "messageReceived"} event to the first
     * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
     * the specified {@link Channel} belongs.
     *
     * @param message        the received message
     * @param remoteAddress  the remote address where the received message
     *                       came from
     */
    public static void fireMessageReceived(Channel channel, Object message, SocketAddress remoteAddress) {
        channel.getPipeline().sendUpstream(
                new UpstreamMessageEvent(channel, message, remoteAddress));
    }

writeFromSelectorLoop 方法里,会调用 write0 方法。

private void write0(NioSocketChannel channel) {
        boolean open = true;
        boolean addOpWrite = false;
        boolean removeOpWrite = false;

        long writtenBytes = 0;

        final SocketSendBufferPool sendBufferPool = this.sendBufferPool;
        final SocketChannel ch = channel.socket;
        final Queue<MessageEvent> writeBuffer = channel.writeBuffer;
        final int writeSpinCount = channel.getConfig().getWriteSpinCount();
        synchronized (channel.writeLock) {
            channel.inWriteNowLoop = true;
            for (;;) {
                MessageEvent evt = channel.currentWriteEvent;
                SendBuffer buf;
                if (evt == null) {
                    if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) {
                        removeOpWrite = true;
                        channel.writeSuspended = false;
                        break;
                    }

                    channel.currentWriteBuffer = buf = sendBufferPool.acquire(evt.getMessage());
                } else {
                    buf = channel.currentWriteBuffer;
                }

                ChannelFuture future = evt.getFuture();
                try {
                    long localWrittenBytes = 0;
                    for (int i = writeSpinCount; i > 0; i --) {
                        localWrittenBytes = buf.transferTo(ch);
                        if (localWrittenBytes != 0) {
                            writtenBytes += localWrittenBytes;
                            break;
                        }
                        if (buf.finished()) {
                            break;
                        }
                    }

                    if (buf.finished()) {
                        // Successful write - proceed to the next message.
                        buf.release();
                        channel.currentWriteEvent = null;
                        channel.currentWriteBuffer = null;
                        evt = null;
                        buf = null;
                        future.setSuccess();
                    } else {
                        // Not written fully - perhaps the kernel buffer is full.
                        addOpWrite = true;
                        channel.writeSuspended = true;

                        if (localWrittenBytes > 0) {
                            // Notify progress listeners if necessary.
                            future.setProgress(
                                    localWrittenBytes,
                                    buf.writtenBytes(), buf.totalBytes());
                        }
                        break;
                    }
                } catch (AsynchronousCloseException e) {
                    // Doesn't need a user attention - ignore.
                } catch (Throwable t) {
                    buf.release();
                    channel.currentWriteEvent = null;
                    channel.currentWriteBuffer = null;
                    buf = null;
                    evt = null;
                    future.setFailure(t);
                    fireExceptionCaught(channel, t);
                    if (t instanceof IOException) {
                        open = false;
                        close(channel, succeededFuture(channel));
                    }
                }
            }
            channel.inWriteNowLoop = false;

            // Initially, the following block was executed after releasing
            // the writeLock, but there was a race condition, and it has to be
            // executed before releasing the writeLock:
            //
            //     https://issues.jboss.org/browse/NETTY-410
            //
            if (open) {
                if (addOpWrite) {
                    setOpWrite(channel);
                } else if (removeOpWrite) {
                    clearOpWrite(channel);
                }
            }
        }

        // 会经由 pipeline 处理
        fireWriteComplete(channel, writtenBytes);
    }

fireWriteComplete会经由 pipeline。

/**
     * Sends a {@code "writeComplete"} event to the first
     * {@link ChannelUpstreamHandler} in the {@link ChannelPipeline} of
     * the specified {@link Channel}.
     */
    public static void fireWriteComplete(Channel channel, long amount) {
        if (amount == 0) {
            return;
        }

        channel.getPipeline().sendUpstream(
                new DefaultWriteCompletionEvent(channel, amount));
    }
NIO的空轮询bug是什么?netty是如何解决NIO空轮询bug的? 文章目录1. NIO的空轮询bug2. netty如何解决NIO空轮询bug的? 1. NIO的空轮询bug         JDK1.5开始引入了epoll基于事件响应机制来优化NIO。相较于select和poll机制来说,epoll机制将事件处理交给了操作系统内核(操作系统硬中断)来处理,优化了elect和poll模型的无效遍历问题。        &n 阅读详情

相关推荐

从一次SocketException排查,聊聊BIO、NIONetty处理连接关闭时的差异

本文通过分析`java.net.SocketException`异常,深入探讨了BIO、NIONetty处理连接关闭时的机制差异。从BIO的同步阻塞困境到NIO的非阻塞革新,再到Netty的异步事件驱动模型,详细解析了各模型的优缺点及适用场景,为网络编程中的连接管理提供了实用建议。

weixin_30522095的博客 551

netty系列之NIO

最开始的阻塞式IO,它在每一个连接创建时,都需要一个用户线程来处理,并且在IO操作没有就绪或者结束时,线程被挂起,进入阻塞等待状态,阻塞式IO就成为导致性能瓶颈的根本原因。

qq_37436172的博客 731

面试题趣谈:Netty如何处理NIO底层epoll空轮询bug?

你了解NettyNIO epoll空轮询bug问题吗? 能否解释一下这个问题的成因和Netty是如何解决的?这个问题的成因是NIO底层epoll实现存在缺陷,它会频繁空轮询所有连接,即使没有新事件发生也会遍历所有连接。这会导致CPU使用率飙升到100%。Netty对这个问题进行了识别和处理。当它检测到Selector出现空轮询时,会立即创建一个新的Selector,然后将旧Selector上的SocketChannel重新注册到新Selector,最后关闭旧的Selector释放资源。

Gemini的博客 464

netty源码分析(二)-处理请求

上一篇对netty的启动过程做了分析,netty源码分析(一)-启动.本篇将对netty处理请求的主要过程进行源码层面分析。根据上一篇的最后部分内容我们知道,netty启动后会不断循环accept请求 public void run() { final Thread currentThread = Thread.currentThread();

jamesjxin的专栏 2075

009 netty实践_多worker线程组模式(实际生产中高性能应用netty)

Netty框架的工作原理 1. 基本过程描述如下 1)初始化创建 2 个 NioEventLoopGroup:其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。 2)基于 ServerBootstrap(服务端启动引导类):配置 EventLoopGroup、Channel 类型,连接参数、配置...

诸般世界 2344

bio、nionetty处理流程简析

本文只是说明流程,具体的代码网上可以找到,如果还没有跑过相关代码的,需要先找找相关资料了解。 Bio的处理流程如下图 当一个客户端请求服务端建立连接后,服务端会单独为客户端生成一个线程处理io以及后续逻辑。如果客户端数量过大,就会使服务端超负荷。如果使用线程池,客户端数量过大,后面的客户端就有可能不能建立连接。 Nio处理流程如下图 服务端会先生成一个ServerSocketChannel,...

qq_35359804的博客 288

Nettynio处理accept&read&write事件

TLV 格式,即 Type 类型、Length 长度、Value 数据,类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量。一种思路是首先分配一个较小的 buffer,例如 4k,如果发现数据不够,再分配 8k 的 buffer,将 4k buffer 内容拷贝至 8k buffer,优点是消息连续容易处理,缺点是数据拷贝耗费性能。如果不取消,会每次可写均会触发 write 事件。

m0_62645012的博客 2102

可能这是关于BIO-NIO-AIO-Netty处理模型最好理解的文章了

无意中看到别人总结BIO-NIO-AIO-Netty的前世今生,也在往上翻阅了一些帖子,总感觉讲的太过于繁杂,往往一个简单的概念层层拓展,盖过了文章的主题,所以我想图文并茂的,层层递进,简单点、再简单点的讲出来。所以本文中不会出现代码。讲的不好的,或者错误的请指正!! BIO(Blocking Input/Output) 处理步骤: 启动服务端,并循环监听客户端连接,每监听到一个请求,创建...

中年闰土的博客 27万+

NettyNetty 对 Java NIO 空轮询问题的处理

文章内容1. Java NIO 在 Linux 平台的空轮询问题1.1 空轮询问题的介绍1.1.1 空轮询的现象1.1.2 空轮询的原因1.2 空轮询的处理思路1.2.1 JDK 层面1.2.2 应用程序层面2. Netty 的应对措施 1. Java NIO 在 Linux 平台的空轮询问题 1.1 空轮询问题的介绍 1.1.1 空轮询的现象 Linux 下使用 IO 复用一般默认就是 epoll,Java NIO 在 Linux 平台默认使用的也是 epoll 机制。但是 JDK 中对接底层 epoll

fearless的博客 618

Netty 对 Java NIO 空轮询问题的处理

摘要: Java NIO在Linux平台使用epoll机制时存在空轮询问题,表现为Selector.select()在没有可处理IO事件时不断被唤醒,导致CPU占用100%。该问题源于底层epoll在socket异常终止(RST)时返回POLLHUP/POLLERR事件,但JDK未在SelectionKey中定义异常事件类型,导致上层无法处理。解决方案包括:JDK层面新增异常事件或扩大事件映射;应用层通过重建Selector(如Netty采用计数器统计空轮询次数,超过阈值512次时重建Selector)来规

刻苦的樊同学 821

IO模型到nettyNIO处理

网络I/O有两个交互过程: 阶段1 wait for data 等待数据准备 阶段2 copy data from kernel to user 将数据从内核拷贝到用户进程中 (1)blocking IO - 阻塞IO:阻塞等待数据准备及拷贝返回 (2)nonblocking IO :循环访问数据是否准备好 (3)IO multiplexing - IO多路复用:轮循多个socekt连接...

kkk6285137的专栏 1073

Netty——网络编程 NIO(Selector处理accept事件)代码示例

Netty——网络编程 NIO(Selector处理accept事件)代码示例

小志的博客 725

netty4心跳处理 (包括自己写的nio框架跟nginx)

  任何有关TCL、UDP的话题,都逃不过心跳包处理的命。  比如nginx或者自己写的nio框架都需要处理。  笔者就曾经自己写过基于nio的框架,心跳是这样处理的:服务端会启动一个特定的线程处理所有合法登陆的用户对象,并且指定时间内扫描客户端对象(向每一个客户端发送心跳包,客户端收到之后需要回复一个心跳),如果在指定时间内客户端没有返回任何数据,服务端会认为该客户端已经死掉了,然后踢掉它。  ...

czk740960212的专栏 2211

java nio 数据分包_netty之粘包分包的处理

1、netty在进行字节数组传输的时候,会出现粘包和分包的情况。当个数据还好,如果数据量很大。并且不间断的发送给服务器,这个时候就会出现粘包和分包的情况。2、简单来说:channelBuffer在接收包的时候,会在当时进行处理,但是当数据量一大,这个时候数据的分隔就不是很明显了。这个时候会出现数据多了或者少了的情况3、处理的方式,一般就是编解码。自己定义数据:数据长度+数据。这种简单的方式来实现。...

weixin_34239861的博客 779

NIOnetty(15) netty的编解码处理

Netty处理器重要概念: * 1.Netty处理器可以分为两类,入站处理器和出站处理器, * 2.入站处理器的顶层是ChannelInboundHandler,出站处理器的顶层是ChannelOutboundHandler * 3.数据处理时常用的各种解码器本质上都是处理器 * 4.编码:本质是一种出站处理器,因此编码一定是一种ChannelOutboundHandler * 5.解码:本质是...

m0_37139189的博客 256

第一章NettyNIO Selector的读事件处理详解

本文分析了NIO中OP_READ事件处理的核心逻辑与关键技术要点。文章首先指出TCP流式特性导致的粘包/拆包问题是主要难点,然后详细介绍了处理流程:通过channel.read()读取数据,根据不同返回值进行处理,特别强调了compact()方法在保留未处理数据方面的重要性。文中提供了完整的代码示例,包含粘包处理的基本实现,并解析了OP_READ的触发时机和性能优化技巧(使用附件Attachment)。最后指出了常见陷阱,如忽略返回值0、未处理半包和资源泄漏等问题。全文将OP_READ处理与前文讨论的Sel

ywl470812087的博客 149

netty半包粘包 处理_Java NIO 框架 Netty 之美:粘包与半包问题

一、前言Netty 是一个可以快速开发网络应用程序的 NIO 框架,它大大简化了 TCP 或者 UDP 服务器的网络编程。Netty 的简易和快速开发并不意味着由它开发的程序将失去可维护性或者存在性能问题,它的设计参考了许多协议的实现,比如 FTP、SMTP、HTTP 和各种二进制和基于文本的传统协议,因此 Netty 成功的实现了兼顾快速开发、性能、稳定性、灵活性为一体,不需要为了考虑一方面原因...

weixin_42237487的博客 267
上一篇: Network Delay Time
下一篇: tomcat/redis/dubbo/netty
寂寞灵魂
博客等级 码龄15年 111粉丝 284原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值