You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android NIO读取大数据时SocketChannel报BUFFER_UNDERFLOW异常求助

问题分析与解决方案

你的问题核心在于当SSLEngine返回BUFFER_UNDERFLOW时,没有正确触发从SocketChannel读取更多数据的逻辑。当处理大于16KB的数据时,单次读取的网络数据不足以完成TLS包的解析,此时需要继续从socket读取补充数据,但你的代码在handleBufferUnderflow后仍停留在unwrap循环里,导致缓冲区数据不足,触发了TLS头解析失败的异常。

修复后的读取方法

下面是修正后的read方法,关键调整了BUFFER_UNDERFLOW的处理逻辑,确保触发再次读取socket数据:

protected ByteBuffer read(SocketChannel socketChannel, SSLEngine engine) throws Exception { 
    // 初始化缓冲区(如果未初始化)
    if (mPeerAppData == null) {
        mPeerAppData = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
    }
    if (mPeerNetData == null) {
        mPeerNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
    }

    boolean exitReadLoop = false;
    mPeerAppData.clear();
    while (!exitReadLoop) {
        // 从Socket读取数据到网络缓冲区
        int bytesRead = socketChannel.read(mPeerNetData);
        if (bytesRead > 0) {
            mPeerNetData.flip(); // 切换为读模式
            boolean needMoreData = false;
            while (mPeerNetData.hasRemaining() && !needMoreData) {
                SSLEngineResult result = engine.unwrap(mPeerNetData, mPeerAppData);
                switch (result.getStatus()) {
                    case OK:
                        // 处理剩余未解析的网络数据
                        if (!mPeerNetData.hasRemaining()) {
                            mPeerNetData.clear();
                        } else {
                            mPeerNetData.compact(); // 保留未处理的数据,避免丢失
                        }
                        // 当应用缓冲区有数据时标记可以退出
                        if (mPeerAppData.position() > 0) {
                            exitReadLoop = true;
                        }
                        break;
                    case BUFFER_OVERFLOW:
                        // 扩容应用缓冲区
                        mPeerAppData = enlargeApplicationBuffer(engine, mPeerAppData);
                        break;
                    case BUFFER_UNDERFLOW:
                        // 网络数据不足,需要读取更多数据
                        mPeerNetData = handleBufferUnderflow(engine, mPeerNetData);
                        needMoreData = true; // 跳出unwrap循环,回到Socket读取步骤
                        break;
                    case CLOSED:
                        closeConnection(socketChannel, engine);
                        return null;
                    default:
                        throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                }
            }
        } else if (bytesRead < 0) {
            handleEndOfStream(socketChannel, engine);
            return null;
        }
        // 未获取到足够数据时短暂等待后重试
        if (!exitReadLoop) {
            Thread.sleep(50);
        }
    }
    mPeerAppData.flip(); // 切换为读模式返回给调用方
    return mPeerAppData;
}

关键修改说明

  1. BUFFER_UNDERFLOW处理:设置needMoreData = true,跳出unwrap循环,回到外层逻辑重新读取Socket数据,补充到网络缓冲区。
  2. 缓冲区安全管理:使用compact()保留网络缓冲区中未处理的TLS片段,避免数据丢失。
  3. 循环条件优化:不再在第一次OK时强制退出,确保处理完所有可用的网络数据,只有当应用缓冲区有有效数据时才标记退出。

Conscrypt PSK TLS 完整通信示例

下面是一个基于Conscrypt的PSK TLS客户端完整示例,涵盖SSLContext初始化、SSLEngine配置和标准读写循环:

import org.conscrypt.Conscrypt;
import javax.net.ssl.*;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.security.Security;

public class ConscryptPskTlsClient {
    private static final String PSK_IDENTITY = "my_psk_identity";
    private static final byte[] PSK_KEY = "my_pre_shared_key_1234".getBytes();

    static {
        // 注册Conscrypt作为优先安全提供者
        Security.insertProviderAt(Conscrypt.newProvider(), 1);
    }

    public static void main(String[] args) throws Exception {
        // 自定义PSK密钥管理器
        PSKKeyManager pskKeyManager = new PSKKeyManager() {
            @Override
            public byte[] getKey(String identityHint, String identity) {
                return PSK_IDENTITY.equals(identity) ? PSK_KEY : null;
            }

            @Override
            public String getIdentity(String identityHint) {
                return PSK_IDENTITY;
            }

            @Override
            public String[] getSupportedIdentityHints() {
                return new String[]{PSK_IDENTITY};
            }
        };

        // 初始化SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "Conscrypt");
        sslContext.init(new KeyManager[]{pskKeyManager}, null, null);

        // 连接服务器
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8443));
        socketChannel.configureBlocking(false);

        // 配置SSLEngine
        SSLEngine engine = sslContext.createSSLEngine();
        engine.setUseClientMode(true);
        engine.beginHandshake();

        // 完成TLS握手
        doHandshake(socketChannel, engine);

        // 读取服务器响应
        ByteBuffer response = read(socketChannel, engine);
        if (response != null) {
            byte[] data = new byte[response.remaining()];
            response.get(data);
            System.out.println("Received response: " + new String(data));
        }

        // 关闭连接
        closeConnection(socketChannel, engine);
    }

    private static void doHandshake(SocketChannel socketChannel, SSLEngine engine) throws Exception {
        ByteBuffer netBuffer = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
        ByteBuffer appBuffer = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
        SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus();

        while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED &&
               handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
            switch (handshakeStatus) {
                case NEED_UNWRAP:
                    int bytesRead = socketChannel.read(netBuffer);
                    if (bytesRead > 0) {
                        netBuffer.flip();
                        SSLEngineResult result = engine.unwrap(netBuffer, appBuffer);
                        netBuffer.compact();
                        handshakeStatus = result.getHandshakeStatus();
                    }
                    break;
                case NEED_WRAP:
                    netBuffer.clear();
                    SSLEngineResult result = engine.wrap(appBuffer, netBuffer);
                    netBuffer.flip();
                    while (netBuffer.hasRemaining()) {
                        socketChannel.write(netBuffer);
                    }
                    handshakeStatus = result.getHandshakeStatus();
                    break;
                case NEED_TASK:
                    Runnable task;
                    while ((task = engine.getDelegatedTask()) != null) {
                        task.run();
                    }
                    handshakeStatus = engine.getHandshakeStatus();
                    break;
                default:
                    throw new IllegalStateException("Unexpected handshake status: " + handshakeStatus);
            }
            Thread.sleep(10);
        }
    }

    private static ByteBuffer read(SocketChannel socketChannel, SSLEngine engine) throws Exception {
        ByteBuffer peerNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
        ByteBuffer peerAppData = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
        boolean exitReadLoop = false;

        peerAppData.clear();
        while (!exitReadLoop) {
            int bytesRead = socketChannel.read(peerNetData);
            if (bytesRead > 0) {
                peerNetData.flip();
                boolean needMoreData = false;
                while (peerNetData.hasRemaining() && !needMoreData) {
                    SSLEngineResult result = engine.unwrap(peerNetData, peerAppData);
                    switch (result.getStatus()) {
                        case OK:
                            if (!peerNetData.hasRemaining()) {
                                peerNetData.clear();
                            } else {
                                peerNetData.compact();
                            }
                            if (peerAppData.position() > 0) {
                                exitReadLoop = true;
                            }
                            break;
                        case BUFFER_OVERFLOW:
                            peerAppData = enlargeApplicationBuffer(engine, peerAppData);
                            break;
                        case BUFFER_UNDERFLOW:
                            peerNetData = handleBufferUnderflow(engine, peerNetData);
                            needMoreData = true;
                            break;
                        case CLOSED:
                            closeConnection(socketChannel, engine);
                            return null;
                        default:
                            throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
                    }
                }
            } else if (bytesRead < 0) {
                handleEndOfStream(socketChannel, engine);
                return null;
            }
            if (!exitReadLoop) {
                Thread.sleep(50);
            }
        }
        peerAppData.flip();
        return peerAppData;
    }

    private static ByteBuffer enlargeApplicationBuffer(SSLEngine engine, ByteBuffer buffer) {
        int newCapacity = engine.getSession().getApplicationBufferSize();
        if (newCapacity <= buffer.capacity()) return buffer;
        ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity);
        buffer.flip();
        newBuffer.put(buffer);
        return newBuffer;
    }

    private static ByteBuffer handleBufferUnderflow(SSLEngine engine, ByteBuffer buffer) {
        int newCapacity = engine.getSession().getPacketBufferSize();
        if (newCapacity <= buffer.capacity()) {
            buffer.compact();
            return buffer;
        }
        ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity);
        buffer.flip();
        newBuffer.put(buffer);
        return newBuffer;
    }

    private static void closeConnection(SocketChannel socketChannel, SSLEngine engine) throws Exception {
        engine.closeOutbound();
        ByteBuffer netBuffer = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
        while (!engine.isOutboundDone() || !engine.isInboundDone()) {
            SSLEngineResult result = engine.wrap(ByteBuffer.allocate(0), netBuffer);
            netBuffer.flip();
            while (netBuffer.hasRemaining()) {
                socketChannel.write(netBuffer);
            }
            netBuffer.clear();
            if (result.getStatus() == SSLEngineResult.Status.CLOSED) break;
            Thread.sleep(10);
        }
        socketChannel.close();
    }

    private static void handleEndOfStream(SocketChannel socketChannel, SSLEngine engine) throws Exception {
        engine.closeInbound();
        closeConnection(socketChannel, engine);
    }
}

示例说明

  1. PSK配置:自定义PSKKeyManager实现,返回预共享密钥和身份标识,适配Conscrypt的PSK认证逻辑。
  2. 握手处理:专门的doHandshake方法处理TLS握手的全流程,覆盖NEED_WRAP/NEED_UNWRAP/NEED_TASK三种状态。
  3. 标准读写逻辑:严格遵循SSLEngine状态机,正确处理缓冲区溢出/不足的场景,确保大体积TLS数据能被完整解析。

按照这个逻辑调整后,应该能解决你遇到的大体积数据读取异常问题。

内容的提问来源于stack exchange,提问作者user3615458

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:16:26