netty-read

本文深入剖析了Netty中AbstractNioByteChannel的read方法,详细解读了读操作的流程,包括分配缓冲区、读取数据、触发读事件、调整预分配大小等关键步骤,并解释了AdaptiveRecvByteBufAllocator如何智能调整读取大小。

AbstractNioByteChannel.read()

@Override
        public final void read() {
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);//每读一次,会加一次read
                    readPending = false;
                    pipeline.fireChannelRead(byteBuf);//触发事件
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();//调用record()每次读完会进行,预分配调整,默认第一次是1024
                pipeline.fireChannelReadComplete();//触发事件

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } 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();
                }
            }
 

DefaultMaxBytesRecvByteBufAllocator

   @Override
        public boolean continueReading() {
            return continueReading(defaultMaybeMoreSupplier);
        }

        @Override
        public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
            return config.isAutoRead() &&
                   maybeMoreDataSupplier.get() &&
                   totalMessages < maxMessagePerRead &&  //有判断读的次数
                   totalBytesRead > 0;
        }
     private final UncheckedBooleanSupplier defaultMaybeMoreSupplier = new UncheckedBooleanSupplier() {
            @Override
            public boolean get() {
                return attemptBytesRead == lastBytesRead;//默认分配了1024 只读了420,所以不继续读
            }
        };

AdaptiveRecvByteBufAllocator.HandleImpl计算每次需要的byteSize平均值

private final class HandleImpl extends MaxMessageHandle {
        private final int minIndex;
        private final int maxIndex;
        private int index;
        private int nextReceiveBufferSize;
        private boolean decreaseNow;

        public HandleImpl(int minIndex, int maxIndex, int initial) {
            this.minIndex = minIndex;
            this.maxIndex = maxIndex;

            index = getSizeTableIndex(initial);
            nextReceiveBufferSize = SIZE_TABLE[index];
        }

        @Override
        public int guess() {
            return nextReceiveBufferSize;
        }
//每次读完会进行,预分配调整,默认第一次是1024
        private void record(int actualReadBytes) {
            if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {
                if (decreaseNow) {
                    index = Math.max(index - INDEX_DECREMENT, minIndex);
                    nextReceiveBufferSize = SIZE_TABLE[index];
                    decreaseNow = false;
                } else {
                    decreaseNow = true;
                }
            } else if (actualReadBytes >= nextReceiveBufferSize) {
                index = Math.min(index + INDEX_INCREMENT, maxIndex);
                nextReceiveBufferSize = SIZE_TABLE[index];
                decreaseNow = false;
            }
        }

        @Override
        public void readComplete() {
            record(totalBytesRead());
        }
    }

    private static int getSizeTableIndex(final int size) {
        for (int low = 0, high = SIZE_TABLE.length - 1;;) {
            if (high < low) {
                return low;
            }
            if (high == low) {
                return high;
            }

            int mid = low + high >>> 1;
            int a = SIZE_TABLE[mid];
            int b = SIZE_TABLE[mid + 1];
            if (size > b) {
                low = mid + 1;
            } else if (size < a) {
                high = mid - 1;
            } else if (size == a) {
                return mid;
            } else {
                return mid + 1;
            }
        }
    }

导读

AbstractNioByteChannel.read()
  AdaptiveRecvByteBufAllocator allocHandle = recvBufAllocHandle()
  byteBuf = allocHandle.allocate(allocator);
  allocHandle.lastBytesRead(doReadBytes(byteBuf));
  allocHandle.incMessagesRead(1);
  readPending = false;
  pipeline.fireChannelRead(byteBuf);
  allocHandle.continueReading()
    continueReading(defaultMaybeMoreSupplier)
    bytesToRead > 0 && maybeMoreDataSupplier.get()
	    UncheckedBooleanSupplier.get()
	      return attemptBytesRead == lastBytesRead
  
  allocHandle.readComplete();
     record(totalBytesRead());
       nextReceiveBufferSize = SIZE_TABLE[index]
  pipeline.fireChannelReadComplete();
  closeOnRead(pipeline)
    if (!isInputShutdown0()) {
    	if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
	       shutdownInput();
	       pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
       }
    else
      doClose()
	      doClose0(promise);
	      outboundBuffer.failFlushed(cause, notify);
	      outboundBuffer.close(closeCause);
	      fireChannelInactiveAndDeregister(wasActive);
    pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE)
  if (!readPending && !config.isAutoRead()) {
      removeReadOp();
  }
按你说的之后C:\Users\admin\.jdks\jdk-17.0.12+7\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2024.3.6\lib\idea_rt.jar=52853" -Dfile.encoding=UTF-8 -classpath D:\UserData\Desktop\Netty和gRPC\Netty_TEST\target\classes;D:\r\io\netty\netty-all\4.1.86.Final\netty-all-4.1.86.Final.jar;D:\r\io\netty\netty-buffer\4.1.86.Final\netty-buffer-4.1.86.Final.jar;D:\r\io\netty\netty-codec\4.1.86.Final\netty-codec-4.1.86.Final.jar;D:\r\io\netty\netty-codec-dns\4.1.86.Final\netty-codec-dns-4.1.86.Final.jar;D:\r\io\netty\netty-codec-haproxy\4.1.86.Final\netty-codec-haproxy-4.1.86.Final.jar;D:\r\io\netty\netty-codec-http\4.1.86.Final\netty-codec-http-4.1.86.Final.jar;D:\r\io\netty\netty-codec-http2\4.1.86.Final\netty-codec-http2-4.1.86.Final.jar;D:\r\io\netty\netty-codec-memcache\4.1.86.Final\netty-codec-memcache-4.1.86.Final.jar;D:\r\io\netty\netty-codec-mqtt\4.1.86.Final\netty-codec-mqtt-4.1.86.Final.jar;D:\r\io\netty\netty-codec-redis\4.1.86.Final\netty-codec-redis-4.1.86.Final.jar;D:\r\io\netty\netty-codec-smtp\4.1.86.Final\netty-codec-smtp-4.1.86.Final.jar;D:\r\io\netty\netty-codec-socks\4.1.86.Final\netty-codec-socks-4.1.86.Final.jar;D:\r\io\netty\netty-codec-stomp\4.1.86.Final\netty-codec-stomp-4.1.86.Final.jar;D:\r\io\netty\netty-codec-xml\4.1.86.Final\netty-codec-xml-4.1.86.Final.jar;D:\r\io\netty\netty-common\4.1.86.Final\netty-common-4.1.86.Final.jar;D:\r\io\netty\netty-handler\4.1.86.Final\netty-handler-4.1.86.Final.jar;D:\r\io\netty\netty-transport-native-unix-common\4.1.86.Final\netty-transport-native-unix-common-4.1.86.Final.jar;D:\r\io\netty\netty-handler-proxy\4.1.86.Final\netty-handler-proxy-4.1.86.Final.jar;D:\r\io\netty\netty-handler-ssl-ocsp\4.1.86.Final\netty-handler-ssl-ocsp-4.1.86.Final.jar;D:\r\io\netty\netty-resolver\4.1.86.Final\netty-resolver-4.1.86.Final.jar;D:\r\io\netty\netty-resolver-dns\4.1.86.Final\netty-resolver-dns-4.1.86.Final.jar;D:\r\io\netty\netty-transport\4.1.86.Final\netty-transport-4.1.86.Final.jar;D:\r\io\netty\netty-transport-rxtx\4.1.86.Final\netty-transport-rxtx-4.1.86.Final.jar;D:\r\io\netty\netty-transport-sctp\4.1.86.Final\netty-transport-sctp-4.1.86.Final.jar;D:\r\io\netty\netty-transport-udt\4.1.86.Final\netty-transport-udt-4.1.86.Final.jar;D:\r\io\netty\netty-transport-classes-epoll\4.1.86.Final\netty-transport-classes-epoll-4.1.86.Final.jar;D:\r\io\netty\netty-transport-classes-kqueue\4.1.86.Final\netty-transport-classes-kqueue-4.1.86.Final.jar;D:\r\io\netty\netty-resolver-dns-classes-macos\4.1.86.Final\netty-resolver-dns-classes-macos-4.1.86.Final.jar;D:\r\io\netty\netty-transport-native-epoll\4.1.86.Final\netty-transport-native-epoll-4.1.86.Final-linux-x86_64.jar;D:\r\io\netty\netty-transport-native-epoll\4.1.86.Final\netty-transport-native-epoll-4.1.86.Final-linux-aarch_64.jar;D:\r\io\netty\netty-transport-native-kqueue\4.1.86.Final\netty-transport-native-kqueue-4.1.86.Final-osx-x86_64.jar;D:\r\io\netty\netty-transport-native-kqueue\4.1.86.Final\netty-transport-native-kqueue-4.1.86.Final-osx-aarch_64.jar;D:\r\io\netty\netty-resolver-dns-native-macos\4.1.86.Final\netty-resolver-dns-native-macos-4.1.86.Final-osx-x86_64.jar;D:\r\io\netty\netty-resolver-dns-native-macos\4.1.86.Final\netty-resolver-dns-native-macos-4.1.86.Final-osx-aarch_64.jar;D:\r\com\fasterxml\jackson\core\jackson-databind\2.13.0\jackson-databind-2.13.0.jar;D:\r\com\fasterxml\jackson\core\jackson-annotations\2.13.0\jackson-annotations-2.13.0.jar;D:\r\com\fasterxml\jackson\core\jackson-core\2.13.0\jackson-core-2.13.0.jar org.example.GreetServer 8月 25, 2025 5:29:14 下午 io.netty.channel.DefaultChannelPipeline onUnhandledInboundException 警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens at [Source: (String)"\u0000\u0000\u0000\u001E{"name":"Alice","greeting":""}"; line: 1, column: 2] at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391) at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735) at com.fasterxml.jackson.core.base.ParserMinimalBase._throwInvalidSpace(ParserMinimalBase.java:713) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipWSOrEnd(ReaderBasedJsonParser.java:2462) at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:698) at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4762) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4668) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3630) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3598) at org.example.GreetServerHandler.channelRead0(GreetServerHandler.java:26) at org.example.GreetServerHandler.channelRead0(GreetServerHandler.java:15) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:840)
最新发布
08-26
你遇到的错误是由于**服务器端在解析 JSON 数据时遇到了非法字符(空字节)**,这通常是因为你在构建消息体时没有正确处理消息长度字段和数据之间的边界。具体来说,Netty 的 `ByteBuf` 读取方式没有跳过前 4 字节的长度字段,导致 JSON 解析失败。 --- ### 🔍 错误分析 报错信息: ``` Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens ``` 说明 Jackson 在解析 JSON 时遇到了控制字符 `\u0000`(即空字节),这通常是因为你的消息中包含了用于表示消息长度的 4 字节,而这些字节被直接当作 JSON 内容的一部分了。 --- ### ✅ 正确的协议格式 你定义的数据报文协议如下: ``` ±------------------------------------------±-----------------------------------------------+ | message length (4 bytes) | message data (Json) | ±------------------------------------------±-----------------------------------------------+ ``` 也就是说,在发送和接收时,应该先读取 4 字节的消息长度,再根据这个长度读取消息内容。 --- ### 🛠️ 修改建议 #### 1. 修改 `GreetServerHandler.java` 的 `channelRead0` 方法 ```java @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { // 确保有 4 字节可以读取 if (msg.readableBytes() < 4) { return; // 不足 4 字节,无法读取长度 } // 标记当前读指针位置 msg.markReaderIndex(); // 读取消息长度 int length = msg.readInt(); // 检查是否有足够的数据可读 if (msg.readableBytes() < length) { msg.resetReaderIndex(); // 回退读指针 return; } // 读取实际的消息数据 byte[] data = new byte[length]; msg.readBytes(data); String json = new String(data); // 处理 JSON 请求 ObjectMapper mapper = new ObjectMapper(); GreetMessage request = mapper.readValue(json, GreetMessage.class); System.out.println("Received: " + request.getName()); // 构建响应 GreetMessage response = new GreetMessage(request.getName(), "Hello, " + request.getName()); String responseJson = mapper.writeValueAsString(response); // 构建并发送响应 ByteBuf out = Unpooled.buffer(4 + responseJson.length()); out.writeInt(responseJson.length()); out.writeBytes(responseJson.getBytes()); ctx.writeAndFlush(out); } ``` #### 2. 修改 `GreetClientHandler.java` 的 `channelRead0` 方法 ```java @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { if (msg.readableBytes() < 4) { return; } msg.markReaderIndex(); int length = msg.readInt(); if (msg.readableBytes() < length) { msg.resetReaderIndex(); return; } byte[] data = new byte[length]; msg.readBytes(data); String json = new String(data); ObjectMapper mapper = new ObjectMapper(); GreetMessage response = mapper.readValue(json, GreetMessage.class); System.out.println("Received from server: " + response.getGreeting()); } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值