《Netty权威指南》- 读书笔记

本文详细介绍了Java的I/O演进,重点讲解了NIO模型和Netty框架。讨论了TCP粘包/拆包问题及其原因,并展示了Netty如何解决这一问题。还探讨了Netty的ByteBuf、ChannelPipeline、EventLoopGroup等关键组件的原理与应用。
摘要由CSDN通过智能技术生成

== 基础篇 走进JavaNIO==

第一章 java的I/O演进之路

1.1 I/O基础入门

Java1.4之前对I/O的支持并不完善,给开发带来的问题有:

  • 没有数据缓冲区,I/O性能存在问题
  • 没有C/C++中channel的概念,只有输入输出流
  • BIO会导致通信被长时间阻塞
  • 支持的字符集优先,硬件移植性不好

1.1.1 linux网络I/O模型简介

linux中所有的外部设备都是一个文件,socket也是一个文件,有文件描述符(fd)指向它。
UNIX提供5中I/O模型:

  • BIO模型:在进程空间调用recvfrom,直到有数据才返回。
  • NIO模型:轮训调用recvfrom。
  • I/O复用:linux提供select/poll,其支持多个fd的NIO,但是select/poll本身是阻塞的。epoll采用事件驱动的方式代替顺序扫描,其性能更高。
  • 信号驱动I/O模型:
  • 异步I/O:通知内核某个操作,并整个操作完成的时候通知我们。

I/O多路复用技术

epoll优点:

  • 支持一个进程打开的fd不受限制
  • IO效率不会随着fd数量增加而下降
  • 使用mmap加速内核和用户空间的消息传递
  • epoll API更加简单

1.2 Java的IO演进

2011年JDK7发布:

  • 提供了能批量获取文件属性的API
  • 提供AIO功能

第二章 NIO入门

2.1 传统的BIO编程

CLient/Server模型中,Server负责绑定IP,启动监听端口;Client发起链接请求,经过三次握手建立连接,通过输入输出流进行同步阻塞式通信。

2.1.1 BIO通信模型图

通过Acceptor处理多个client连接请求,处理完成后销毁线程。

该模型的问题就是支持的线程数量有限。

2.2 伪异步IO模型

伪异步是为了解决BIO一个链路需要一个线程的问题。
通过一个线程池处理多个客户端的请求接入

  • 线程的数量不会大量膨胀导致资源耗尽
  • 问题是:没有解决同步IO导致的线程阻塞问题

2.3 NIO模型

2.3.1 NIO简介

缓冲区buf
本质是一个字节数组(ByteBuff),同时提供数据的结构化访问以及维护读写位置。

通道 channel
channel是全双工的。
流是单向的。
多路复用 selector
selector简单来说就是轮训注册在其上的channel,

2.3.2 NIO服务序列图

2.4 AIO

== 入门篇 Netty NIO开发指南==

第三章 Netty入门应用

第四章 TCP粘包/拆包问题的解决之道

4.1 TCP粘包/拆包

4.1.1 TCP粘包/拆包问题说明

TCP协议是”流“协议,流是没有间隔的。tcp会根据缓存大小将业务上的大包划分成多个小包发送出去、也可能多个小包合成一个大包发送出去。

4.1.2 TCP粘包/拆包发生的原因

  • 应用层:大于套接字接口缓冲区大小
  • TCP层:MSS
  • IP层:MTU

4.1.3 TCP粘包/拆包问题的解决策略

  • 消息定长len,例如每个报文固定200字节。那么读取到定长len后就重置计数器开始读取下一个包。
  • 包尾加换行符分割,如ftp。
  • 消息头+消息体。消息头包含消息长度信息。
  • 更复杂的应用协议,如netty.

4.3 Netty解决tcp粘包问题

  • LineBasedFrameDecoder:原理是遍历ByteBuf中字节,以换行符分割
  • StringDecoder:将接收的byte对象转换为字符串,然后调用后面的handler
    如果发送的消息不是以换行符结束的,netty也有其他解码器支持。

第五章 分隔符和定长解码器的应用

5.1 DelimiterBasedFrameDecoder

支持任意字符为分隔符
支持设置单条消息最大长度,如果找了最大长度还没找到分隔符就抛出异常

5.2 FixedLengthFrameDecoder

使用简单,指定包长渡就ok。
== 中门篇 Netty 编解码开发指南==

第六章 编解码技术

6.1Java序列化缺点

  • 不支持跨语言
  • 序列化后的码流太大
  • 序列化性能低

6.2 业界主流的编解码框架

  • Google 的protobuf
  • Facebook的Thrift
  • JBoss的Marshalling

第七章 Java序列化

可以用Netty提供的ObjectEncoder编码器和ObjectDecoder解码器实现对普通对象的序列化。
而且不存在粘包问题。

第八章 Google Protobuf编解码

8.1 入门

protobuf支持跨语言使用。

8.3 注意事项

protobufDecoder仅仅负责解码,不支解决持粘包问题。因此,在protobufDecoder之前要有能解决粘包问题的解码器,有三种选择:

  • Netty提供的ProtobuffVarint32FrameDecoder
  • Netty提供的通用粘包解码器LengthFieldBasedFrameDecoder
  • 继承ByteToMessageDecoder类,自己处理粘包问题

第九章 JBoss Marshalling编解码

== 高级篇 Netty 多协议开发和应用==

第十章 Http协议开发应用

10.1 Http协议特点

  • 支持client/server模式
  • 简单 - 指定URL和参数就可以访问
  • 灵活 - 支持传输任意类型的对象
  • 无状态 - 无状态协议
    HttpRequest包含三部分:HTTP请求行、HTTP消息头、HTTP请求正文。
    如:GET /netty5 HTTP/1.1
    HttpResponse包含三部分:状态行、消息报头、相应正文。

第十一章 WebSocket协议开发

第十二章 UDP协议开发

第十三章 文件传输

第十四章 私有协议栈开发

Netty 源码分析

第十五章 ByteBuf和相关辅助类

JDK NIO也有ByteBuffer,其缺点有:

  • 不支持动态扩容缩容
  • 只有一个position指针,调用者需要手工调用flip()和rewind(),给使用者带来难度和出错的机会
  • API支持有限
    基于这些,Netty提供了自己的Bytebuffer实现-ByteBuf

15.1 ByteBuf

ByteBuf工作原理

  • 通过两个指针来操作读写,readerIndex和writerIndex
  • 支持动态扩容
    Discardable bytes
    缓冲区的分配和释放是个耗时的操作
    缓冲区的动态扩张需要字节复制,它也是个耗时的操作
    因此,为提高性能,要最大程度提高缓冲区的重用率。

    可调用discardReadBytes来回收已经读过的内存,但是会发生内存复制,所以频繁调用会导致性能下降。

15.2 ByteBuf源码分析

继承关系

从内存分配的角度看,byteBuf分为两类:

  • 堆内存缓冲区:优点是内存的分配和回收快。缺点是进行IO读写时需要一次内存复制,用户空间和内核空间的复制。
  • 直接内存缓冲区:优缺点和堆内存缓冲区整好相反。
    经验表明ByteBuf的最佳实践是在IO通信线程的读写缓冲区使用DirectByteBuf,后端业务的编解码模块使用HeapByteBuf,这样的组合可以达到性能最优。
    从内存回收的角度看、ByteBuf分两类,基于对象池的ByteBuf和普通ByteBuf。使用内存池后的Netty在高并发和高负载环境下内存和GC更加平稳。
    15.2.1 AbstractByteBuf源码分析
    实现ByteBuf的一些公共属性和功能。
    主要成员变量
    static final ResourceLeakDetector<ByteBuf> leakDetector =
            ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);//用于对象是否泄漏,定义为static,意味着所有byteBuf共享

    int readerIndex;//读索引
    int writerIndex;//写索引
    private int markedReaderIndex;//读mark
    private int markedWriterIndex;//写mark
    private int maxCapacity;//最大容量

Byte数组不在这里,因为AbstractByteBuf无法确定使用直接内存还是堆内存。
readBytes(ByteBuf dst, int dstIndex, int length)

public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
   
       checkReadableBytes(length);//校验可读性
       getBytes(readerIndex, dst, dstIndex, length);//读取。从readerIndex开始读取length个字节到目标数组中
       readerIndex += length;//移动读指针
       return this;
   }

再看一下checkReadableBytes():

/**
     * Throws an {@link IndexOutOfBoundsException} if the current
     * {@linkplain #readableBytes() readable bytes} of this buffer is less
     * than the specified value.
     */
    protected final void checkReadableBytes(int minimumReadableBytes) {
   
        if (minimumReadableBytes < 0) {
   
            throw new IllegalArgumentException("minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)");
        }
        checkReadableBytes0(minimumReadableBytes);
    }
    
private void checkReadableBytes0(int minimumReadableBytes) {
   
        ensureAccessible();
        if (readerIndex > writerIndex - minimumReadableBytes) {
   
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                    readerIndex, minimumReadableBytes, writerIndex, this));
        }
    }

writeBytes(byte[] src, int srcIndex, int length)

public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
   
        ensureWritable(length);//可写校验和扩容
        setBytes(writerIndex, src, srcIndex, length);//从writerIndex开始写length长度
        writerIndex += length;//移动写指针
        return this;
    }

ensureWritable(int minWritableBytes)

public ByteBuf ensureWritable(int minWritableBytes) {
   
        if (minWritableBytes < 0) {
   
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }
        ensureWritable0(minWritableBytes);
        return this;
    }
    
final void ensureWritable0(int minWritableBytes) {
   
        ensureAccessible();//检查这个buf是否还有引用(如果已经没有引用那就没必要在写了)
        if (minWritableBytes <= writableBytes()) {
   //写入的字节小于可写字节,校验通过
            return;
        }

        if (minWritableBytes > maxCapacity - writerIndex) {
   //写入的字节大于最大可写入字节,抛异常
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);

        // Adjust to the new capacity.
        capacity(newCapacity);
    }

重用缓冲区

public ByteBuf discardReadBytes() {
   
        ensureAccessible();
        if 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值