Netty学习系列|Netty学习三(Netty框架之引导器)

一、基于Netty搭建简单的Http服务器
基于Netty搭建简单得Http服务器,只需要创建服务启动类和业务逻辑处理类即可
服务启动类

public class HttpServer { public void start(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ch.pipeline() .addLast("codec", new HttpServerCodec())// HTTP 编解码 .addLast("compressor", new HttpContentCompressor())// HttpContent 压缩 .addLast("aggregator", new HttpObjectAggregator(65536)) // HTTP 消息聚合 .addLast("handler", new HttpServerHandler()); // 自定义业务逻辑处理器 } }) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind().sync(); System.out.println("Http Server started, Listening on " + port); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } }public static void main(String[] args) throws Exception { new HttpServer().start(8088); } }

业务逻辑处理类
public class HttpServerHandler extends SimpleChannelInboundHandler< FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) { String content = String.format("Receive http request, uri: %s, method: %s, content: %s%n", msg.uri(), msg.method(), msg.content().toString(CharsetUtil.UTF_8)); FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes())); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }

测试:
  • 启动 HttpServer 的 main 函数。
  • 终端或浏览器发起 HTTP 请求。
$ curl http://localhost:8088/abc $ Receive http request, uri: /abc, method: GET, content:

二、引导器实践指南
Netty服务端的启动过程大致分为:
  • 配置线程池
  • Channel初始化
  • 端口绑定
1. 配置线程池 通过参数设置,可以支持三种Reactor模式:单线程模型、多线程模型、主从多线程模型
  • 单线程模型:所有I/O操作由一个线程完成,只启动一个EventLoopGroup线程池,线程池类只有一个EventLoop线程
EventLoopGroup group = new NioEventLoopGroup(1); ServerBootstrap b = new ServerBootstrap(); b.group(group)

  • 多线程模型:与单线程类似,但EventLoopGroup不需要参数,默认启动2倍CPU核数的EventLoop线程,也至此自定义线程数
EventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(group)

  • 主从多线程模型:Boss 是主 Reactor,Worker 是从 Reactor。它们分别使用不同的 NioEventLoopGroup,主 Reactor 负责处理 Accept,然后把 Channel 注册到从 Reactor 上,从 Reactor 主要负责 Channel 生命周期内的所有 I/O 事件。
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup)

2. Channel初始化 NIO 模型是 Netty 中最成熟且被广泛使用的模型。因此,推荐 Netty 服务端采用NioServerSocketChannel 作为 Channel 的类型,客户端采用 NioSocketChannel。
通过childHandler()方法注册ChannelHandler。ChannelInitializer是实现了 ChannelHandler接口的匿名类,通过实例化 ChannelInitializer 作为 ServerBootstrap 的参数。
ChannelInitializer的initChannel()绑定提个PipeLine,用于服务编排。向PipeLine中按顺序添加多个ChannelHandler。
通过option()和childOption()方法设置Channel属性,option 主要负责设置 Boss 线程组,而 childOption 对应的是 Worker 线程组。Netty 提供了默认参数设置ChannelOption,具体参数参考:
https://www.cnblogs.com/googlemeoften/p/6082785.html
3. 端口绑定 【Netty学习系列|Netty学习三(Netty框架之引导器)】完成Netty配置后,bind() 方法会真正触发启动,sync() 方法则会阻塞,直至整个启动过程完成。
三、Netty实现HTTP客户端
public class HttpClient { public void connect(String host, int port) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group); b.channel(NioSocketChannel.class); b.option(ChannelOption.SO_KEEPALIVE, true); b.handler(new ChannelInitializer< SocketChannel> () { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new HttpResponseDecoder()); ch.pipeline().addLast(new HttpRequestEncoder()); ch.pipeline().addLast(new HttpClientHandler()); } }); ChannelFuture f = b.connect(host, port).sync(); URI uri = new URI("http://127.0.0.1:8088"); String content = "hello world"; DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString(), Unpooled.wrappedBuffer(content.getBytes(StandardCharsets.UTF_8))); request.headers().set(HttpHeaderNames.HOST, host); request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes()); f.channel().write(request); f.channel().flush(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } }public static void main(String[] args) throws Exception { HttpClient client = new HttpClient(); client.connect("127.0.0.1", 8088); } }

public class HttpClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; ByteBuf buf = content.content(); System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8)); buf.release(); } } }

    推荐阅读