Netty详解-02

Netty详解-02

Netty详解-01

先随便聊聊

其实是我遇到的很多疑问的总结:

要netty干嘛?

就一个在线聊天平台,通过springboot + websocket可以快速开发

(在我的其他文章里有Websocket讲解和案例哦)

但他会有很多缺点:

  • 高并发扛不住
  • 延迟高
  • 无法实现复杂需求(清理僵尸用户等)
  • 消息同步困难(分布式)

这些是因为默认用到了tomcat,底层虽然也是nio,但tomcat只提供了WebSocket的基础通信能力

因此就要用netty来开发

tomcat和netty都基于nio,不是tomcat基于netty

后端项目中可让tomcat处理传统http请求,netty处理websocket长连接

Websocket是网络应用层协议(和http一层)

不是说netty可以 替代 Websocket实现在线聊天,而是用netty 结合 Websocket可以让效果更好

netty要实现长连接基于Websocket (不过WebSocket只是Netty支持的其中一种长连接方案,并非只有Websocket才能实现长连接)

长连接的本质是TCP连接不主动关闭

netty可以基于TCP自定义长连接,更底层

请求与连接

注意请求和连接的区别:

连接:本质是TCP连接(如HTTP短连接、WebSocket长连接),是客户端与服务端之间的通信通道(对应 Netty 的Channel

请求:是连接上传输的业务数据单元(如HTTP的一次GET/POST、WebSocket的一条聊天消息)

要发送请求→ 建立连接→ 传输请求(因请求诞生连接,连接上跑请求)

连接由IO线程管理,分发给工作线程处理

IO线程少(CPU 核心 ×2)

工作线程多(20~100 个,按需调整)

TCP是面向连接、保证可靠 传输但实时性稍低的协议,UDP是无连接、不保证可靠传输但实时性极高的协议

IO线程管连接,连接(http短连接(跑一次请求),websocket长连接(跑n次请求))上跑请求

工作线程管业务

IO线程调用工作线程,前者不阻塞,后者容易阻塞

请求传输流程

  1. 前端通常通过axios发送请求,axios基于(封装)XHR(XmlHttpRequest)
  2. XHR包装请求数据(请求路径、请求头、请求体)
  3. 浏览器 通过Http协议 将请求数据转为Http请求报文(二进制,包含请求行、请求头、请求体)
  4. 浏览器调用操作系统 的网络模块,让TCP给Http报文加上TCP头并传给电脑网卡
  5. 网络层模块给这个TCP段(TCP头+Http报文)添加IP头
  6. 网卡 将二进制数据转为电信号,并通过网线传输
  7. 到达路由器,路由器解析目标IP,判断是否要发到外网
  8. 若需要,路由器则将电信号传给光猫(光纤宽带)
  9. 光猫将电信号转为光信号并传给外部(小区分光箱、城域网、互联网骨干网(国家级网络高速路))
  10. 互联网骨干网根据目标IP将光信号不断转发到对应机房
  11. 机房光模块将光信号转回电信号 ,再传回网卡
  12. 网卡将电信号再转为二进制数据 交给操作系统
  13. 操作系统根据TCP头中的目标端口(比如 8080),把二进制数据交给正在监听该端口的应用层服务器(Tomcat/Netty)
  14. Tomcat/Netty解析二进制数据中的Http报文,提取出请求路径、参数、请求体,分发到对应的后端业务代码(Controller/Servlet)
  15. 业务代码处理请求(查库、逻辑计算),生成响应数据
  16. Tomcat/Netty把响应数据包装成Http响应报文(二进制),交给服务器操作系统 + TCP
  17. 响应报文通过原物理链路(机房→骨干网→光猫→路由器→客户端网卡)转回电信号,再转为二进制数据
  18. 客户端操作系统 + TCP 把二进制数据交给浏览器,浏览器解析Http响应报文,提取响应体
  19. 浏览器通过XHR把响应体回传给axios,axios转换为JSON后,交给前端代码处理(比如渲染页面)

前端代码 -> 浏览器 + Http -> 客户端操作系统 + TCP/IP -> 网卡 -> 路由器 -> 光猫 -> 外部网络 -> 机房 -> 网卡 -> 服务端操作系统 -> 服务器(netty)-> 后端代码 -> 服务端操作系统 -> 原物理链路 -> 客户端操作系统 -> 浏览器 -> 前端代码(axios)

  1. 应用层(HTTP):包装请求数据→HTTP报文(二进制)
  2. 传输层(TCP):给HTTP报文加TCP头(含目标端口、源端口等)→TCP段
  3. 网络层(IP):给TCP 段加IP头(含目标IP、源IP等)→IP数据报
  4. 数据链路层(网卡):给IP数据报加帧头→数据帧,转为电信号 / 光信号传输

Netty总体流程结构:!!!

重点:

  1. 配置(IO线程池、Channel、Pipeline(Handler))、启动

    Bootstrap(一个EventLoopGroup(WorkerGroup))配置客户端

    ServerBootstrap(两个EventLoopGroup(BossGroup(接收连接)、WorkerGroup(处理IO)))配置服务器

    EventLoop就是IO线程 + Selectot + 任务队列(存放轻量的非IO任务),EventLoopGroup是EventLoop集合

  2. 一个IO线程对应一个Selector(通常1:1,也可以1:n)

  3. 一个Selector监控多个Channel

  4. 每个Channel绑定Pipline

  5. 每个Pipline对应多个Handler(Pipeline是Handler的双向链表)

  6. Buffer存放具体数据,通过Channel发送

  7. 连接到来时,BossGroup中的IO线程建立连接,并将Channel注册到WorkGroup中的某个IO线程的Selector下

  8. 当有Channel就绪时(连接建立、可读(来数据)、可写(有对象))(就绪不代表Buffer有数据,不要局限)

  9. Selector告知IO线程(WorkGroup)

  10. IO线程调用对应Channel的Pipline,依次执行所有Handler

  11. IO线程自己执行轻量级操作(解码、编码)

  12. IO线程将业务逻辑Handler交给工作线程池(非IO线程)处理

  13. 处理结果(对象)在服务器一侧编码为二进制字节流,再存入Buffer(例如ByteBuf),通过Channel传给客户端

  14. 客户端(浏览器Http客户端 或者 netty客户端)一侧重复8~13(netty客户端并非刚需,只有我们不想使用浏览器这种情况时,才自定义客户端,netty客户端和浏览器作用相同)

在开始下面的详解前,一定要先自己了解一下nio、netty核心组件(我的另一篇文章有哦!)

然后好好理清楚上面13条的流程结构,我觉得一定一定对你有所帮助!!!

NIO详解

netty底层就是nio,所以,得学

以前java基础中的io操作中,FileInputStream这种就是BIO(阻塞的)

其他基本概念可查看Netty详解-01

三大组件

Channel

Channel是双向通道,FileInputStream这种是单向的

常见:

  • FileChannel
  • SocketChannel
  • ServcerSocketChannel
Buffer

Buffer负责临时存储输入Channel或者从Channel输出的数据

常见:

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • IntBuffer
  • CharBuffer
Selector

服务器设计演化:

  1. 单线程版本:所有连接共用一个线程(完全不并发)

  2. 多线程版本:每个连接对应一个线程(无节制创建线程)

  3. 线程池版本:复用线程

  4. selector版本:多路复用优化

    selector充当监控的作用,一个selector监控多个channel,只要有channel传来了数据,selector将其交给IO线程处理

  5. 主从Reactor版本

接下来根据nio中常用组件进行讲解

注意:

nio中buffer、channel都有很多种,但是在netty使用中不用特别关心用哪种(你也可以直接去看Netty详解

关于Buffer:Netty只用ByteBuf,且默认选好了实现

关于Channel:Netty按场景自动绑定

服务端用ServerBootstrap配置时,指定NioServerSocketChannel

客户端:用Bootstrap配置时,指定NioSocketChannel

后续Netty详解 会给出一个netty案例,到时候你可以再来这里回味一下

NIO示例

曾经的FileInputStream(BIO):

java 复制代码
public class FileInputStreamDemo {
    public static void main(String[] args) {
        String filePath = "test.txt";
        byte[] buf = new byte[1024]; // 缓冲区大小(1KB,可根据文件大小调整,如 4096)
        int readLen; // 实际读取的字节数

        try (FileInputStream fis = new FileInputStream(filePath)) {
            // 循环读取:每次最多读 1024 字节,存入 buf
            while ((readLen = fis.read(buf)) != -1) {
                // 字节数组转字符串(参数:数组、起始索引、实际长度,避免读取到缓冲区残留数据)
                System.out.print(new String(buf, 0, readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

基于ByteBuffer + FileChannel(NIO)

java 复制代码
public class ByteBuffer_demo {
    public static void main(String[] args) {
        try(FileChannel channel = new FileInputStream("data.txt").getChannel()){
            ByteBuffer buffer = ByteBuffer.allocate(10);
            int len;
            while ((len = channel.read(buffer)) != -1){
                System.out.println("read " + len + " bytes");
                buffer.flip();
                while (buffer.hasRemaining()){
                    byte b = buffer.get();
                    System.out.println((char)b);
                }
                buffer.clear();
                
//                // 直接把缓冲区有效字节转成字符串(无需逐个字节读)
//                System.out.print(new String(buffer.array(), 0, len, "UTF-8"));
//                buffer.clear(); // 切换回写模式
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

对比这两段代码,你会感觉到基础文件操作中,BIO、NIO操作类似,只是后者多加了几个步骤(可控事件更多)

维度 BIO(FileInputStream) NIO(FileChannel+ByteBuffer)
核心模型 「流模型」:数据是 "流动的字节流",只能 "从头到尾读",不能回退、不能跳着读 「块模型」:数据是 "块级存储",缓冲区支持回退(position--)、随机访问(position(100))、部分读取
控制权 黑盒封装:底层缓冲区、数据拷贝、读写切换都由框架处理,你只能 "被动接收数据" 显式掌控:缓冲区的读写模式、读取位置、剩余数据都由你控制,能 "主动操作数据"
额外能力(复杂场景) 几乎没有:无法处理跨缓冲区数据、无法随机访问、无法避免二次拷贝 核心能力:支持随机访问(channel.position(100) 直接读第 100 字节)、跨缓冲区数据解析、直接缓冲区(避免二次拷贝)、非阻塞读取(网络 I/O 场景)
适用场景 简单需求:小文件读取、文本打印、简单复制(不需要复杂字节操作) 复杂需求:大文件处理、自定义协议解析、随机访问文件、网络高并发(和 Netty 等框架配合)

简单来说,BIO这套有很多是框架定好的,NIO允许我们做更加底层丰富的定义

例如,上面我们只是定义了ByteBuffer的大小,但还可以通过 ByteBuffer 的 API 实现位置控制、范围限制、数据压缩、批量读写、直接缓冲区等复杂操作

ByteBuffer概念

FileChannel概念

NIO具体代码后面Netty详解-03再讲吧

现在感觉理清楚概念、组件顺序就好了

Netty详解

就先来一点开胃小菜吧

理不清楚时一定要再去看之前的执行流程!!!

算了我直接粘过来吧:

netty服务端编码:

java 复制代码
public class HelloServer {
    public static void main(String[] args) {
        // 启动器
        new ServerBootstrap()
                // 创建线程组(理论上需要:BossEventLoop、WorkerEventLoop用于分配处理连接和处理IO的线程)
                // 此处只创建BossEventLoop
                // NioEventLoopGroup:多个NioEventLoop线程
                // NioEventLoop:是一个IO线程+Selector+任务队列
                .group(new NioEventLoopGroup())
                // 选择服务器的ServerSocketChannel实现
                .channel(NioServerSocketChannel.class)
                // 添加处理器
                .childHandler(
                        // 创建一个通道初始化对象
                    new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 通道绑定Pipline(处理器链)
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            //父类方法public void channelRead(ChannelHandlerContext ctx, Object msg)
                            //因此不能用protected void channelRead(ChannelHandlerContext ctx, Object msg)
                            //子类不能比父类还保守
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8080);
    }
}

netty客户端编码:

java 复制代码
public class HelloClient {
    public static void main(String[] args) throws InterruptedException{
//        IO线程 -> Selector -> channel -> Pipline
        // 启动器
        new Bootstrap()
                // 线程组EventLoop
                .group(new NioEventLoopGroup())
                // 通道Channel
                .channel(NioSocketChannel.class)
                // 处理器链Pipline
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                    }
                })
                .connect(new InetSocketAddress("localhost", 8080))
                .sync()
                .channel()
                .writeAndFlush("hello world");
    }
}

可以先看看这两段代码

重点理清楚使用时,我们要:

IO线程 -> Selector -> channel -> Pipline

其中IO线程 + Selectot 属于EventLoop

因此:

复制代码
   ch.pipeline().addLast(new StringDecoder());
                }
            })
            .connect(new InetSocketAddress("localhost", 8080))
            .sync()
            .channel()
            .writeAndFlush("hello world");
}

}

复制代码
可以先看看这两段代码

重点理清楚使用时,我们要:

IO线程 -> Selector -> channel -> Pipline

其中IO线程 + Selectot 属于EventLoop

因此:

**EventLoop -> Channel -> Pipline**
相关推荐
跟着珅聪学java2 小时前
HttpServletRequest中的 Attribute(属性)生命周期和作用域是 Java Web 开发中的重要概念
java
m0_495562783 小时前
Swift-static和class
java·服务器·swift
Chief_fly3 小时前
RestTemplate 和 Apache HttpClient 实现 HTTP 请求
网络协议·http·apache
合作小小程序员小小店3 小时前
web网页开发,在线物流管理系统,基于Idea,html,css,jQuery,jsp,java,SSM,mysql
java·前端·后端·spring·intellij-idea·web
qq_356531453 小时前
浏览器访问web服务器经过了哪些过程
网络协议
这周也會开心4 小时前
SpringMVC整理
java·springmvc
東雪木4 小时前
Spring Boot 2.x 集成 Knife4j (OpenAPI 3) 完整操作指南
java·spring boot·后端·swagger·knife4j·java异常处理
数学难4 小时前
Java面试题2:Java线程池原理
java·开发语言
Charles_go4 小时前
C#8、有哪些访问修饰符
java·前端·c#