几十行代码基于Netty搭建一个 HTTP Server( 四 )

简单解析一下服务端的创建过程具体是怎样的!
1.创建了两个 NioEventLoopGroup 对象实例:bossGroupworkerGroup

  • bossGroup : 用于处理客户端的 TCP 连接请求 。
  • workerGroup : 负责每一条连接的具体读写数据的处理逻辑 , 真正负责 I/O 读写操作 , 交由对应的 Handler 处理 。
举个例子:我们把公司的老板当做 bossGroup , 员工当做 workerGroup , bossGroup 在外面接完活之后 , 扔给 workerGroup 去处理 。 一般情况下我们会指定 bossGroup 的 线程数为 1(并发连接量不大的时候), workGroup 的线程数量为 CPU 核心数 *2。 另外 , 根据源码来看 , 使用 NioEventLoopGroup 类的无参构造函数设置线程数量的默认值就是 CPU 核心数 *2。
2.创建一个服务端启动引导/辅助类: ServerBootstrap , 这个类将引导我们进行服务端的启动工作 。
3.通过 .group() 方法给引导类 ServerBootstrap 配置两大线程组 , 确定了线程模型 。
4.通过channel()方法给引导类 ServerBootstrap指定了 IO 模型为NIO
  • NioServerSocketChannel :指定服务端的 IO 模型为 NIO , 与 BIO 编程模型中的ServerSocket对应
  • NioSocketChannel : 指定客户端的 IO 模型为 NIO ,与 BIO 编程模型中的Socket对应
5.通过 .childHandler()给引导类创建一个ChannelInitializer, 然后指定了服务端消息的业务处理逻辑也就是自定义的ChannelHandler 对象
6.调用 ServerBootstrap 类的 bind()方法绑定端口。
//bind()是异步的 , 但是 , 你可以通过 sync()方法将其变为同步 。 ChannelFuture f = b.bind(port).sync();自定义服务端 ChannelHandler 处理 HTTP 请求我们继承SimpleChannelInboundHandler ,并重写下面 3 个方法:
  1. channelRead() :服务端接收并处理客户端发送的 HTTP 请求调用的方法 。
  2. exceptionCaught() :处理客户端发送的 HTTP 请求发生异常的时候被调用 。
  3. channelReadComplete() : 服务端消费完客户端发送的 HTTP 请求之后调用的方法 。
另外 , 客户端 HTTP 请求参数类型为 FullHttpRequest 。 我们可以把 FullHttpRequest对象看作是 HTTP 请求报文的 Java 对象的表现形式 。
@Slf4jpublic class HttpServerHandler extends SimpleChannelInboundHandler {private static final String FAVICON_ICO = "/favicon.ico";private static final AsciiString CONNECTION = AsciiString.cached("Connection");private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {log.info("Handle http request:{}", fullHttpRequest);String uri = fullHttpRequest.uri();if (uri.equals(FAVICON_ICO)) {return;}RequestHandler requestHandler = RequestHandlerFactory.create(fullHttpRequest.method());Object result;FullHttpResponse response;try {result = requestHandler.handle(fullHttpRequest);String responseHtml = "" + result + "";byte[] responseBytes = responseHtml.getBytes(StandardCharsets.UTF_8);response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(responseBytes));response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());} catch (IllegalArgumentException e) {e.printStackTrace();String responseHtml = "" + e.toString() + "";byte[] responseBytes = responseHtml.getBytes(StandardCharsets.UTF_8);response = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, Unpooled.wrappedBuffer(responseBytes));response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");}boolean keepAlive = HttpUtil.isKeepAlive(fullHttpRequest);if (!keepAlive) {ctx.write(response).addListener(ChannelFutureListener.CLOSE);} else {response.headers().set(CONNECTION, KEEP_ALIVE);ctx.write(response);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}}


推荐阅读