ByteBuffer - 编码字符串和 put 与 CharSet 编码之间的区别?

ByteBuffer - difference between encoding string and put vs CharSet encode?

我有两种不同的方法从字符串创建 ByteBuffer 对象:

  1. 从字符串中获取 byte[] 并使用 ByteBuffer.put(byte[]) 方法:
private ByteBuffer respWithPut() {
    ByteBuffer respBuf = ByteBuffer.allocate(1024);
    respBuf.put(httpResponse().getBytes(StandardCharsets.US_ASCII));
    return respBuf;
}
  1. 使用Charset.encode(String)方法:
private ByteBuffer respFromChar() {
    return StandardCharsets.US_ASCII.encode(httpResponse());
}

我正在尝试使用它发送一个简单的 HTTP 响应(问题末尾的完整代码)。使用 respWithPut() 我在客户端收到损坏的响应,而 respFromChar() 工作正常。

我在 respWithPut() 中做错了什么?

完整示例代码:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Arrays;
import java.util.concurrent.Future;

public class AsyncServer {
    final String HTTP_DELIM = "\r\n";

    private String httpResponse() {
        String body = "HELLO";
        String prologue = "HTTP/1.1 200 OK";
        String header = String.join(HTTP_DELIM,
                Arrays.asList(
                        "Date: " + Instant.now().toString(),
                        "Content-Type: text/plain",
                        String.format("Content-Length: %d", body.length()),
                        HTTP_DELIM
                )
        );
        return prologue + HTTP_DELIM + header +body;
    }

    private ByteBuffer respWithPut() {
        ByteBuffer respBuf = ByteBuffer.allocate(1024);
        respBuf.put(httpResponse().getBytes(StandardCharsets.US_ASCII));
        return respBuf;
    }

    private ByteBuffer respFromChar() {
        return StandardCharsets.US_ASCII.encode(httpResponse());
    }

    public void startHttpServer() throws Exception {
        AsynchronousServerSocketChannel listener
                = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
        while (true) {
            Future<AsynchronousSocketChannel> asyncCh = listener.accept();
            AsynchronousSocketChannel aSock = asyncCh.get();
            aSock.write(respWithPut());
            aSock.close();
        }
    }

    public static void main(String[] args) throws Exception {
        AsyncServer asyncServer = new AsyncServer();
        asyncServer.startHttpServer();
    }
}

要提出样品请求,请使用:curl -v "http://localhost:8080/"

一个 ByteBuffer 有一个 position indicating where the next byte should be read from. Your respWithPut method needs to call respBuf.flip() 来确保缓冲区的位置指向你刚放入其中的数据。

ByteBuffer.allocate之后:

position                              limit
↓                                     ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size

调用 ByteBuffer.put 之后,例如,一个长度为八的字节数组:

                  position            limit
                  ↓                   ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size

下一个 ByteBuffer.get 调用将读取索引 8 处的字节,该字节仍为零,因为您还没有使用 put 在此处添加任何数据。

调用ByteBuffer.flip后,limit将是旧位置,新位置将为零,使任何现有数据都可以读取:

next get operation will read from here
↓
position          limit
↓                 ↓
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| … |_|
0 1 2 3 4 5 5 6 7 8 9 1 1 1 1 1 1     ↑
                      0 1 2 3 4 5     buffer size