首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Netty 5 HTTP 協議棧深度解析與實戰指南

     編輯:Java 綜合教學

閱讀導航

  1. 前言:為何選 Netty 實現高併發 HTTP 服務
  2. HTTP 協議基礎:GET/POST 請求結構、Content-Type 詳解
  3. Netty 編解碼器體系:HttpServerCodec、HttpObjectAggregator
  4. GET/POST 解析實戰:QueryStringDecoder、HttpPostRequestDecoder
  5. 自定義 Decoder:HttpJsonDecoder、HttpProtobufDecoder
  6. 踩坑實錄:內存洩漏、HTTP 長連接處理

一、項目背景

視頻質量統計系統,需支持 10W+ TPS。選 Netty 因:異步非阻塞模型、輕量無容器開銷、資源佔用優於 Tomcat、官方宣稱 HTTP 支持完善。

GitHub 實戰項目:netty-http,已實現 GET/POST 解析、三種 Content-Type 處理、文件上傳、自定義 Decoder。

二、HTTP 協議核心

1. GET 請求

GET /test?name=XXG&age=23 HTTP/1.1
Host: localhost:8081
...Headers...

參數在 URI 中,無 Message Body。

2. POST 請求

POST /test HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

name=XXG&age=23

參數在 Body 中,需 Content-Length 指定長度。

3. 三大 POST Content-Type

  • application/x-www-form-urlencoded:鍵值對編碼,表單默認
  • multipart/form-data:文件上傳,Boundary 分割字段
  • application/json:JSON 字符串,適合結構化數據、REST API

三、Netty HTTP 編解碼器

  • HttpRequestDecoder:ByteBuf → HttpRequest/HttpContent
  • HttpResponseEncoder:HttpResponse/HttpContent → ByteBuf
  • HttpServerCodec:上述兩者組合
  • 關鍵 HttpObjectAggregator:將 HttpMessage + HttpContent 聚合為 FullHttpRequest/Response,解決分片傳輸與 POST Body 解析問題

Pipeline 配置:

ch.pipeline().addLast("codec", new HttpServerCodec())
              .addLast("aggregator", new HttpObjectAggregator(65536));

四、請求解析實戰

1. GET 參數解析

QueryStringDecoder decoder = new QueryStringDecoder(uri, CharsetUtil.UTF_8);
Map> params = decoder.parameters();

2. POST 解析(需 Aggregator)

FullHttpRequest fullReq = (FullHttpRequest) msg;
// JSON
String json = fullReq.content().toString(CharsetUtil.UTF_8);
JSONObject obj = JSON.parseObject(json);

// form-urlencoded:QueryStringDecoder 或 HttpPostRequestDecoder
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, CharsetUtil.UTF_8);
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
    if (data.getHttpDataType() == HttpDataType.Attribute) {
        Attribute attr = (Attribute) data;
        System.out.println(attr.getName() + "=" + attr.getValue());
    }
}

// multipart 文件上傳
DiskFileUpload.baseDirectory = "/data/fileupload/";
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
    if (data.getHttpDataType() == HttpDataType.FileUpload) {
        FileUpload file = (FileUpload) data;
        if (file.isCompleted()) file.renameTo(new File(baseDir + file.getFilename()));
    }
}

五、自定義 Decoder 實現

1. HttpJsonDecoder

public class HttpJsonDecoder extends MessageToMessageDecoder {
    protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List out) {
        FullHttpRequest req = (FullHttpRequest) msg;
        ByteBuf content = req.content();
        byte[] bytes = new byte[content.readableBytes()];
        content.getBytes(0, bytes);
        out.add(JSON.parseObject(new String(bytes)));
    }
}

使用:.addLast("jsonDecoder", new HttpJsonDecoder()),後續 Handler 直接接收 JSONObject。

2. HttpProtobufDecoder

public class HttpProtobufDecoder extends MessageToMessageDecoder {
    private final MessageLite prototype;
    public HttpProtobufDecoder(MessageLite prototype) { this.prototype = prototype.getDefaultInstanceForType(); }
    protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List out) {
        FullHttpRequest req = (FullHttpRequest) msg;
        ByteBuf content = req.content();
        byte[] bytes = new byte[content.readableBytes()];
        content.getBytes(0, bytes);
        out.add(prototype.getParserForType().parseFrom(bytes));
    }
}

六、生產級踩坑總結

1. 內存洩漏:ByteBuf 引用計數

Netty 4+ 使用引用計數管理 ByteBuf 生命周期,非 GC。Inbound Handler 必須在 finally 中 release():

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try { ... } finally { ReferenceCountUtil.release(msg); }
}

或繼承 SimpleChannelInboundHandler 自動釋放。啟用洩漏檢測:-Dio.netty.leakDetectionLevel=advanced。

2. HTTP 長連接陷阱

Nginx + Netty,Nginx 配置 proxy_http_version 1.1; proxy_set_header Connection ""; 開啟 Keep-Alive。Netty 響應必須:

  • 短連接:ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
  • 長連接:設置 Content-Length 頭,不關閉連接,由客戶端按長度讀取
private void writeResponse(Channel ch, HttpResponseStatus status, String msg, boolean forceClose) {
    ByteBuf buf = Unpooled.wrappedBuffer(msg.getBytes());
    FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
    boolean close = isCloseRequest(); // 判斷 Connection: close 或 HTTP/1.0 無 Keep-Alive
    if (!close && !forceClose) {
        resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
    }
    ChannelFuture f = ch.write(resp);
    if (close || forceClose) f.addListener(ChannelFutureListener.CLOSE);
}

3. TCP KeepAlive vs HTTP KeepAlive

  • TCP KeepAlive:內核層探測連接存活,默認 2 小時
  • HTTP KeepAlive:應用層復用 TCP 連接傳輸多請求,減少握手開銷
  • Netty childOption(ChannelOption.SO_KEEPALIVE, true) 僅開啟 TCP 層,不等於 HTTP 長連接

4. 長連接下判斷數據接收完畢

  • Content-Length:固定長度,按字節數讀取
  • Transfer-Encoding: chunked:分塊傳輸,以 0 長度塊結束

七、參考資源

  • Netty 官方 HTTP Example
  • Netty 文檔:引用計數對象、內存洩漏檢測
  • HTTP Keep-Alive 規範詳解
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved