当前位置: 首页 > news >正文

Netty第三部

继续Netty第二部的内容

一、ChannelHandler

1、ChannelHandler接口

ChannelHandler是Netty的主要组件,处理所有的入站和出站数据的应用程序逻辑的容器,可以应用在数据的格式转换、异常处理、数据报文统计等

继承ChannelHandler的两个子接口:

ChannelInboundHandler:处理入站数据以及各种状态变化

ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作

2、ChannelInboundHandler接口

  1. channelRegistered当Channel已经注册到它的EventLoop并且能够处理I/O时被调用
  2. channelUnregistered当Channel从它的EventLoop注销并且无法处理任何I/O时被调用
  3. channelActive当Channel处于活动状态时被调用;Channel已经连接/绑定并且已经就绪
  4. channelInactive当 Channel离开活动状态并且不再连接它的远程节点时被调用
  5. channelReadComplete当Channel上的一个读操作完成时被调用
  6. channelRead 当从Channel读取数据时被调用
  7. channelWritabilityChanged当Channel的可写状态发生改变时被调用。可以通过调用Channel的 isWritable()方法来检测Channel的可写性。与可写性相关的阈值可以通过 Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置
  8. userEventTriggered当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用。

3、ChannelOutboundHandler接口

出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。

所有由 ChannelOutboundHandler 本身所定义的方法:

  1. bind(ChannelHandlerContext,SocketAddress,ChannelPromise) 当请求将 Channel 绑定到本地地址时被调用
  2. connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) 当请求将 Channel 连接到远程节点时被调用
  3. disconnect(ChannelHandlerContext,ChannelPromise) 当请求将 Channel 从远程节点断开时被调用
  4. close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
  5. deregister(ChannelHandlerContext,ChannelPromise) 当请求将 Channel 从它的 EventLoop 注销时被调用
  6. read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用
  7. flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调用
  8. write(ChannelHandlerContext,Object,ChannelPromise) 当请求通过 Channel 将数据写到 远程节点时被调用

4、ChannelHandler的适配器

ChannelInboundHandlerAdapter(处理入站)和ChannelOutboundHandlerAdapter(处理出站)是Netty提供的ChannelHandler基类,降低了ChannelHandler实现的复杂度

为什么ChannelOutboundHandlerAdapter会有read()方法

ChannelOutboundHandler.read()是主动触发读事件的,或者是处理读事件的前置处理器;调用ChannelOutboundHandler.read()方法时Channel会向Selector读取数据,读取数据之后会交给ChannelPipeline,这时候ChannelPipeline就会触发ChannelInboundHandler.channelRead()读事件

如何在一个ChannelHandler实现同时实现入栈出战

继承ChannelDuplexHandler,也可以同时实现ChannelOutboundHandler, ChannelInboundHandler这两个接口

5、共享Handler

ChannelHandlerAdapter提供了isSharable()方法,如果实现ChannelHandler的类上指定了@Sharable注解,isSharable()方法就会返回true,反之则否;

每个SocketChannel都有自己的ChannelPipeline,同时SocketChannel会对应一个EventLoop,也就是说只会一个线程来处理,ChannelHandler实例之间是完全独立,只要不是共享了全局变量,ChannelHandler是线程安全的

如何通过共享Handler实现包统计

继承ChannelDuplexHandler类,同时类上加上@Sharable注解注解,指定该实例为static,保证只有一个,添加到ChannelPipeline中,重写read()和flush()方法,就可以具体发包收包记录了

6、资源管理和SimpleChannelInboundHandler

在NIO实现中需要依靠创建buffer来进行Channel之间的数据交换;

Netty也是同样的设计,在read网络数据时由Netty创建Buffer,write时会拿到这个Buffer写到网络中(outBoundHandler处理了write()操作并丢弃了数据,而read()需要继续往下一个Handler传递,所以没有相关处理)

可能产生内存泄露的情况:

  1. 没有调用相关的fireChannelRead方法,也不释放Buffer
  2. 执行channelRead方法抛出异常导致fireChannelRead方法未执行,同时也不释放Buffer

正常执行fireChannelRead方法入站往后传递Buffer,Netty都会释放Buffer,Netty提供了SimpleChannelInboundHandler类来支持这种情况,继承SimpleChannelInboundHandler类,重写channelRead0方法,即使不抛出异常,也不调用fireChannelRead方法,最终Netty也会帮我们做Buffer的释放

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {boolean release = true;try {if (acceptInboundMessage(msg)) {@SuppressWarnings("unchecked")I imsg = (I) msg;channelRead0(ctx, imsg);} else {release = false;ctx.fireChannelRead(msg);}} finally {if (autoRelease && release) {ReferenceCountUtil.release(msg);}}
}/*** Is called for each message of type {@link I}.** @param ctx           the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}*                      belongs to* @param msg           the message to handle* @throws Exception    is thrown if an error occurred*/
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

二、Netty内置通信传输模式

  1. NIO:io.netty.channel.socket.nio 使用java.nio.channels包作为基础——基于选择器的方式
  2. Epoll:io.netty.channel.epoll 由JNI驱动的epoll()和非阻塞 IO。这个传输支持只有在Linux上可用的多种特性,如SO_REUSEPORT,比NIO传输更快,而且是完全非阻塞的。将NioEventLoopGroup替换为EpollEventLoopGroup,并且将NioServerSocketChannel.class替换为EpollServerSocketChannel.class即可。
  3. OIO:io.netty.channel.socket.oio 使用java.net包作为基础——使用阻塞流
  4. Local:io.netty.channel.local 可以在VM内部通过管道进行通信的本地传输
  5. Embedded:io.netty.channel.embedded Embedded 传输,允许使用ChannelHandler而又不需要一个真正的基于网络的传输。在测试ChannelHandler实现时非常有用

三、引导Bootstrap

ServerBootstrap将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap则是由想要连接到远程节点的客户端应用程序所使用的。

引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootstrap则需要两个(也可以是同一个实例)

@Override
public ServerBootstrap group(EventLoopGroup group) {return group(group, group);
}/*** Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These* {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and* {@link Channel}'s.*/
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {super.group(parentGroup);if (this.childGroup != null) {throw new IllegalStateException("childGroup set already");}this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");return this;
}

与ServerChannel相关联的EventLoopGroup将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup就会给它的Channel分配 一个EventLoop。

四、ChannelInitializer

ChannelInitializer是ChannelInboundHandlerAdapter的子类,当中的initChannel()方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。

/*** This method will be called once the {@link Channel} was registered. After the method returns this instance* will be removed from the {@link ChannelPipeline} of the {@link Channel}.** @param ch            the {@link Channel} which was registered.* @throws Exception    is thrown if an error occurs. In that case it will be handled by*                      {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close*                      the {@link Channel}.*/
protected abstract void initChannel(C ch) throws Exception;@SuppressWarnings("unchecked")
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {if (initMap.add(ctx)) { // Guard against re-entrance.try {initChannel((C) ctx.channel());} catch (Throwable cause) {// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).// We do so to prevent multiple calls to initChannel(...).exceptionCaught(ctx, cause);} finally {ChannelPipeline pipeline = ctx.pipeline();if (pipeline.context(this) != null) {pipeline.remove(this);}}return true;}return false;
}

五、ChannelOption

  1. ChannelOption.SO_BACKLOG:对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,从Linux2.2开始,backlog的参数行为在Linux2.2中发生了变化,现在它指定等待接受的完全建立的套接字的队列长度,而不是不完整的连接请求的数量
  2. ChannelOption.SO_REUSEADDR:对应于套接字选项中的SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口
  3. ChannelOption.SO_KEEPALIVE:对应于套接字选项中的 SO_KEEPALIVE,如果在两小时内没有数据的通信时,TCP会自动发送一 个活动探测数据报文,确定连接状态
  4. ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF:对应于套接字选项中的 SO_SNDBUF和 SO_RCVBUF,这两个参数用于操作接 收缓冲区和发送缓冲区的大小
  5. ChannelOption.SO_LINGER: 对应于套接字选项中的 SO_LINGER,保证TCP四次回收最后一次发送close()时数据传输完毕
  6. ChannelOption.TCP_NODELAY:对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关,Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

六、ByteBuf

ByteBuf 维护了两个不同的索引,名称以 read 或者 write 开头的 ByteBuf 方法,将会 推进其对应的索引,而名称以 set 或者 get 开头的操作则不会。 如果打算读取字节直到 readerIndex 达到和 writerIndex 同样的值时会发生什么。在那 时,你将会到达“可以读取的”数据的末尾。就如同试图读取超出数组末尾的数据一样,试 图读取超出该点的数据将会触发一个 IndexOutOf-BoundsException。 可以指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触发 一个异常。(默认的限制是 Integer.MAX_VALUE。)

七、粘包/半包问题

1、TCP粘包/半包发生的原因

分包产生的原因就简单的多:就是一个数据包被分成了多次接收。 更具体的原因至少包括:

  1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
  2. 进行 MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的 数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度。

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节 数是不确定的,故可能存在以下 4 种情况。

  1. 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
  2. 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
  3. 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
  4. 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第 二次读取到了D1包的剩余内容D1_2和D2包的整包。

如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

2、解决TCP粘包/半包问题

由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重 组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案, 可以归纳如下。

  1. 在包尾增加分割符,比如回车换行符进行分割,例如 FTP 协议; 参见 cn.tuling.nettybasic.splicing.linebase(回车换行符进行分割)和 cn.tuling.nettybasic.splicing.delimiter(自定义分割符)下的代码
  2. 消息定长,例如每个报文的大小为固定长度 200 字节,如果不够,空位补空格; 参见 cn.tuling.nettybasic.splicing.fixed 下的代码
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度) 的字段,通常设计思路为消息头的第一个字段使用 int32 来表示消息的总长度,使用 LengthFieldBasedFrameDecoder

4、分析channelRead和channelReadComplete

  • Netty是在读到完整的业务请求报文后才调用一次业务ChannelHandler的channelRead方法
  • 如果一个业务消息被TCP协议栈发送了N次,则服务端的channelReadComplete方法就会被调用N次。

九、编码器和解码器

  • 将字节解码为消息——ByteToMessageDecoder
  • 将一种消息类型解码为另一种——MessageToMessageDecoder。

十、序列化问题

1、Java序列化的缺点

  1. 无法跨语言
  2. 序列化后的码流太大
  3. 序列化性能太低

2、序列化框架

具体可以参考:几种Java常用序列化框架的选型与对比-阿里云开发者社区

相关文章:

Netty第三部

继续Netty第二部的内容 一、ChannelHandler 1、ChannelHandler接口 ChannelHandler是Netty的主要组件,处理所有的入站和出站数据的应用程序逻辑的容器,可以应用在数据的格式转换、异常处理、数据报文统计等 继承ChannelHandler的两个子接口&#xff…...

【C++入门篇】保姆级教程篇【下】

目录 一、运算符重载 1)比较、赋值运算符重载 2) 流插入留提取运算符重载 二、剩下的默认成员函数 1)赋值运算符重载 2)const成员函数 3)取地址及const取地址操作符重载 三、再谈构造函数 1)初始化列表 …...

CCLink转Modbus TCP网关_CCLINK参数配置

CCLink转Modbus TCP网关(XD-ETHCL20),具有CCLINK主从站功能。主要用途是将各种MODBUS-TCP设备接入到CCLINK总线中。它可以作为从站连接到CCLINK总线上,也可以作为主站或从站连接到MODBUS-MTP总线上。 1、 配置网关的CCLINK参数&am…...

一文2000字从0到1使用压测神器JMeter进行压力测试!

概 述 Apache JMeter 是 Apache组织开发的基于 Java的压力测试工具。用于对软件做压力测试,它最初被设计用于 Web应用测试但后来扩展到其他测试领域。它可以用于测试静态和动态资源例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库, FTP 服…...

极狐GitLab CI 助力 .Net 项目研发效率和质量双提升

目录 .NET nuget 自动生成测试包(prerelease)版本号 .NET 版本号规范 持续集成自动打包 持续集成自动修改版本号 .NET 行级增量代码规范——拯救老项目 本地全量代码规范 行级增量代码规范 很多团队或开发者都会使用 C#、VB 等语言开发 .Net 应用…...

[协程]生成器协程调度器的实现-未完

本章内容的三个层次...

Git之分支与版本->课程目标及知识点的应用场景,分支的场景应用,标签的场景应用

1.课程目标及知识点的应用场景 Git分支和标签的命名规范 分支 dev/test/pre/pro(即master) dev:开发环境--windows (自己的电脑) test:测试环境--windows/linux (公司专门的测试电脑 pre:灰度环境(非常大的公司非常重要的项目) pro:正式环境 灰度环境与正式环境的服务器配置…...

PHP正则提取或替换img标记属性

<?php/*PHP正则提取图片img标记中的任意属性*/ $str <center><img src"/uploads/images/20100516000.jpg" height"120" width"120"><br />PHP正则提取或更改图片img标记中的任意属性</center>;//1、取整个图片代码…...

Git 命令行使用指南

Git 命令行使用指南 第一部分&#xff1a;配置 Git 1.1 设置用户信息1.2 配置换行符处理 第二部分&#xff1a;创建和配置仓库 2.1 初始化仓库2.2 克隆仓库2.3 递归克隆2.4 深度克隆 第三部分&#xff1a;基本操作 3.1 添加文件3.2 提交更改3.3 查看状态和提交历史3.4 创建和切…...

Spring 常见面试题

1、Spring概述 1.1、Spring是什么? Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。这些功能的底层都依赖于它的两个核心特性&#xff0c;也就是…...

caffe搭建squeezenet网络的整套工程

之前用pytorch构建了squeezenet&#xff0c;个人觉得pytorch是最好用的&#xff0c;但是有的工程就是需要caffe结构的&#xff0c;所以本篇也用caffe构建一个squeezenet网络。 数据处理 首先要对数据进行处理&#xff0c;跟pytorch不同&#xff0c;pytorch读取数据只需要给数据…...

【OWT】梳理构建的webrtc和owt mfc工程

梳理构建的webrtc和owt mfc工程M98 + owtp2p : 发现最终基于m98的owt也可以直接跑通 【owt】p2p client mfc 工程梳理 服务端使用github版本。 本地运行调试即可。 M98 VS2017 构建 :只构建了m98的webrtc.lib 【webrtc】vs2017 重新构建m98 G:\webrtc_m98_yjf\src webrtc本身…...

02 powershell服务器远程执行命令

一、获取服务器登录凭证 $Username myft\xngrq $PWD 123!# #将密码加密成特殊的字符串对象 $pass ConvertTo-SecureString -AsPlainText $PWD -Force #创建一个登录凭证对象 $Cred New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass …...

LeetCode257. Binary Tree Paths

文章目录 一、题目二、题解 一、题目 Given the root of a binary tree, return all root-to-leaf paths in any order. A leaf is a node with no children. Example 1: Input: root [1,2,3,null,5] Output: [“1->2->5”,“1->3”] Example 2: Input: root […...

Linux下MSSQL (SQL Server)数据库无法启动故障处理

有同事反馈一套CentOS7下的mssql server2017无法启动需要我帮忙看看&#xff0c;启动报错情况如下 检查日志并没有更新日志信息 乍一看mssql-server服务有问题&#xff0c;检查mssql也确实没有进程 既然服务有问题&#xff0c;那么我们用一种方式直接手工后台启动mssql引擎来…...

2311极语言高亮说明书

入门 安装目录下Sec.exe为ide.Sc为编译器. .sec为单文件二进制源码结构,.SEC和.极为多文件文本结构,命令行:cmd Sc.exe 源码路径. 基础 整数变量也可以是万能指针,传送参数,参数只有整数和小数两种. 可在名称前面加或&符号取变量或函数名指针地址,文本变量只取地址不用加…...

金蝶云星空与金蝶云星空对接集成盘亏单查询打通盘亏单新增

金蝶云星空与金蝶云星空对接集成盘亏单查询打通盘亏单新增 接通系统&#xff1a;金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&am…...

深入理解 Django 信号机制

Django 信号&#xff08;signals&#xff09;是一种实现解耦的有力工具&#xff0c;它允许某些发生的事件通知其他部分的代码。信号主要用于在 Django 应用中的不同部分之间传递信息&#xff0c;尤其是在模型操作发生时。本文将深入探讨 Django 信号的工作原理、如何定义和接收…...

uniapp开发app应用从创建到上架

目录 前言 一、项目初始化 1.初始化方式 2.账号注册 3.插件安装 二、项目结构及重点文件介绍 1.项目基本结构 2.项目文件介绍 三、应用打包 1. 安卓打包 2.苹果打包 四、应用发布 1. 安卓市场发布 用户权限和隐私政策 注销 软著和App备案证书 2. 苹果市场发布 …...

为什么使用Golang而非Rust开发桌面应用?

MoonGuard 团队选择 Golang 而不是 Rust 作为他们的 Krater 桌面应用程序&#xff0c;因为 Golang 中更容易进行内存管理、类型安全和 ORM 支持。 使用 Rust 和 Tauri 时面临的一些挑战包括&#xff1a; 难以理解 Rust 的所有权和借用规则、其严格的类型安全有时会限制开发速…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...