前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Netty】服务端和客户端

【Netty】服务端和客户端

作者头像
用户3467126
修改2019-07-04 10:23:51
1.1K0
修改2019-07-04 10:23:51
举报
文章被收录于专栏:爱编码

本文是基于Netty4.1.36进行分析

服务端

Netty服务端的启动代码基本都是如下:

代码语言:javascript
复制
private void start() throws Exception {
 final EchoServerHandler serverHandler = new EchoServerHandler();
 /**
         * NioEventLoop并不是一个纯粹的I/O线程,它除了负责I/O的读写之外
         * 创建了两个NioEventLoopGroup,
         * 它们实际是两个独立的Reactor线程池。
         * 一个用于接收客户端的TCP连接,
         * 另一个用于处理I/O相关的读写操作,或者执行系统Task、定时任务Task等。
         */
 EventLoopGroup bossGroup = new NioEventLoopGroup();
 EventLoopGroup childGroup = new NioEventLoopGroup();
 try {
 //ServerBootstrap负责初始化netty服务器,并且开始监听端口的socket请求
 ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, childGroup)
 .channel(NioServerSocketChannel.class)
 .localAddress(new InetSocketAddress(port))
 .childHandler(new ChannelInitializer<SocketChannel>() {
 @Override
 protected void initChannel(SocketChannel socketChannel) throws Exception {
//                            为监听客户端read/write事件的Channel添加用户自定义的ChannelHandler
                            socketChannel.pipeline().addLast(serverHandler);
 }
 });
 ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
 } catch (InterruptedException e) {
            e.printStackTrace();
 } finally {
            bossGroup.shutdownGracefully().sync();
            childGroup.shutdownGracefully().sync();
 }
 }

从上图的代码可以总结为以下几个步骤:

1、创建ServerBootStrap实例 2、设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel 3、设置并绑定服务端的channel 4、5、创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证 6、绑定并启动监听端口 7、当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler

服务端创建时序图

ServerBootStrap引导启动服务端

它就是主要引导启动服务端,工作包括以下:

  • 1.创建服务端Channel
  • 2.初始化服务端Channel
  • 3.将Channel注册到selector
  • 4.端口绑定
1.创建服务端Channel

流程: 首先从用户代码的bind()其实就是AbstractBootstrap.bind(),然后通过反射工厂将用户通过b.channel(NioServerSocketChannel.class)传入的NioServerSocketChannel通过调用底层的jdk的SelectorProvider创建channel,同时也接着创建好对应的ChannelPipeline。 详情可以参考下图,自己去查看一下源码:

2.初始化服务端Channel

主要工作如下:

1)设置的option缓存到NioServerSocketChannelConfig里 2)设置的attr设置到channel里 3)保存配置的childOptions,配置的childAttrs 到ServerBootstrapAcceptor里 4)往NioSocketChannel的pipeline中添加一个ServerBootstrapAcceptor

主要的核心源码如下:

代码语言:javascript
复制
 @Override
 void init(Channel channel) throws Exception {
 final Map<ChannelOption<?>, Object> options = options0();
 synchronized (options) {
            setChannelOptions(channel, options, logger);
 }
 final Map<AttributeKey<?>, Object> attrs = attrs0();
 synchronized (attrs) {
 for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
 @SuppressWarnings("unchecked")
 AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
 }
 }
 ChannelPipeline p = channel.pipeline();
 final EventLoopGroup currentChildGroup = childGroup;
 final ChannelHandler currentChildHandler = childHandler;
 final Entry<ChannelOption<?>, Object>[] currentChildOptions;
 final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
 synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
 }
 synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
 }
        p.addLast(new ChannelInitializer<Channel>() {
 @Override
 public void initChannel(final Channel ch) throws Exception {
 final ChannelPipeline pipeline = ch.pipeline();
 ChannelHandler handler = config.handler();
 if (handler != null) {
                    pipeline.addLast(handler);
 }
                ch.eventLoop().execute(new Runnable() {
 @Override
 public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
 }
 });
 }
 });
 }

小结: 总体如上面工作流程所述。 特别地建议:查看ServerBootstrapAcceptor源码,你可以发现ServerBootstrapAcceptor在channelRead事件触发的时候(也就有客户端连接的时候),把childHandler加到childChannel Pipeline的末尾,设置childHandler的options和attrs,最后把childHandler注册进childGroup

3.将Channel注册到selector

注册过程如下图

小结: Channel 注册过程所做的工作就是将 Channel 与对应的 EventLoop 关联。

1).每个 Channel 都会关联一个特定的 EventLoop, 并且这个 Channel 中的所有 IO 操作都是在这个 EventLoop 中执行的;

2).当关联好 Channel 和 EventLoop 后, 会继续调用底层的 Java NIO SocketChannel 的 register 方法, 将底层的 Java NIO SocketChannel 注册到指定的 selector 中.

通过这两步, 就完成了 Netty Channel 的注册过程.

4.端口绑定

端口绑定的源码流程基本如下图,详情可以还是你自己读一下源码比较好点。

小结: 其实netty端口绑定是调用 jdk的javaChannel().bind(localAddress, config.getBacklog());进行绑定,然后TCP链路建立成功,Channel激活事件,通过channelPipeline进行传播。

客户端

客户端启动的常规代码如下:

代码语言:javascript
复制
 private void start() throws Exception {
 /**
         * Netty用于接收客户端请求的线程池职责如下。
         * (1)接收客户端TCP连接,初始化Channel参数;
         * (2)将链路状态变更事件通知给ChannelPipeline
         */
 EventLoopGroup group = new NioEventLoopGroup();
 try {
 Bootstrap b = new Bootstrap();
            b.group(group)
 .channel(NioSocketChannel.class)
 .remoteAddress(new InetSocketAddress(host,port))
 .handler(new ChannelInitializer<SocketChannel>() {
 @Override
 protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
 }
 });
 //绑定端口
 ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
 } catch (Exception e) {
 group.shutdownGracefully().sync();
 }
 }
流程:

1.用户线程创建Bootstrap实例,通过API设置创建客户端相关的参数,异步发起客户端连接。 2.创建处理客户端连接、I/O读写的Reactor线程组NioEventLoopGroup,默认为CPU内核数的2倍。 3.通过Bootstrap的ChannelFactory和用户指定的Channel类型创建用于客户端NioSocketChannel,它的功能类似于JDK NIO类库提供的SocketChannel 4.创建默认的Channel Handler Pipeline,用于调度和执行网路事件。 5.异步发起TCP连接,判断连接是否成功。如果成功,则直接将NioSocketChannel注册到多路复用器上,监听读操作位,用于数据包读取和消息发送,如果没有立即连接成功,则注册连接监听为到多路复用器,等待连接结果。 6.注册对应的网络监听状态为到多路复用器。 7.由多路复用器在I/O现场中轮询个Channel,处理连接结果。 8.如果连接成功,设置Future结果,发送连接成功事件,触发ChannelPipeline执行。 9.由ChannelPipeline调度执行系统和用户的ChannelHandler,执行逻辑。

源码调用流程如下图:

小结:客户端是如何发起 TCP 连接的?

如下图:

特别提醒: 在AbstractChannelHandlerContext.connect()#findContextOutbound这步操作是返回的结果next其实是头节点,也就是说在下一步next.invokeConnect()这里的next就是头节点,所以最终是调用HeadContext .connect()

总结

本文主要讲述netty服务端和客户端的简单工作流程。 具体服务端与客户端如何通信,以及内存管理等方面的知识下一次再写。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 服务端
    • 服务端创建时序图
      • ServerBootStrap引导启动服务端
        • 1.创建服务端Channel
        • 2.初始化服务端Channel
        • 3.将Channel注册到selector
        • 4.端口绑定
        • 流程:
        • 源码调用流程如下图:
    • 客户端
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档