從零手寫實現 nginx-03-nginx 基於 Netty 實現

老马啸西风發表於2024-06-04

前言

大家好,我是老馬。很高興遇到你。

我們希望實現最簡單的 http 服務資訊,可以處理靜態檔案。

如果你想知道 servlet 如何處理的,可以參考我的另一個專案:

手寫從零實現簡易版 tomcat minicat

netty 相關

如果你對 netty 不是很熟悉,可以讀一下

Netty 權威指南-01-BIO 案例

Netty 權威指南-02-NIO 案例

Netty 權威指南-03-AIO 案例

Netty 權威指南-04-為什麼選擇 Netty?Netty 入門教程

手寫 nginx 系列

如果你對 nginx 原理感興趣,可以閱讀:

從零手寫實現 nginx-01-為什麼不能有 java 版本的 nginx?

從零手寫實現 nginx-02-nginx 的核心能力

從零手寫實現 nginx-03-nginx 基於 Netty 實現

從零手寫實現 nginx-04-基於 netty http 出入參最佳化處理

從零手寫實現 nginx-05-MIME型別(Multipurpose Internet Mail Extensions,多用途網際網路郵件擴充套件型別)

從零手寫實現 nginx-06-資料夾自動索引

從零手寫實現 nginx-07-大檔案下載

從零手寫實現 nginx-08-範圍查詢

從零手寫實現 nginx-09-檔案壓縮

從零手寫實現 nginx-10-sendfile 零複製

從零手寫實現 nginx-11-file+range 合併

從零手寫實現 nginx-12-keep-alive 連線複用

從零手寫實現 nginx-13-nginx.conf 配置檔案介紹

從零手寫實現 nginx-14-nginx.conf 和 hocon 格式有關係嗎?

從零手寫實現 nginx-15-nginx.conf 如何透過 java 解析處理?

從零手寫實現 nginx-16-nginx 支援配置多個 server

基本實現

思路

上一節我們實現了基於 serverSocket 的處理監聽,那個效能畢竟一般。

這一次我們一起透過 netty 對程式碼進行改造升級。

核心實現

啟動類

INginxServer 介面不變,我們加一個 netty 的實現類。

這裡針對 EventLoopGroup 的配置我們暫時使用預設值,後續可以考慮可以讓使用者自定義。

/**
 * netty 實現
 * 
 * @author 老馬嘯西風
 * @since 0.2.0
 */
public class NginxServerNetty implements INginxServer {

    private static final Log log = LogFactory.getLog(NginxServerNetty.class);


    private NginxConfig nginxConfig;

    @Override
    public void init(NginxConfig nginxConfig) {
        this.nginxConfig = nginxConfig;
    }

    @Override
    public void start() {
        // 伺服器監聽的埠號
        String host = InnerNetUtil.getHost();
        int port = nginxConfig.getHttpServerListen();

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //worker 執行緒池的數量預設為 CPU 核心數的兩倍
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NginxNettyServerHandler(nginxConfig));
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // Bind and start to accept incoming connections.
            ChannelFuture future = serverBootstrap.bind(port).sync();

            log.info("[Nginx4j] listen on http://{}:{}", host, port);

            // Wait until the server socket is closed.
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("[Nginx4j] start meet ex", e);
            throw new Nginx4jException(e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();

            log.info("[Nginx4j] shutdownGracefully", host, port);
        }
    }

}

處理類

核心的處理邏輯都在 NginxNettyServerHandler 這個類。

這裡主要做 3 件事

  1. 解析請求類

  2. 根據請求獲取對應的內容

  3. 返回響應內容

/**
 * netty 處理類
 * @author 老馬嘯西風
 * @since 0.2.0
 */
public class NginxNettyServerHandler extends ChannelInboundHandlerAdapter {

    private static final Log logger = LogFactory.getLog(NginxNettyServerHandler.class);

    private final NginxConfig nginxConfig;

    public NginxNettyServerHandler(NginxConfig nginxConfig) {
        this.nginxConfig = nginxConfig;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String requestString = new String(bytes, nginxConfig.getCharset());
        logger.info("[Nginx] channelRead requestString={}", requestString);

        // 請求體
        final NginxRequestConvertor requestConvertor = nginxConfig.getNginxRequestConvertor();
        NginxRequestInfoBo nginxRequestInfoBo = requestConvertor.convert(requestString, nginxConfig);

        // 分發
        final NginxRequestDispatch requestDispatch = nginxConfig.getNginxRequestDispatch();
        String respText = requestDispatch.dispatch(nginxRequestInfoBo, nginxConfig);

        // 結果響應
        ByteBuf responseBuf = Unpooled.copiedBuffer(respText.getBytes());
        ctx.writeAndFlush(responseBuf)
                .addListener(ChannelFutureListener.CLOSE); // Close the channel after sending the response
        logger.info("[Nginx] channelRead writeAndFlush DONE");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("[Nginx] exceptionCaught", cause);
        ctx.close();
    }

}

其中請求的解析為物件,便於後續開發中使用。

分發處理只是加了一層抽象,整體實現和上一節類似。

感興趣可以閱讀原始碼

小結

本節我們使用 netty 大幅度提升一下響應效能。

到這裡我們實現了一個簡單的 http 伺服器,當然這是遠遠不夠的。

後續我們會繼續一起實現更多的 nginx 特性。

我是老馬,期待與你的下次重逢。

開源地址

為了便於大家學習,已經將 nginx 開源

https://github.com/houbb/nginx4j

相關文章