Netty源码系列 之 bind绑定流程 源码

Netty框架总览

Netty是一个基于NIO异步通信框架

Netty框架是由许多组件,优化的数据结构所构建成。

正是通过灵活的组件构建,优化后的数据结构,进而才能保证Netty框架面对高并发场景具有一定的能力

Netty相关组件

Netty重要的组件有:Channel,EventLoop,Unsafe,ChannelPipeline,Bootstrap,ServerBootstrap等

Channel:

Netty最核心的组件为:Channel

1.通过Channel管道可以设置自定义封装的参数,也可以设置TCP-操作系统级别的参数【TCP参数通常在linux等OS操作系统配置文件中可以设置】,在Netty中,这些TCP参数通常以SO_xxxx开头的

2.Channel同样是Netty框架各个组件结构的串联者

EventLoop:

EventLoop实际上是一个独立的线程,是一个单线程池

你可以把它视作是一个类似于new Thread()创建的线程,但是EventLoop更加贴合Netty体系。EventLoop可以处理连接操作,IO操作,普通任务,定时任务。

Unsafe:

Unsafe是线程不安全的。它提供了直接访问底层数据的能力,用于高效地进行网络数据的读写操作。Unsafe主要用于解决Java NIO中的一些性能瓶颈和限制

Channel的流转通信需要EventLoop线程去做,Channel的IO读写需要Unsafe具体去做,无论是EventLoop还是Unsafe,都是围绕着Channel去做工作的。

当管道Channel建立完成后,后续引入ChannelPipeline体系处理工作:

ChannelPipeline:

该组件包含ChannelContext和ChannelHandler。

ChannelHandler是我们日常程序员开发最重要,打交道最多的地方。

Bootstra,ServerBootstrap:

后续再通过Bootstrap,ServerBootstrap进行整合Channel,EventLoop,Unsafe,ChannelPipeline等。

优化的数据结构

优化的数据结构包括:Selector,FastThreadLocal,HashWheelTimer等

Selector:

Netty的Selector就是一个IO多路复用器,但是Netty的Selector相对于JavaNIO中原生的Selector而言,做了性能优化。JavaNIO的Selector底层是基于Set集合的,但是Netty的Selector是基于数组的

FastThreadLocal:

在多线程环境下,你一定会需要ThreadLocal做线程数据独享的,但是Java体系的ThreadLocal存在性能瓶颈。在高并发多线程环境下,Netty使用FastThreadLocal这一数据结构优化原生的ThreadLocal,能够更加应对高并发多线程的场景

HashWheelTimer:

HashWheelTimer和Timer一样,都是存储定时任务的数据结构,然后到特定时刻后,就会触发调度相对应的定时任务。比如说:19点51分需要调度定时任务1,而19点52分需要调度定时任务2。

该数据结构也是对Java原生数据结构Timer的优化。原生的Timer是基于二叉树的,对每一个加入的定时任务的调度时间控制的十分精确。但是HashWheelTimer是基于哈希时间轮[一个环形的数组],HashWheelTimer降低了对定时任务的调度的精度,但是极大的优化了性能。【具体见之后的讲解或之前的总结】

从服务端启动类的源码开始分析bind源码

  • NIO方式编写服务端程序
  //方式1:使用NIO进行编写服务器端

        //Selector对象会被封装到EventLoop类的成员变量,在构造方法中进行初始化
        Selector selector = Selector.open();

        //完成ServerSocketChannel创建的过程---->【初始化操作】
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);

        //完成ServerSocketChannel注册的工作---->【注册操作】
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
        //设置事件
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
        //断开绑定的工作
        serverSocketChannel.bind(new InetSocketAddress(8000));
  • Netty方式编写服务端程序
 //方式2:使用Netty编写服务端
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
            }
        });
        Channel channel = serverBootstrap.bind(8000).sync().channel();
        channel.closeFuture().sync();

对比NIO与Netty的编码:

1.原有的NIO代码 在netty编程“消失”---》 NIO代码被Netty封装了起来。

2.Netty中组件NioServerSocketChannel 作用:首先它是一个Channel,Channel是各个组件的串联者,Channel同样是Pipeline的管理者。

先抛出结论:

Netty是NIO代码的封装。

NIO代码和Netty毫无关系,那么Netty的NioServerSocketChannel如何和Nio体系整合的呢?是通过serverSocketChannel.register(selector,0,附件对象),把NioServerSocketChannel传入到附件对象所在的位置,通过附件机制进行绑定整合的。【后续会验证】

NioServerSocketChannel体系图:

父类AbstractNioMessageChannel:

继承该类使得NioServerSocketChannel具有读写操作Netty的网络数据的能力,当然它的底层是通过Unsafe为基础进行网络读写的。

接口AttributeMap:该接口用于在netty中给Channel(SSC或SC)设置属性,所谓属性就是一系列的参数,参数包括netty自定义的参数或TCP-OS系统级别的参数。其实就是一个Map,key键值存储参数名,value值存储的是参数名所对应的参数值。

提出问题:

既然说Netty会封装NIO的代码,那么NIO代码都被封装到哪里了?

如下:

还是那个问题:NIO与Netty毫无关系,那么最核心的NioServerSocketChannel如何和NIO整合的?

根据上述那个结论:通过SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null)这句NIO代码的附件机制传递NioServerSocketChannel给Netty体系,完成整合,后续debug源码会看到的。

doBind源码之ServerSocketChannel的初始化过程,注册过程 源码

  • 从serverBootstrap.bind(8000).sync().channel()这句代码开始debug源码

测试代码如下:

package com.messi.netty_source_03.Test01;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;

/**
 * @Description TODO
 * @Author etcEriksen
 * @Date 2024/1/5 17:08
 * @Version 1.0
 */
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(eventLoopGroup);
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
            }
        });
        Channel channel = serverBootstrap.bind(8000).sync().channel();
        channel.closeFuture().sync();
    }

}

源码debug流程:

1.

2.final ChannelFuture regFuture = initAndRegister();

initAndRegister()是异步方法,异步结果使用ChannelFuture接收,ChannelFuture就是底层的promise。该异步结果是如何设置的?异步开启的新线程进行执行对应的业务逻辑,让该异步线程会设置异步执行的结果给promise。main主线程接收到异步线程设置的promise,并且以ChannelFuture类型的regFuture进行接收。

3.进入initAndRegister();方法

4.channelFactory.newChannel():创建NioServerSocketChannel对象

5.进入init(channel)方法:完成NioServerSocketChannel的初始化

6.ChannelFuture regFuture = config().group().register(channel)

进而group方法:分配创建一个EventLoopGroup线程池

进入register方法:

此时线程栈也会切换到新创建的NioEventLoop线程

进入register0方法:

进入doRegister方法:该方法就是把当前NioServerSocketChannel对象注册到当前异步开启的新线程NioEventLoop的Selector多路复用器上

进入pipeline.invokeHandlerAddedIfNeeded()方法:

进入safeSetSuccess方法:

doBind源码之bind源码核心绑定流程

  • 从serverBootstrap.bind(8000).sync().channel()这句代码开始debug源码
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(eventLoopGroup);
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
            }
        });
        Channel channel = serverBootstrap.bind(8000).sync().channel();
        channel.closeFuture().sync();
    }

}

过程如下:

1.

2.

3.

4.

5.进入bind方法

6.进入invokeBind

7.

8.

9.

10.不再继续深入追了,一步步回退一下

11.

12.

13.进入readIfIsAutoRead方法

14.

15.

14.重点:完成通道对应【SelectionKey的创建和相关事件的注册】

详细说说SelectionKey:

这里其实还是封装的NIO代码。但是Netty做了很多优化,比如说在存储SelectionKey时,Netty使用的是数组进行存储,而NIO使用的是Set进行存储。

言归正传,SelectionKey是啥?比如说:当一个服务端启动并且注册,则是你把一个ServerSocketChannel注册到Selector上并且注册Accepte连接事件,此时Netty就会给你分配一个SelectionKey,该SelectionKey就是用于标识这个channel通道和该通道注册监听的事件的。同理如果该服务端监控到连接Accept事件的发生,那么SSC会给每一个客户端连接对应分配一个SocketChannel对象,我们同样会把SocketChannel对象注册到Selector上并且注册监听read或write事件,此时Netty也会对应给该SocketChannel分配一个SelectionKey用于标识该channel并且注册监听该事件。

当一个服务端接收多个客户端连接时,SSC会分配多个SC对象,那么多个SC对象注册到Selector则会产生多个SelectionKey,每一个客户端连接对应一个SelectionKey。但是只有一个服务端并且只注册到Selector一次,所以服务端只对应一个SelectionKey。

注释:

虽然说Selector是监控器,但是当监控到事件触发后,真正处理事件逻辑代码的是线程,线程在netty做了封装,也就是EventLoop。EventLoop可不止一个new Thread()这么简单。后续慢慢展开总结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/376295.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营DAY14 | 二叉树 (1)

一、二叉树理论基础 1.存储方式 链式存储&#xff1a; 顺序存储&#xff1a; 2.二叉树标准定义(Java) public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val val; }TreeNode(int val, TreeNode left, TreeNode right) {…

spring cloud stream

背景 主要解决不同消息中间件切换问题。实现不同中间件的代码解耦。 链接: 支持的中间件 后文使用kafka测试。 引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-stream</artifactId></depende…

微服务介绍、使用 Nacos 实现远程调用以及 OpenFeign 的使用

1 微服务的概念 区别于单体项目 单体项目拆分成微服务项目的目标&#xff1a;高内聚、低耦合 拆分思路 纵向拆分&#xff1a;根据功能模块 横向拆分&#xff1a;抽取可复用模块 2 微服务拆分——远程调用 背景&#xff1a;微服务单一职责&#xff0c;每个服务只有自己的功能…

电脑没有声音是怎么回事?几招快速解决

当电脑突然失去声音&#xff0c;这可能成为一种令人烦恼的体验&#xff0c;尤其是在你期望享受音乐、观看视频或进行在线会议的时候。幸运的是&#xff0c;大多数时候&#xff0c;电脑没有声音的问题是可以迅速解决的。电脑没有声音是怎么回事&#xff1f;本文将为你介绍一些常…

老是抓不准现货白银实时报价怎么办?

现货白银的实时报价是不断变动的&#xff0c;投资者要了解当下的现货白银实时走势&#xff0c;并且依靠对实时报价的分析预判未来的趋势&#xff0c;这是不容易的&#xff0c;但是不是不能做到呢&#xff1f;也不是。因为市场不是横盘就是趋势&#xff0c;只要有趋势&#xff0…

零代码3D可视化快速开发平台

老子云平台 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。此技术已经在全球申请了专利…

.NET Avalonia开源、免费的桌面UI库 - SukiUI

前言 今天分享一款.NET Avalonia基于MIT License协议开源、免费的桌面UI库&#xff1a;SukiUI。 Avalonia介绍 Avalonia是一个强大的框架&#xff0c;使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件&#xff0c;确保在Windows、macOS、Linux、An…

用云手机打造tiktok账号需要注意些什么?

随着tiktok平台的火热&#xff0c;越来越多的商家开始尝试更高效的tiktok运营方法。其中&#xff0c;tiktok云手机作为一种新科技引起了很多人的注意&#xff0c;那么用云手机运营tiktok需要注意些什么&#xff1f;下文将对此进行详细解析。 1. 不是所有的云手机都适合做tiktok…

跳过mysql密码并重置密码 shell脚本

脚本 目前只是验证了5.7 版本是可以的&#xff0c;8.多的还需要验证 以下是一个简单的Shell脚本&#xff0c;用于跳过MySQL密码设置并重置密码&#xff1a; #!/bin/bash yum install psmisc -y# 停止MySQL服务 sudo service mysqld stop# 跳过密码验证 sudo mysqld --skip-g…

通过 docker-compose 部署 Flink

概要 通过 docker-compose 以 Session Mode 部署 flink 前置依赖 Docker、docker-composeflink 客户端docker-compose.yml version: "2.2" services:jobmanager:image: flink:1.17.2ports:- "8081:8081"command: jobmanagervolumes:- ${PWD}/checkpoin…

分析伦敦银报价总失败?你试试这样

做伦敦银交易的投资者要先对伦敦银报价进行分析&#xff0c;但是有些投资者反映自己分析伦敦银报价总是失败&#xff0c;抓不住市场价格的变化趋势&#xff0c;为什么会这样呢&#xff1f;我们可以从以下这两个方面来考虑。 转变分析工具。为什么分析伦敦银报价总失败&#xff…

提升你的PHP开发效率:探索JetBrains PhpStorm 2022的全新特性

在当今快速发展的软件开发领域&#xff0c;选择一个强大且高效的开发工具对于提升开发效率、保证代码质量至关重要。对于PHP开发者来说&#xff0c;JetBrains PhpStorm一直是市场上最受欢迎的IDE之一。随着JetBrains PhpStorm 2022的发布&#xff0c;这款工具带来了一系列创新功…

蓝桥杯-求阶乘-python

问题描述 满足N!的末尾恰好有K个0的最小的N是多少&#xff1f; 如果这样的N不存在输出一1。 思路解析 末尾的0是由10产生的&#xff0c;而10是由质数2和5产生的 在求阶乘的过程中&#xff0c;只要是偶数就会有2&#xff0c;而5相对2更少&#xff0c;所以对于10的数量我们可以…

机器学习-梯度下降法

不是一个机器学习算法是一种基于搜索的最优化方法作用&#xff1a;最小化一个损失函数梯度上升法&#xff1a;最大化一个效用函数 并不是所有函数都有唯一的极值点 解决方法&#xff1a; 多次运行&#xff0c;随机化初始点梯度下降法的初始点也是一个超参数 代码演示 impor…

手势检测跟踪解决方案

美摄科技&#xff0c;作为业界领先的人工智能技术提供商&#xff0c;致力于为企业提供先进的手势检测与跟踪解决方案&#xff0c;以推动企业在智能化、高效化的道路上阔步前行。 一、手势检测与跟踪技术的优势 手势检测与跟踪技术作为人机交互的重要一环&#xff0c;具有以下…

视频无损放大修复工具Topaz Video AI 新手入门教程

想要自学Topaz Video AI &#xff1f;Topaz Video AI 如何使用&#xff1f;这里给大家带来了视频无损放大修复工具Topaz Video AI 新手入门教程&#xff0c;快来看看吧&#xff01; 下载&#xff1a;Topaz Video AI for mac 导入您的文件 有两种方法可以将文件导入 Topaz Vid…

《乱弹篇(十一)过年与心灵鸡汤》

快过年了&#xff0c;为了不使一些网站感到为难&#xff0c;也让笔者绕开“敏感词”&#xff0c;营造一个眼不见心不烦&#xff0c;双方都能接受的安宁清静的好心境&#xff0c;今天本“人民体验”推广人民日报官方微博文化产品《夜读&#xff1a;快过年了&#xff0c;这三句话…

每日一练:LeeCode-112、路径总和【二叉树+DFS+回溯】

本文是力扣LeeCode-112、路径总和 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有…

CSS太极动态图

CSS太极动态图 1. 案例效果 我们今天学习用HTML和CSS实现动态的太极&#xff0c;看一下效果。 2. 分析思路 太极图是由两个旋转的圆组成&#xff0c;一个是黑圆&#xff0c;一个是白圆。实现现原理是使用CSS的动画和渐变背景属性。 首先&#xff0c;为所有元素设置默认值为0…

第2章 路由

学习目标 掌握注册路由的方式&#xff0c;能够独立完成路由的注册 掌握URL传递参数的方式&#xff0c;能够通过URL规则向视图函数传递参数 掌握转换器用法&#xff0c;能够根据业务需求灵活应用内置转换器或自定义转换器 掌握指定请求方式&#xff0c;能够在注册路由时指定请…