mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 05:06:42 +08:00
503 lines
31 KiB
HTML
503 lines
31 KiB
HTML
<!DOCTYPE html>
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
<link rel="icon" href="/static/favicon.png">
|
||
<title>03 引导器作用:客户端和服务端启动都要做些什么?.md.html</title>
|
||
<!-- Spectre.css framework -->
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
<!-- theme css & js -->
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
<div class="book-sidebar">
|
||
<div class="book-brand">
|
||
<a href="/">
|
||
<img src="/static/favicon.png">
|
||
<span>技术文章摘抄</span>
|
||
</a>
|
||
</div>
|
||
<div class="book-menu uncollapsible">
|
||
<ul class="uncollapsible">
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li><a href="../">上一级</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/00 学好 Netty,是你修炼 Java 内功的必经之路.md.html">00 学好 Netty,是你修炼 Java 内功的必经之路</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/01 初识 Netty:为什么 Netty 这么流行?.md.html">01 初识 Netty:为什么 Netty 这么流行?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/02 纵览全局:把握 Netty 整体架构脉络.md.html">02 纵览全局:把握 Netty 整体架构脉络</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/Netty 核心原理剖析与 RPC 实践-完/03 引导器作用:客户端和服务端启动都要做些什么?.md.html">03 引导器作用:客户端和服务端启动都要做些什么?</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/04 事件调度层:为什么 EventLoop 是 Netty 的精髓?.md.html">04 事件调度层:为什么 EventLoop 是 Netty 的精髓?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/05 服务编排层:Pipeline 如何协调各类 Handler ?.md.html">05 服务编排层:Pipeline 如何协调各类 Handler ?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/06 粘包拆包问题:如何获取一个完整的网络包?.md.html">06 粘包拆包问题:如何获取一个完整的网络包?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/07 接头暗语:如何利用 Netty 实现自定义协议通信?.md.html">07 接头暗语:如何利用 Netty 实现自定义协议通信?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/08 开箱即用:Netty 支持哪些常用的解码器?.md.html">08 开箱即用:Netty 支持哪些常用的解码器?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/09 数据传输:writeAndFlush 处理流程剖析.md.html">09 数据传输:writeAndFlush 处理流程剖析</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/10 双刃剑:合理管理 Netty 堆外内存.md.html">10 双刃剑:合理管理 Netty 堆外内存</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/11 另起炉灶:Netty 数据传输载体 ByteBuf 详解.md.html">11 另起炉灶:Netty 数据传输载体 ByteBuf 详解</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/12 他山之石:高性能内存分配器 jemalloc 基本原理.md.html">12 他山之石:高性能内存分配器 jemalloc 基本原理</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/13 举一反三:Netty 高性能内存管理设计(上).md.html">13 举一反三:Netty 高性能内存管理设计(上)</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/14 举一反三:Netty 高性能内存管理设计(下).md.html">14 举一反三:Netty 高性能内存管理设计(下)</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/15 轻量级对象回收站:Recycler 对象池技术解析.md.html">15 轻量级对象回收站:Recycler 对象池技术解析</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/16 IO 加速:与众不同的 Netty 零拷贝技术.md.html">16 IO 加速:与众不同的 Netty 零拷贝技术</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/17 源码篇:从 Linux 出发深入剖析服务端启动流程.md.html">17 源码篇:从 Linux 出发深入剖析服务端启动流程</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/18 源码篇:解密 Netty Reactor 线程模型.md.html">18 源码篇:解密 Netty Reactor 线程模型</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/19 源码篇:一个网络请求在 Netty 中的旅程.md.html">19 源码篇:一个网络请求在 Netty 中的旅程</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/20 技巧篇:Netty 的 FastThreadLocal 究竟比 ThreadLocal 快在哪儿?.md.html">20 技巧篇:Netty 的 FastThreadLocal 究竟比 ThreadLocal 快在哪儿?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/21 技巧篇:延迟任务处理神器之时间轮 HashedWheelTimer.md.html">21 技巧篇:延迟任务处理神器之时间轮 HashedWheelTimer</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/22 技巧篇:高性能无锁队列 Mpsc Queue.md.html">22 技巧篇:高性能无锁队列 Mpsc Queue</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/23 架构设计:如何实现一个高性能分布式 RPC 框架.md.html">23 架构设计:如何实现一个高性能分布式 RPC 框架</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/24 服务发布与订阅:搭建生产者和消费者的基础框架.md.html">24 服务发布与订阅:搭建生产者和消费者的基础框架</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/25 远程通信:通信协议设计以及编解码的实现.md.html">25 远程通信:通信协议设计以及编解码的实现</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/26 服务治理:服务发现与负载均衡机制的实现.md.html">26 服务治理:服务发现与负载均衡机制的实现</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/27 动态代理:为用户屏蔽 RPC 调用的底层细节.md.html">27 动态代理:为用户屏蔽 RPC 调用的底层细节</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/28 实战总结:RPC 实战总结与进阶延伸.md.html">28 实战总结:RPC 实战总结与进阶延伸</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/29 编程思想:Netty 中应用了哪些设计模式?.md.html">29 编程思想:Netty 中应用了哪些设计模式?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/30 实践总结:Netty 在项目开发中的一些最佳实践.md.html">30 实践总结:Netty 在项目开发中的一些最佳实践</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/31 结束语 技术成长之路:如何打造自己的技术体系.md.html">31 结束语 技术成长之路:如何打造自己的技术体系</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
<div class="sidebar-toggle-inner"></div>
|
||
</div>
|
||
<script>
|
||
function add_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.add('show')
|
||
}
|
||
function remove_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.remove('show')
|
||
}
|
||
function sidebar_toggle() {
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let content = document.querySelector('.off-canvas-content')
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
sidebar_toggle.classList.remove('extend')
|
||
sidebar.classList.remove('hide')
|
||
content.classList.remove('extend')
|
||
} else { // hide
|
||
sidebar_toggle.classList.add('extend')
|
||
sidebar.classList.add('hide')
|
||
content.classList.add('extend')
|
||
}
|
||
}
|
||
function open_sidebar() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.add('show')
|
||
overlay.classList.add('show')
|
||
}
|
||
function hide_canvas() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.remove('show')
|
||
overlay.classList.remove('show')
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
<div class="columns">
|
||
<div class="column col-12 col-lg-12">
|
||
<div class="book-navbar">
|
||
<!-- For Responsive Layout -->
|
||
<header class="navbar">
|
||
<section class="navbar-section">
|
||
<a onclick="open_sidebar()">
|
||
<i class="icon icon-menu"></i>
|
||
</a>
|
||
</section>
|
||
</header>
|
||
</div>
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;">
|
||
<div class="book-post">
|
||
<p id="tip" align="center"></p>
|
||
<div><h1>03 引导器作用:客户端和服务端启动都要做些什么?</h1>
|
||
<p>你好,我是若地。上节课我们介绍了 Netty 中核心组件的作用以及组件协作的方式方法。从这节课开始,我们将对 Netty 的每个核心组件依次进行深入剖析解读。我会结合相应的代码示例讲解,帮助你快速上手 Netty。</p>
|
||
<p>我们在使用 Netty 编写网络应用程序的时候,一定会从<strong>引导器 Bootstrap</strong>开始入手。Bootstrap 作为整个 Netty 客户端和服务端的<strong>程序入口</strong>,可以把 Netty 的核心组件像搭积木一样组装在一起。本节课我会从 Netty 的引导器<strong>Bootstrap</strong>出发,带你学习如何使用 Netty 进行最基本的程序开发。</p>
|
||
<h3>从一个简单的 HTTP 服务器开始</h3>
|
||
<p>HTTP 服务器是我们平时最常用的工具之一。同传统 Web 容器 Tomcat、Jetty 一样,Netty 也可以方便地开发一个 HTTP 服务器。我从一个简单的 HTTP 服务器开始,通过程序示例为你展现 Netty 程序如何配置启动,以及引导器如何与核心组件产生联系。</p>
|
||
<p>完整地实现一个高性能、功能完备、健壮性强的 HTTP 服务器非常复杂,本文仅为了方便理解 Netty 网络应用开发的基本过程,所以只实现最基本的<strong>请求-响应</strong>的流程:</p>
|
||
<ol>
|
||
<li>搭建 HTTP 服务器,配置相关参数并启动。</li>
|
||
<li>从浏览器或者终端发起 HTTP 请求。</li>
|
||
<li>成功得到服务端的响应结果。</li>
|
||
</ol>
|
||
<p>Netty 的模块化设计非常优雅,客户端或者服务端的启动方式基本是固定的。作为开发者来说,只要照葫芦画瓢即可轻松上手。大多数场景下,你只需要实现与业务逻辑相关的一系列 ChannelHandler,再加上 Netty 已经预置了 HTTP 相关的编解码器就可以快速完成服务端框架的搭建。所以,我们只需要两个类就可以完成一个最简单的 HTTP 服务器,它们分别为<strong>服务器启动类</strong>和<strong>业务逻辑处理类</strong>,结合完整的代码实现我将对它们分别进行讲解。</p>
|
||
<h4>服务端启动类</h4>
|
||
<p>所有 Netty 服务端的启动类都可以采用如下代码结构进行开发。简单梳理一下流程:首先创建引导器;然后配置线程模型,通过引导器绑定业务逻辑处理器,并配置一些网络参数;最后绑定端口,就可以完成服务器的启动了。</p>
|
||
<pre><code>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<SocketChannel>() {
|
||
@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);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4>服务端业务逻辑处理类</h4>
|
||
<p>如下代码所示,HttpServerHandler 是业务自定义的逻辑处理类。它是入站 ChannelInboundHandler 类型的处理器,负责接收解码后的 HTTP 请求数据,并将请求处理结果写回客户端。</p>
|
||
<pre><code>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);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>通过上面两个类,我们可以完成 HTTP 服务器最基本的请求-响应流程,测试步骤如下:</p>
|
||
<ol>
|
||
<li>启动 HttpServer 的 main 函数。</li>
|
||
<li>终端或浏览器发起 HTTP 请求。</li>
|
||
</ol>
|
||
<p>测试结果输出如下:</p>
|
||
<pre><code>$ curl http://localhost:8088/abc
|
||
$ Receive http request, uri: /abc, method: GET, content:
|
||
</code></pre>
|
||
<p>当然,你也可以使用 Netty 自行实现 HTTP Client,客户端和服务端的启动类代码十分相似,我在附录部分提供了一份 HTTPClient 的实现代码仅供大家参考。</p>
|
||
<p>通过上述一个简单的 HTTP 服务示例,我们基本熟悉了 Netty 的编程模式。下面我将结合这个例子对 Netty 的引导器展开详细的介绍。</p>
|
||
<h3>引导器实践指南</h3>
|
||
<p>Netty 服务端的启动过程大致分为三个步骤:</p>
|
||
<ol>
|
||
<li><strong>配置线程池;</strong></li>
|
||
<li><strong>Channel 初始化;</strong></li>
|
||
<li><strong>端口绑定。</strong></li>
|
||
</ol>
|
||
<p>下面,我将逐一为大家介绍每一步具体需要做哪些工作。</p>
|
||
<h4>配置线程池</h4>
|
||
<p>Netty 是采用 Reactor 模型进行开发的,可以非常容易切换三种 Reactor 模式:<strong>单线程模式</strong>、<strong>多线程模式</strong>、<strong>主从多线程模式</strong>。</p>
|
||
<h4><strong>单线程模式</strong></h4>
|
||
<p>Reactor 单线程模型所有 I/O 操作都由一个线程完成,所以只需要启动一个 EventLoopGroup 即可。</p>
|
||
<pre><code>EventLoopGroup group = new NioEventLoopGroup(1);
|
||
ServerBootstrap b = new ServerBootstrap();
|
||
b.group(group)
|
||
</code></pre>
|
||
<h4><strong>多线程模式</strong></h4>
|
||
<p>Reactor 单线程模型有非常严重的性能瓶颈,因此 Reactor 多线程模型出现了。在 Netty 中使用 Reactor 多线程模型与单线程模型非常相似,区别是 NioEventLoopGroup 可以不需要任何参数,它默认会启动 2 倍 CPU 核数的线程。当然,你也可以自己手动设置固定的线程数。</p>
|
||
<pre><code>EventLoopGroup group = new NioEventLoopGroup();
|
||
ServerBootstrap b = new ServerBootstrap();
|
||
b.group(group)
|
||
</code></pre>
|
||
<h4><strong>主从多线程模式</strong></h4>
|
||
<p>在大多数场景下,我们采用的都是<strong>主从多线程 Reactor 模型</strong>。Boss 是主 Reactor,Worker 是从 Reactor。它们分别使用不同的 NioEventLoopGroup,主 Reactor 负责处理 Accept,然后把 Channel 注册到从 Reactor 上,从 Reactor 主要负责 Channel 生命周期内的所有 I/O 事件。</p>
|
||
<pre><code>EventLoopGroup bossGroup = new NioEventLoopGroup();
|
||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||
ServerBootstrap b = new ServerBootstrap();
|
||
b.group(bossGroup, workerGroup)
|
||
</code></pre>
|
||
<p>从上述三种 Reactor 线程模型的配置方法可以看出:Netty 线程模型的可定制化程度很高。它只需要简单配置不同的参数,便可启用不同的 Reactor 线程模型,而且无需变更其他的代码,很大程度上降低了用户开发和调试的成本。</p>
|
||
<h3>Channel 初始化</h3>
|
||
<h4>设置 Channel 类型</h4>
|
||
<p>NIO 模型是 Netty 中最成熟且被广泛使用的模型。因此,推荐 Netty 服务端采用 NioServerSocketChannel 作为 Channel 的类型,客户端采用 NioSocketChannel。设置方式如下:</p>
|
||
<pre><code> b.channel(NioServerSocketChannel.class);
|
||
</code></pre>
|
||
<p>当然,Netty 提供了多种类型的 Channel 实现类,你可以按需切换,例如 OioServerSocketChannel、EpollServerSocketChannel 等。</p>
|
||
<h4>注册 ChannelHandler</h4>
|
||
<p>在 Netty 中可以通过 ChannelPipeline 去注册多个 ChannelHandler,每个 ChannelHandler 各司其职,这样就可以实现最大化的代码复用,充分体现了 Netty 设计的优雅之处。那么如何通过引导器添加多个 ChannelHandler 呢?其实很简单,我们看下 HTTP 服务器代码示例:</p>
|
||
<pre><code>b.childHandler(new ChannelInitializer<SocketChannel>() {
|
||
@Override
|
||
public void initChannel(SocketChannel ch) {
|
||
ch.pipeline()
|
||
.addLast("codec", new HttpServerCodec())
|
||
.addLast("compressor", new HttpContentCompressor())
|
||
.addLast("aggregator", new HttpObjectAggregator(65536))
|
||
.addLast("handler", new HttpServerHandler());
|
||
}
|
||
})
|
||
</code></pre>
|
||
<p>ServerBootstrap 的 childHandler() 方法需要注册一个 ChannelHandler。<strong>ChannelInitializer</strong>是实现了 ChannelHandler<strong>接口的匿名类</strong>,通过实例化 ChannelInitializer 作为 ServerBootstrap 的参数。</p>
|
||
<p>Channel 初始化时都会绑定一个 Pipeline,它主要用于服务编排。Pipeline 管理了多个 ChannelHandler。I/O 事件依次在 ChannelHandler 中传播,ChannelHandler 负责业务逻辑处理。上述 HTTP 服务器示例中使用链式的方式加载了多个 ChannelHandler,包含<strong>HTTP 编解码处理器、HTTPContent 压缩处理器、HTTP 消息聚合处理器、自定义业务逻辑处理器</strong>。</p>
|
||
<p>在以前的章节中,我们介绍了 ChannelPipeline 中<strong>入站 ChannelInboundHandler</strong>和<strong>出站 ChannelOutboundHandler</strong>的概念,在这里结合 HTTP 请求-响应的场景,分析下数据在 ChannelPipeline 中的流向。当服务端收到 HTTP 请求后,会依次经过 HTTP 编解码处理器、HTTPContent 压缩处理器、HTTP 消息聚合处理器、自定义业务逻辑处理器分别处理后,再将最终结果通过 HTTPContent 压缩处理器、HTTP 编解码处理器写回客户端。</p>
|
||
<h4>设置 Channel 参数</h4>
|
||
<p>Netty 提供了十分便捷的方法,用于设置 Channel 参数。关于 Channel 的参数数量非常多,如果每个参数都需要自己设置,那会非常繁琐。幸运的是 Netty 提供了默认参数设置,实际场景下默认参数已经满足我们的需求,我们仅需要修改自己关系的参数即可。</p>
|
||
<pre><code>b.option(ChannelOption.SO_KEEPALIVE, true);
|
||
</code></pre>
|
||
<p>ServerBootstrap 设置 Channel 属性有<strong>option</strong>和<strong>childOption</strong>两个方法,option 主要负责设置 Boss 线程组,而 childOption 对应的是 Worker 线程组。</p>
|
||
<p>这里我列举了经常使用的参数含义,你可以结合业务场景,按需设置。</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>参数</th>
|
||
<th>含义</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>SO_KEEPALIVE</td>
|
||
<td>设置为 true 代表启用了 TCP SO_KEEPALIVE 属性,TCP 会主动探测连接状态,即连接保活</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SO_BACKLOG</td>
|
||
<td>已完成三次握手的请求队列最大长度,同一时刻服务端可能会处理多个连接,在高并发海量连接的场景下,该参数应适当调大</td>
|
||
</tr>
|
||
<tr>
|
||
<td>TCP_NODELAY</td>
|
||
<td>Netty 默认是 true,表示立即发送数据。如果设置为 false 表示启用 Nagle 算法,该算法会将 TCP 网络数据包累积到一定量才会发送,虽然可以减少报文发送的数量,但是会造成一定的数据延迟。Netty 为了最小化数据传输的延迟,默认禁用了 Nagle 算法</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SO_SNDBUF</td>
|
||
<td>TCP 数据发送缓冲区大小</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SO_RCVBUF</td>
|
||
<td>TCP数据接收缓冲区大小,TCP数据接收缓冲区大小</td>
|
||
</tr>
|
||
<tr>
|
||
<td>SO_LINGER</td>
|
||
<td>设置延迟关闭的时间,等待缓冲区中的数据发送完成</td>
|
||
</tr>
|
||
<tr>
|
||
<td>CONNECT_TIMEOUT_MILLIS</td>
|
||
<td>建立连接的超时时间</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<h4>端口绑定</h4>
|
||
<p>在完成上述 Netty 的配置之后,bind() 方法会真正触发启动,sync() 方法则会阻塞,直至整个启动过程完成,具体使用方式如下:</p>
|
||
<pre><code>ChannelFuture f = b.bind().sync();
|
||
</code></pre>
|
||
<p>bind() 方法涉及的细节比较多,我们将在《源码篇:从 Linux 出发深入剖析服务端启动流程》课程中做详细地解析,在这里就先不做展开了。</p>
|
||
<p>关于如何使用引导器开发一个 Netty 网络应用我们就介绍完了,服务端的启动过程一定离不开配置线程池、Channel 初始化、端口绑定三个步骤,在 Channel 初始化的过程中最重要的就是绑定用户实现的自定义业务逻辑。是不是特别简单?你可以参考本节课的示例,自己尝试开发一个简单的程序练练手。</p>
|
||
<h3>总结</h3>
|
||
<p>本节课我们围绕 Netty 的引导器,学习了如何开发最基本的网络应用程序。引导器串接了 Netty 的所有核心组件,通过引导器作为学习 Netty 的切入点有助于我们快速上手。Netty 的引导器作为一个非常方便的工具,避免我们再去手动完成繁琐的 Channel 的创建和配置等过程,其中有很多知识点可以深挖,在后续源码章节中我们再一起探索它的实现原理。</p>
|
||
<h3>附录</h3>
|
||
<h4>HTTP 客户端类</h4>
|
||
<pre><code>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);
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h4>客户端业务处理类</h4>
|
||
<pre><code>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();
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/02 纵览全局:把握 Netty 整体架构脉络.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/Netty 核心原理剖析与 RPC 实践-完/04 事件调度层:为什么 EventLoop 是 Netty 的精髓?.md.html">下一页</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
</div>
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709973415e753d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
</body>
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
dataLayer.push(arguments);
|
||
}
|
||
gtag('js', new Date());
|
||
gtag('config', 'G-NPSEEVD756');
|
||
var path = window.location.pathname
|
||
var cookie = getCookie("lastPath");
|
||
console.log(path)
|
||
if (path.replace("/", "") === "") {
|
||
if (cookie.replace("/", "") !== "") {
|
||
console.log(cookie)
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
}
|
||
} else {
|
||
setCookie("lastPath", path)
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
var d = new Date();
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
var expires = "expires=" + d.toGMTString();
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
}
|
||
function getCookie(cname) {
|
||
var name = cname + "=";
|
||
var ca = document.cookie.split(';');
|
||
for (var i = 0; i < ca.length; i++) {
|
||
var c = ca[i].trim();
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
}
|
||
return "";
|
||
}
|
||
</script>
|
||
</html>
|