基于Netty的简单HTTP服务例子

该博客介绍了如何基于Netty 4.1.25.Final和Spring Boot 2搭建一个简单的HTTP服务,并提供了相关代码示例如AppInitializer.java、AppHandler.java和HttpDemoApplication.java。强调了在编写HTTP业务时避免阻塞EventLoop线程以保持Netty的高性能,建议使用额外的线程池处理阻塞操作,并分享了性能测试结果,表明手动处理异步任务的性能优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

例子是基于 Netty 4.1.25.Final + Spring Boot 2 + JDK 1.8 + Maven

demo:

https://github.com/emacsist/netty-http-demo

AppInitializer.java


 
  1. package io.github.emacsist.netty.httpdemo.config;

  2. import io.github.emacsist.netty.httpdemo.handler.AppHandler;

  3. import io.netty.channel.Channel;

  4. import io.netty.channel.ChannelHandler;

  5. import io.netty.channel.ChannelInitializer;

  6. import io.netty.channel.ChannelPipeline;

  7. import io.netty.handler.codec.http.HttpObjectAggregator;

  8. import io.netty.handler.codec.http.HttpServerCodec;

  9. import org.springframework.beans.factory.annotation.Autowired;

  10. import org.springframework.stereotype.Component;

  11. /**

  12. * @author emacsist

  13. */

  14. @Component

  15. @ChannelHandler.Sharable

  16. public class AppInitializer extends ChannelInitializer {

  17.    private static final int MB = 1024 * 1024;

  18.    @Autowired

  19.    private AppHandler appHandler;

  20.    @Override

  21.    protected void initChannel(final Channel channel) {

  22.        final ChannelPipeline p = channel.pipeline();

  23.        p.addLast(new HttpServerCodec());

  24.        p.addLast(new HttpObjectAggregator(1 * MB));

  25.        p.addLast(appHandler);

  26.    }

  27. }

AppHandler.java


 
  1. package io.github.emacsist.netty.httpdemo.handler;

  2. import io.netty.buffer.Unpooled;

  3. import io.netty.channel.ChannelHandler;

  4. import io.netty.channel.ChannelHandlerContext;

  5. import io.netty.channel.SimpleChannelInboundHandler;

  6. import io.netty.handler.codec.http.*;

  7. import io.netty.util.AsciiString;

  8. import org.slf4j.Logger;

  9. import org.slf4j.LoggerFactory;

  10. import org.springframework.stereotype.Component;

  11. /**

  12. * @author emacsist

  13. */

  14. @Component

  15. @ChannelHandler.Sharable

  16. public class AppHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

  17.    private static final Logger log = LoggerFactory.getLogger(AppHandler.class);

  18.    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");

  19.    private static final AsciiString TEXT_PLAIN = AsciiString.cached("text/plain; charset=utf-8");

  20.    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");

  21.    @Override

  22.    protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest fullHttpRequest) {

  23.        final String uri = fullHttpRequest.uri();

  24.        final QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);

  25.        final String requestPath = queryStringDecoder.path();

  26.        String body = "";

  27.        switch (requestPath) {

  28.            case "/hello":

  29.                log.info("in hello => {}", queryStringDecoder.parameters());

  30.                //请自行检测参数, 这里假设  /hello 是会带上 ?name=world 类似这参数值的

  31.                body = "Hello " + queryStringDecoder.parameters().get("name").get(0);

  32.                break;

  33.            case "/netty":

  34.                log.info("in netty => {}", queryStringDecoder.parameters());

  35.                body = "Hello Netty.";

  36.                break;

  37.            default:

  38.                break;

  39.        }

  40.        final DefaultFullHttpResponse defaultFullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(body.getBytes()));

  41.        defaultFullHttpResponse.headers().set(CONTENT_TYPE, TEXT_PLAIN);

  42.        defaultFullHttpResponse.headers().set(CONTENT_LENGTH, defaultFullHttpResponse.content().readableBytes());

  43.        ctx.write(defaultFullHttpResponse);

  44.    }

  45.    @Override

  46.    public void channelReadComplete(final ChannelHandlerContext ctx) {

  47.        ctx.flush();

  48.    }

  49.    @Override

  50.    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {

  51.        log.error(cause.getMessage(), cause);

  52.        ctx.close();

  53.    }

  54. }

HttpDemoApplication.java


 
  1. package io.github.emacsist.netty.httpdemo;

  2. import io.github.emacsist.netty.httpdemo.config.AppInitializer;

  3. import io.netty.bootstrap.ServerBootstrap;

  4. import io.netty.channel.Channel;

  5. import io.netty.channel.ChannelOption;

  6. import io.netty.channel.EventLoopGroup;

  7. import io.netty.channel.nio.NioEventLoopGroup;

  8. import io.netty.channel.socket.nio.NioServerSocketChannel;

  9. import io.netty.handler.logging.LogLevel;

  10. import io.netty.handler.logging.LoggingHandler;

  11. import org.springframework.beans.factory.annotation.Autowired;

  12. import org.springframework.beans.factory.annotation.Value;

  13. import org.springframework.boot.CommandLineRunner;

  14. import org.springframework.boot.SpringApplication;

  15. import org.springframework.boot.autoconfigure.SpringBootApplication;

  16. /**

  17. * @author emacsist

  18. */

  19. @SpringBootApplication

  20. public class HttpDemoApplication implements CommandLineRunner {

  21.    @Value("${server.port}")

  22.    private int port;

  23.    private static final int KB = 1024;

  24.    @Autowired

  25.    private AppInitializer appInitializer;

  26.    public static void main(final String[] args) {

  27.        SpringApplication.run(HttpDemoApplication.class, args);

  28.    }

  29.    @Override

  30.    public void run(final String... args) {

  31.        final ServerBootstrap serverBootstrap = new ServerBootstrap();

  32.        final EventLoopGroup master = new NioEventLoopGroup();

  33.        final EventLoopGroup worker = new NioEventLoopGroup();

  34.        try {

  35.            serverBootstrap

  36.                    .option(ChannelOption.SO_BACKLOG, 4 * KB)

  37.                    .option(ChannelOption.TCP_NODELAY, true)

  38.                    .group(master, worker)

  39.                    .channel(NioServerSocketChannel.class)

  40.                    .handler(new LoggingHandler(LogLevel.DEBUG))

  41.                    .childHandler(appInitializer);

  42.            final Channel ch = serverBootstrap.bind(port).sync().channel();

  43.            System.out.println("start app ok...");

  44.            ch.closeFuture().sync();

  45.        } catch (final InterruptedException e) {

  46.            //ignore

  47.        } finally {

  48.            master.shutdownGracefully();

  49.            worker.shutdownGracefully();

  50.            System.out.println("stop app ok...");

  51.        }

  52.    }

  53. }

注意

编写自己的HTTP业务时, 请记住千万不要阻塞EventLoop线程~, 不然会导致 Netty 的性能急剧下降.

当要处理一些阻塞操作时, 请用其他的线程池来处理。

在Netty中一般有两种方式:

ChannelPipeline 指定 executor:

例如这样子:


 
  1. /**

  2. * @author emacsist

  3. */

  4. @Component

  5. @ChannelHandler.Sharable

  6. public class AppInitializer extends ChannelInitializer {

  7.    private static final int MB = 1024 * 1024;

  8.    @Autowired

  9.    private AppHandler appHandler;

  10.    private static final DefaultEventExecutorGroup executorGroup = new DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors() * 2);

  11.    @Override

  12.    protected void initChannel(final Channel channel) {

  13.        final ChannelPipeline p = channel.pipeline();

  14.        p.addLast(new HttpServerCodec());

  15.        p.addLast(new HttpObjectAggregator(1 * MB));

  16.        p.addLast(executorGroup, appHandler);

  17.    }

  18. }

创建一个全局的 ExecutorGroup , 自己手工创建任务


 
  1. executorGroup.submit(() -> maxService.dealClick(queryStringDecoder));

这个是公司项目代码的一个片段, 要将HTTP参数 接收-> 解密 -> 进队和处理 Redis, 这些都是阻塞操作来的, 所以这里创建了一个异步任务来处理这些耗时的操作, 以免阻塞 EventLoop 线程.

性能

经过测试, 还是手工这种按需处理异步任务的性能更高点. 差不多高1倍. 当然, 具体问题要具体分析, 总之就是不要阻塞 EventLoop 线程就好, 毕竟Netty高性能的核心, 就在于不要阻塞 EventLoop.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值