Netty源码 之 bind绑定流程

news2024/11/15 21:32:04

1.Netty框架总览

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

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

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

1.1 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等。

1.2 优化的数据结构

优化的数据结构包括: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降低了对定时任务的调度的精度,但是极大的优化了性能。【具体见之后的讲解或之前的总结】

2.从服务端启动类的源码开始分析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()这么简单。后续慢慢展开总结。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot中使用Spring自带线程池ThreadPoolTaskExecutor与Java8CompletableFuture实现异步任务示例

场景 关于线程池的使用&#xff1a; Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)&#xff1a; Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)_executorservice executorservice executors.newfix-CSDN博客 Java中创建线程的方式…

Unity_ShaderGraph节点问题

Unity_ShaderGraph节点问题 Unity版本&#xff1a;Unity2023.1.19 为什么在Unity2023.1.19的Shader Graph中找不见PBR Master节点&#xff1f; 以下这个PBR Maste从何而来&#xff1f;

Arthas使用教程—— 阿里开源线上监控诊断产品

文章目录 1 简介2背景3 图形界面工具 arthas 阿里开源3.1 &#xff1a;启动 arthas3.2 help :查看arthas所有命令3.3 查看 dashboard3.4 thread 列出当前进程所有线程占用CPU和内存情况3.5 jvm 查看该进程的各项参数 &#xff08;类比 jinfo&#xff09;3.6 通过 jad 来反编译 …

【Chrono Engine学习总结】1-安装配置与程序运行

本文仅用于个人安装记录。 官方安装教程 https://api.projectchrono.org/8.0.0/tutorial_install_chrono.html Windows下安装 windows下安装就按照教程好了。采用cmake-gui进行配置&#xff0c;建议首次安装只安装核心模块。然后依此configure下irrlicht&#xff0c;sensor…

JVM 性能调优 - 参数调优(3)

查看 JVM 内存的占用情况 编写代码 package com.test;public class PrintMemoryDemo {public static void main(String[] args) {// 堆内存总量long totalMemory Runtime.getRuntime().totalMemory();// jvm 试图使用的最大堆内存long maxMemory Runtime.getRuntime().maxM…

国内游戏服务器价格表

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;可以选择轻量应用服务器和云服务器&#xff0c;阿腾云atengyu…

PHP客服系统-vue客服聊天系统

PHP-Vue客服聊天系统是一款高效、灵活的客户服务解决方案&#xff0c;基于ThinkPHP6、Vue3和Workerman(Gateworker)框架开发&#xff0c;专为单商户场景打造。 系统亮点&#xff1a; 分布式部署支持&#xff0c;轻松应对高并发场景&#xff1b;本地消息存储功能&#xff0c;确…

Python 数据分析(PYDA)第三版(四)

原文&#xff1a;wesmckinney.com/book/ 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 八、数据整理&#xff1a;连接、合并和重塑 原文&#xff1a;wesmckinney.com/book/data-wrangling 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 此开放访问网络版本的…

STM32F1 - 点灯-寄存器模式

点灯 实验概述&#xff1a;Step1> 建立工程Step2> 宏定义 - 寄存器地址 实验概述&#xff1a; 用配置寄存器的方式&#xff0c;开关一个LED灯&#xff0c; 只用标准库中提供的启动文件&#xff0c; Step1> 建立工程 出现错误&#xff1a;导入文件类型错误 keil5编译中…

QMUI_Android:提升Android开发效率与质量的利器

QMUI_Android&#xff1a;提升Android开发效率与质量的利器 在Android应用开发过程中&#xff0c;开发者常常面临着重复编写基础组件和处理兼容性问题的挑战&#xff0c;这不仅耗费时间&#xff0c;也降低了开发效率。为了解决这一问题&#xff0c;Tencent推出了QMUI_Android框…

如何使用Python + 百度翻译API 自动大批量免费翻译Excel文件中的外语内容

手里有一个Excel文件,包括了大量的亚马逊德语搜索词(关键词),每个单元格1个,需要翻译为中文。但是文件大小超过了10M,不能使用百度或Google免费的文档功能,如果手工一个个的翻译然后粘贴又太麻烦,于是想到用Python加免费翻译API完成。 一、openpyxl库 用Python编辑处…

【数据结构】排序之冒泡排序和快速排序

简单不先于复杂&#xff0c;而是在复杂之后。 文章目录 1. 交换排序1.1 冒泡排序1.2 快速排序1.3 快速排序优化1.4 快速排序非递归 1. 交换排序 基本思想&#xff1a;所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换…

day44_jdbc

今日内容 0 复习昨日 1 讲作业 2 数据库连接池(druid) 3 反射 4 改造DBUtil 5 完成CRUD练习 0 复习昨日 1 sql注入 2 预处理语句 3 事务操作 4 DBUtil 1 作业【重要】 利用ORM完成,以下的几个方法非常重要,将来写项目就是这些操作 写项目步骤 搭建环境 创建项目导入依赖工具类数…

《动手学深度学习(PyTorch版)》笔记7.4

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

前端学习第四天

目录 一、复合选择器 1.后代选择器 2.子代选择器 3.并集选择器 4.交集选择器 5.伪类选择器 1.伪类-超链接&#xff08;拓展&#xff09; 二、CSS特性 1.继承性 2.层叠性 3.优先级 1.优先级-叠加计算规则 2.emmet写法 三、背景属性 1.背景图 ​编辑2.背景图平铺方…

【知识整理】一文理解系统服务高可用

一、如何理解高可用 1、什么是高可用 高可用性&#xff08;英语&#xff1a; High Availability&#xff0c;缩写为 HA&#xff09;&#xff0c;指系统无中断地执行其功能的能力&#xff0c;代表系统的可用性程度&#xff0c;是进行系统设计时的准则之一。 2、决定可用性的两…

指针进阶(上)

二级指针 二级指针是用来存放一级指针地址。 如何使用和解引用呢&#xff1f; #include <stdio.h>int main() {int a 5;int* p &a;int** p2 &p;**p2 10;printf("%d\n", a);return 0; }这里的解引用使用两颗星号的原因是&#xff1a;一个星号找到…

Python初学者学习记录——python基础综合案例:数据可视化——动态柱状图

一、案例效果 通过pyecharts可以实现数据的动态显示&#xff0c;直观的感受1960~2019年世界各国GDP的变化趋势 二、通过Bar构建基础柱状图 反转x轴和y轴 标签数值在右侧 from pyecharts.charts import Bar from pyecharts.options import LabelOpts# 构建柱状图对象 bar Bar()…

介绍docker

一&#xff1a;介绍docker&#xff1a; Docker 并没有单独的图形界面&#xff0c;它主要通过命令行来进行管理和操作 1、 docker ps&#xff1a;显示正在运行的容器。 docker images&#xff1a;显示本地的镜像。 docker run&#xff1a;创建并启动一个新容器。 docker stop&a…

台灯学生用哪个牌子好?学生用护眼台灯品牌推荐

晚上学习&#xff0c;有台灯肯定比没台灯好。只要是盏合格的、能用的台灯&#xff0c;都能给你一个稳定又亮堂的环境。但是有些不合格的台灯会给眼睛带来伤害&#xff0c;尤其是学习负担比较重的学生。那有哪些台灯是学生用着比较好用的呢&#xff1f; 一、学生使用护眼台灯的…