👆点击“博文视点Broadview”,获取更多书讯
Netty在服务端启动过程中是如何绑定端口、启动服务的呢?
在启动服务的过程中,我们可以顺势了解到Netty各大核心组件。
本文暂时不会详细描述这些组件,先简单介绍一下各大组件是如何协同工作、一起构建Netty核心的。
01
服务端启动示例
我们写了一个比较完整的服务端启动例子,绑定在8888端口,使用NIO模式。
public final class SimpleServer {
public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup();
try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new SimpleServerHandler()) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { } });
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
private static class SimpleServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) { System.out.println("channelActive"); }
@Override public void channelRegistered(ChannelHandlerContext ctx) { System.out.println("channelRegistered"); }
@Override public void handlerAdded(ChannelHandlerContext ctx) { System.out.println("handlerAdded"); } }}
下面来回顾一下每个方法的作用。
1. EventLoopGroup:服务端的线程模型外观类。从字面意思可以了解到,Netty的线程模型是事件驱动型的,也就是说,这个线程要做的事情就是不停地检测IO事件、处理IO事件、执行任务,不断重复这三个步骤。
2. ServerBootstrap:服务端的一个启动辅助类。通过给它设置一系列参数来绑定端口启动服务。
3. .group(bossGroup, workerGroup):设置服务端的线程模型。读者可以先想象一下:在一个工厂里,我们需要两种类型的人干活,一种是老板,一种是工人。老板负责从外面接活,把接到的活分配给工人。放到这里,bossGroup的作用就是不断地接收新的连接,将新连接交给workerGroup来处理。
4. .channel(NioServerSocketChannel.class):设置服务员的IO类型为NIO。Netty通过指定Channel的类型来指定IO类型。Channel在Netty里是一大核心概念,可以理解为,一个Channel就是一个连接或者一个服务端bind动作,后面会细讲。
5. .handler(new SimpleServerHandler():表示在服务端启动过程中,需要经过哪些流程。这里SimpleServerHandler最终的顶层接口为ChannelHandler,是Netty的一大核心概念,表示数据流经过的处理器,可以理解为流水线上的每一道关卡。
6. .childHandler(new ChannelInitializer<SocketChannel>)...:使用过Netty的读者应该知道,这里的方法体主要用于设置一系列Handler来处理每个连接的数据,也就是上面所说的,老板接到一个活之后,告诉每个工人这个活的固定步骤。
7. ChannelFuture f = b.bind(8888).sync():绑定端口同步等待。这里就是真正的启动过程了,绑定端口8888,等服务端启动完毕,才会进入下一行代码。
8. f.channel().closeFuture().sync():等待服务端关闭端口绑定,这里的作用其实是让程序不会退出。
9. bossGroup.shutdownGracefully()和workerGroup.shutdownGracefully():关闭两组事件循环,关闭之后,main方法就结束了。
上述代码可以很轻松地在本地运行,最终控制台的输出如下。
handlerAddedchannelRegisteredchannelActive
为什么控制台会按顺序输出这些字符,接下来我们就深入细节一探究竟。
02
服务端启动的核心步骤
我们通过以上示例代码来理一理服务端启动的基本流程。
在上面的示例代码中,我们是通过ServerBootstrap这个辅助类来实现服务端启动的,给这个启动类设置一些参数,然后通过它的外观接口来实现启动。重点落在下面这行代码上。
b.bind(8888).sync();
【提示】我们刚开始看源码时,在对细节没那么清楚的情况下可以借助IDE的Debug功能,单步执行,以确定程序运行的入口。
我们跟进到bind()方法。
ServerBootstrap.java
public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort));}
通过端口号创建一个InetSocketAddress对象,然后继续调用重载方法bind()。
ServerBootstrap.java
public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress);}
validate()验证服务启动需要的必要参数,然后调用doBind()。
ServerBootstrap.java
private ChannelFuture doBind(final SocketAddress localAddress) { //... final ChannelFuture regFuture = initAndRegister(); //... final Channel channel = regFuture.channel(); //... doBind0(regFuture, channel, localAddress, promise); //... return promise;}
在这里,笔者减掉了细枝末节,专注于核心方法,分别为initAndRegister()和doBind0()。
从方法名我们已经可以略窥一二,init代表初始化,register代表注册,bind代表绑定端口。联系NIO开发的基本流程,可能是把某个东西初始化之后注册到Selector上,最后bind像是在本地绑定端口号。
带着这些猜测,我们深入分析下去。
我们先来看一下initAndRegister()方法。
AbstractBootstrap.java
final ChannelFuture initAndRegister() { Channel channel = null; //... // 1. 创建服务端 Channel channel = channelFactory.newChannel(); //... // 2. 初始化服务端 Channel init(channel); //... // 3. 注册服务端 Channel ChannelFuture regFuture = config().group().register(channel); //... return regFuture;}
同样地,笔者略去了其他细节,专注于骨干代码。initAndRegister()中的3个主要方法与doBind0()方法一起组合成了服务端启动的4个过程。
1. 创建服务端Channel。
2. 初始化服务端Channel。
3. 注册服务端Channel。
4. 绑定服务端端口。
这4个过程的详细分析写在《跟闪电侠学 Netty:Netty 即时聊天实战与底层原理》一书中,大家可以阅读这本书继续学习哦~~
本书上篇通过一个即时聊天的例子,让读者能够系统地使用一遍Netty,全面掌握Netty的知识点;下篇通过对源码的层层剖析,让读者能够掌握Netty底层原理,知其然并知其所以然,从而编写出高性能网络应用程序。
上篇 入门实战
在入门实战篇中,读者跟随笔者实践完这个即时聊天系统后,能够学会如何使用Netty完成最基本的网络通信程序,可以掌握以下知识点:
1. 如何启动服务端?
2. 如何启动客户端?
3. 如何设计长连自定义协议?
4. 拆包/粘包原理与实践。
5. 如何实现自定义编解码。
6. 如何使用Pipeline与ChannelHandler?
7. 心跳与空闲检测的方法。
8. 如何性能调优?
本篇通俗易懂,可一口气读完,让你一周内进入实战!
下篇 源码分析
在源码分析篇中,笔者从用户视角出发,环环相扣,带领读者逐个攻破Netty底层原理,掌握以下知识点:
1. 服务端启动流程:ServerBootstrap外观,创建NioServerSocketChannel,初始化,注册Selector,绑定端口,接收新连接。
2. 高并发线程模型:Netty无锁化串行设计,精心设计的Reactor线程模型榨干CPU、打满网卡、让应用程序性能爆表的底层原理。
3. 新连接接入流程:Boss Reactor线程,监测新连接,创建NioSocketChannel,IO线程分配,Selector注册事件。
4. 解码原理:解码顶层抽象,定长解码器,行解码器,分隔符解码器,基于长度域解码器全面分析。
5. 事件传播机制脉络:大动脉Pipeline,处理器ChannelHandler,Inbound和Outbound事件传播与异常传播的原理,编码原理。
6. writeAndFlush流程:深入了解使用最频繁的writeAndFlush的底层原理,避免踩坑。
适读人群
本书适合以下三类人群:
1. 如果你听说过或简单使用过Netty,想全面系统地学习Netty,并掌握一些性能调优方法,本书的入门实战篇可以帮助你达成这个目标。
2. 如果你深度使用过Netty,想深入了解Netty的底层设计,编写出更灵活高效的网络通信程序,本书的源码分析篇可以帮助你达成这个目标。
3. 如果你从未读过开源框架源码,本书将是你的第一本源码指导书,阅读优秀的开源软件源码可以助你写出更优美的程序。读源码并不难,难的是迈出这一小步,之后就能通往更广阔的空间。
本书推荐使用方式
01. 按章节顺序把入门实战篇的代码一章章敲出来,在没有掌握前一章节的知识点之前,建议不要跳跃学习。
02. 入门实战篇学完之后,合上书本,把本书即时聊天系统的代码再整体敲若干遍,敲的过程中可能会发现自己有遗忘知识点,这个时候可能需要不断翻阅书本,没有关系,翻阅就好了。
03. 确保最后一次实现本书的即时聊天系统的例子是没有翻阅书本的,是完全自行实现的,之后进入源码分析篇的学习。
04. 针对源码分析篇,建议读者按章节顺序来学习,不要跳跃,不要图快,每一步都要扎实。
05. 在源码学习的过程中,先跟随书本,对照源码,把对应章节的流程过一遍,每个章节学完之后,建议花较多的时间进行调试和阅读,确保掌握了前一章节的内容之后再进行下一章的学习。
京东限时立减50,快快扫码抢购吧!
发布:刘恩惠
审核:陈歆懿
如果喜欢本文欢迎 在看丨留言丨分享至朋友圈 三连
热文推荐
我jio得,有望过上钢铁侠一样的生活了!
书单 | 春节假期,我想把这几本书带回家!
2023十大科技趋势(达摩院发布)
手把手教你编写Node.js模块
▼点击阅读原文,了解本书详情~
本文分享自 博文视点Broadview 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!