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

== 基础篇 走进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,直到有数据才返回。
    《Netty权威指南》- 读书笔记_第1张图片
  • NIO模型:轮训调用recvfrom。
    《Netty权威指南》- 读书笔记_第2张图片
  • I/O复用:linux提供select/poll,其支持多个fd的NIO,但是select/poll本身是阻塞的。epoll采用事件驱动的方式代替顺序扫描,其性能更高。
    《Netty权威指南》- 读书笔记_第3张图片
  • 信号驱动I/O模型:
    《Netty权威指南》- 读书笔记_第4张图片
  • 异步I/O:通知内核某个操作,并整个操作完成的时候通知我们。
    《Netty权威指南》- 读书笔记_第5张图片

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连接请求,处理完成后销毁线程。
《Netty权威指南》- 读书笔记_第6张图片
该模型的问题就是支持的线程数量有限。

2.2 伪异步IO模型

伪异步是为了解决BIO一个链路需要一个线程的问题。
通过一个线程池处理多个客户端的请求接入
《Netty权威指南》- 读书笔记_第7张图片

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

2.3 NIO模型

2.3.1 NIO简介

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

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

2.3.2 NIO服务序列图

《Netty权威指南》- 读书笔记_第8张图片

2.4 AIO

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

第三章 Netty入门应用

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

4.1 TCP粘包/拆包

4.1.1 TCP粘包/拆包问题说明

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

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

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

《Netty权威指南》- 读书笔记_第9张图片

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包含三部分:状态行、消息报头、相应正文。
    《Netty权威指南》- 读书笔记_第10张图片

第十一章 WebSocket协议开发

第十二章 UDP协议开发

第十三章 文件传输

第十四章 私有协议栈开发

Netty 源码分析

第十五章 ByteBuf和相关辅助类

JDK NIO也有ByteBuffer,其缺点有:

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

15.1 ByteBuf

ByteBuf工作原理

  • 通过两个指针来操作读写,readerIndex和writerIndex
  • 支持动态扩容
    Discardable bytes
    缓冲区的分配和释放是个耗时的操作
    缓冲区的动态扩张需要字节复制,它也是个耗时的操作
    因此,为提高性能,要最大程度提高缓冲区的重用率。
    《Netty权威指南》- 读书笔记_第11张图片
    可调用discardReadBytes来回收已经读过的内存,但是会发生内存复制,所以频繁调用会导致性能下降。

15.2 ByteBuf源码分析

继承关系
《Netty权威指南》- 读书笔记_第12张图片
从内存分配的角度看,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 (readerIndex == 0) {
            return this;
        }

        if (readerIndex != writerIndex) {
            setBytes(0, this, readerIndex, writerIndex - readerIndex);//复制缓冲区
            writerIndex -= readerIndex;//重置写指针
            adjustMarkers(readerIndex);//调整mark指针
            readerIndex = 0;//重置读指针
        } else {
            adjustMarkers(readerIndex);
            writerIndex = readerIndex = 0;
        }
        return this;
    }

15.2.2 AbstractReferenceCountedByteBuf源码分析

public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {

    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
            AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");//通过原子的方式对成员变量进行更新,消除锁

    private volatile int refCnt;//跟踪对象的引用次数,采用CAS对其自增1,默认值为1
}

15.2.2 UnPooledHeapByteBuf源码分析
非池化的基于堆内存,频繁的大块内存分配和回收会对性能造成影响,但是相比对外内存的申请和释放,成本还是低一些。
相比HeapByteBuf, UnPooledHeapByteBuf的实现更加加单,也不容易出现内存管理方面的问题,因此在满足性能的条件下,推荐使用UnPooledHeapByteBuf。

public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
    byte[] array;//这里直接使用JDK的ByteBuffer也可以,之所以使用Byte数组是因为性能和便捷的位操作
    private ByteBuffer tmpNioBuf;//用于实现将netty的byteBuf转换为JDK的ByteBuffer
 }

转换JDK Buffer
netty基于byte数组实现
jdk的nio buf提供wrap方法,可直接实现 转换
看下转换方法

public ByteBuffer nioBuffer(int index, int length) {
        ensureAccessible();
        return ByteBuffer.wrap(array, index, length).slice();
    }
public ByteBuffer slice() {
        return new HeapByteBuffer(hb,//仍然使用的是原buffer的全局数组,只是改变了position和limit的位置,所以新buf和原buf内容是相互影响的
                                        -1,
                                        0,
                                        this.remaining(),
                                        this.remaining(),
                                        this.position() + offset);
    }

slice方法的作用:copy position到limit之间的内容,
15.2.3 pooledByteBuf内存池原理分析
PoolArena是netty的内存池显现类。
为了集中管理内存,提供内存申请是释放的效率,很多框架会申请一大块内存,提供相应的接口分配和释放内存,这样就不再频繁的使用系统调用来使用内存,可以提高性能。预先申请的那块内存就叫Memory Arena。PoolArena是netty对Memory Arena的实现。
Netty的PoolArena由多个chunk组成,每个chunk由多个Page组成。
PoolArena源码:

abstract class PoolArena<T> implements PoolArenaMetric {
    static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe();

    enum SizeClass {
        Tiny,
        Small,
        Normal
    }

    static final int numTinySubpagePools = 512 >>> 4;

    final PooledByteBufAllocator parent;

    private final int maxOrder;
    final int pageSize;
    final int pageShifts;
    final int chunkSize;
    final int subpageOverflowMask;
    final int numSmallSubpagePools;
    final int directMemoryCacheAlignment;
    final int directMemoryCacheAlignmentMask;
    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools;

    private final PoolChunkList<T> q050;
    private final PoolChunkList<T> q025;
    private final PoolChunkList<T> q000;
    private final PoolChunkList<T> qInit;
    private final PoolChunkList<T> q075;
    private final PoolChunkList<T> q100;
    }

PoolChunk的实现
PoolChunk负责多个Page的内存管理,PoolChunk将其负责的多个Page构建成一棵二叉树。
假设一个chunk由16个page组成,则其组织形式:
《Netty权威指南》- 读书笔记_第13张图片
Page大小是4字节,chunk大小是64字节。
每个节点都记录自己在整个Memory Arena中的偏移地址,一旦被分配,则该节点及其子节点在接下来的内存分配过程中会被忽略。
举例来说,我们申请16个字节空间,则第三层的某个节点会被标记为已分配,则再次分配内存的时候会从其他三个节点中分配。
分配内存时对树采用深度优先算法,但是从哪棵子树开始深度遍历是随机的。
PoolSubPage的实现
申请内存小于一个page,则内存分配在page中完成,每个page会被分为大小相等的多个块。
被分的单位块大小等于第一次申请的内存大小,例如一个Page8字节,第一次申请2字节,则该page被切分成4块,每块2字节。而且这个page以后只能用于分配2字节的内存申请,如果再来一个4字节的内存申请,只能在另一个Page中申请。
Page使用标识位来表示内存块是否可用。维护一个long数组,每个位表示一个块的使用情况。
例如page为128字节,第一次申请内存为1字节,则该page被分为128块,则long数组中有2个元素,(每个long64位,两个long可以表示128位)。0、1表示该块是否可用。
15.2.4 PooledDirectByteBuf内存池原理分析
创建字节缓冲区
由于采用内存池实现,所以创建PooledDirectByteBuf对象不能new一个实例,而是从内存池获取。然后设置引用计数器。

static PooledDirectByteBuf newInstance(int maxCapacity) {
        PooledDirectByteBuf buf = RECYCLER.get();
        buf.reuse(maxCapacity);
        return buf;
    }
final void reuse(int maxCapacity) {
        maxCapacity(maxCapacity);
        setRefCnt(1);
        setIndex0(0, 0);
        discardMarks();
    }

复制字节缓冲区
会从内存池中获取一个新的buffer而不是new一个。

15.3 ByteBuffer相关辅助类

15.3.1 ByteBufHolder
对消息体进行包装和抽象,不同的子类有不同的实现。
实现ByteBufHolder的子类可以自己实现一些实用的方法。
Netty也有一些子类继承自ByteBufHolder。
15.3.2 ByteBufAllocator
字节缓冲区分配器,其实现类有两种:基于池的和普通的。
其API:
《Netty权威指南》- 读书笔记_第14张图片
15.3.3 CompositeByteBuf
允许将多个ByteBuf组装到一起。
使用场景:如某协议包含消息头和消息体,当对消息进行编码的时候需要进行整合。
这种场景有两种实现方式:

  • 将一个buf复制到另一个buf。或者创建一个新的buf将两个buf都放到新的buf。
  • 通过List或其他集合容器,将两个buf都放入容器统一维护和处理。

看下源码:

public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf> {

   private static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer();
   private static final Iterator<ByteBuf> EMPTY_ITERATOR = Collections.<ByteBuf>emptyList().iterator();

   private final ByteBufAllocator alloc;
   private final boolean direct;
   private final ComponentList components;//维护buf的容器
   private final int maxNumComponents;

   private boolean freed;
   }

再看下ComponentList:

private static final class ComponentList extends ArrayList<Component> {

        ComponentList(int initialCapacity) {
            super(initialCapacity);
        }

        // Expose this methods so we not need to create a new subList just to remove a range of elements.
        @Override
        public void removeRange(int fromIndex, int toIndex) {
            super.removeRange(fromIndex, toIndex);
        }
    }

private static final class Component {
        final ByteBuf buf;
        final int length;
        int offset;//在集合中的位置偏移
        int endOffset;

        Component(ByteBuf buf) {
            this.buf = buf;
            length = buf.readableBytes();
        }

        void freeIfNecessary() {
            buf.release(); // We should not get a NPE here. If so, it must be a bug.
        }
    }

15.3.4 ByteBufUtil
几个常用的工具方法:

  • encodeString
  • decodeString
  • hexDump

第十六章 channel和Unsafe

Unsafe是Netty实现的channel的内部接口,设计初衷就是只是内部使用。负责协助netty进行网络读写操作。

16.1 channel功能说明

16.1.1 的工作原理
JDK已经提供了channel,为什么netty要再实现一个呢?主要原因在于:

  • JDK的channel没有提供统一的接口,开发者使用不方便

  • JDk的channel主要职责就是网络IO操作,由于他们是SPI类接口,由具体的虚拟机厂家提供实现类,所以通过集成SPI类来扩展其功能的难度增大。

  • Netty的channel可以和Netty框架融合在一起。
    基于以上4点,netty的channel设计理念有:

  • 在channel接口层,通过外观(facade)模式封装。

  • channel接口的定义功能尽量大而全。

  • 具体实现采用聚合而非包含的方式,更加灵活。
    16.1.2 功能介绍

/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */
package io.netty.channel;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.AttributeMap;

import java.net.InetSocketAddress;
import java.net.SocketAddress;


public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {

   /** * Returns the globally unique identifier of this {@link Channel}. */
   ChannelId id();

   /** * Return the {@link EventLoop} this {@link Channel} was registered to. * 获取当前channel所在的EventLoop * netty的EventLoop不仅仅是处理网络IO和可以处理用户自定义的NioTask */
   EventLoop eventLoop();

   /** * Returns the parent of this channel. * * @return the parent channel. * {@code null} if this channel does not have a parent channel. */
   Channel parent();

   /** * Returns the configuration of this channel. */
   ChannelConfig config();

   /** * Returns {@code true} if the {@link Channel} is open and may get active later */
   boolean isOpen();

   /** * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}. */
   boolean isRegistered();

   /** * Return {@code true} if the {@link Channel} is active and so connected. */
   boolean isActive();

   /** * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}. * 在Netty中每个Channel都对应一个物理连接,每个连接都有自己的TCP链接参数 * 如tcp缓冲区大小、超时时间等 */
   ChannelMetadata metadata();

   /** * Returns the local address where this channel is bound to. The returned * {@link SocketAddress} is supposed to be down-cast into more concrete * type such as {@link InetSocketAddress} to retrieve the detailed * information. * * @return the local address of this channel. * {@code null} if this channel is not bound. */
   SocketAddress localAddress();

   /** * Returns the remote address where this channel is connected to. The * returned {@link SocketAddress} is supposed to be down-cast into more * concrete type such as {@link InetSocketAddress} to retrieve the detailed * information. * * @return the remote address of this channel. * {@code null} if this channel is not connected. * If this channel is not connected but it can receive messages * from arbitrary remote addresses (e.g. {@link DatagramChannel}, * use {@link DatagramPacket#recipient()} to determine * the origination of the received message as this method will * return {@code null}. */
   SocketAddress remoteAddress();

   /** * Returns the {@link ChannelFuture} which will be notified when this * channel is closed. This method always returns the same future instance. */
   ChannelFuture closeFuture();

   /** * Returns {@code true} if and only if the I/O thread will perform the * requested write operation immediately. Any write requests made when * this method returns {@code false} are queued until the I/O thread is * ready to process the queued write requests. */
   boolean isWritable();

   /** * Get how many bytes can be written until {@link #isWritable()} returns {@code false}. * This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0. */
   long bytesBeforeUnwritable();

   /** * Get how many bytes must be drained from underlying buffers until {@link #isWritable()} returns {@code true}. * This quantity will always be non-negative. If {@link #isWritable()} is {@code true} then 0. */
   long bytesBeforeWritable();

   /** * Returns an internal-use-only object that provides unsafe operations. */
   Unsafe unsafe();

   /** * Return the assigned {@link ChannelPipeline}. */
   ChannelPipeline pipeline();

   /** * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. */
   ByteBufAllocator alloc();

   @Override
   Channel read();

   @Override
   Channel flush();

   /** * Unsafe operations that should never be called from user-code. These methods * are only provided to implement the actual transport, and must be invoked from an I/O thread except for the * following methods: * 
    *
  • {@link #localAddress()}
  • *
  • {@link #remoteAddress()}
  • *
  • {@link #closeForcibly()}
  • *
  • {@link #register(EventLoop, ChannelPromise)}
  • *
  • {@link #deregister(ChannelPromise)}
  • *
  • {@link #voidPromise()}
  • *
*/
interface Unsafe { /** * Return the assigned {@link RecvByteBufAllocator.Handle} which will be used to allocate {@link ByteBuf}'s when * receiving data. */ RecvByteBufAllocator.Handle recvBufAllocHandle(); /** * Return the {@link SocketAddress} to which is bound local or * {@code null} if none. */ SocketAddress localAddress(); /** * Return the {@link SocketAddress} to which is bound remote or * {@code null} if none is bound yet. */ SocketAddress remoteAddress(); /** * Register the {@link Channel} of the {@link ChannelPromise} and notify * the {@link ChannelFuture} once the registration was complete. */ void register(EventLoop eventLoop, ChannelPromise promise); /** * Bind the {@link SocketAddress} to the {@link Channel} of the {@link ChannelPromise} and notify * it once its done. */ void bind(SocketAddress localAddress, ChannelPromise promise); /** * Connect the {@link Channel} of the given {@link ChannelFuture} with the given remote {@link SocketAddress}. * If a specific local {@link SocketAddress} should be used it need to be given as argument. Otherwise just * pass {@code null} to it. * * The {@link ChannelPromise} will get notified once the connect operation was complete. */ void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); /** * Disconnect the {@link Channel} of the {@link ChannelFuture} and notify the {@link ChannelPromise} once the * operation was complete. */ void disconnect(ChannelPromise promise); /** * Close the {@link Channel} of the {@link ChannelPromise} and notify the {@link ChannelPromise} once the * operation was complete. */ void close(ChannelPromise promise); /** * Closes the {@link Channel} immediately without firing any events. Probably only useful * when registration attempt failed. */ void closeForcibly(); /** * Deregister the {@link Channel} of the {@link ChannelPromise} from {@link EventLoop} and notify the * {@link ChannelPromise} once the operation was complete. */ void deregister(ChannelPromise promise); /** * Schedules a read operation that fills the inbound buffer of the first {@link ChannelInboundHandler} in the * {@link ChannelPipeline}. If there's already a pending read operation, this method does nothing. */ void beginRead(); /** * Schedules a write operation. */ void write(Object msg, ChannelPromise promise); /** * Flush out all write operations scheduled via {@link #write(Object, ChannelPromise)}. */ void flush(); /** * Return a special ChannelPromise which can be reused and passed to the operations in {@link Unsafe}. * It will never be notified of a success or error and so is only a placeholder for operations * that take a {@link ChannelPromise} as argument but for which you not want to get notified. */ ChannelPromise voidPromise(); /** * Returns the {@link ChannelOutboundBuffer} of the {@link Channel} where the pending write requests are stored. */ ChannelOutboundBuffer outboundBuffer(); } }

16.2 AbstractChannel源码分析

16.2.1 成员变量

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);

//定义全局异常
    private static final ClosedChannelException FLUSH0_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new ClosedChannelException(), AbstractUnsafe.class, "flush0()");
    private static final ClosedChannelException ENSURE_OPEN_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new ClosedChannelException(), AbstractUnsafe.class, "ensureOpen(...)");
    private static final ClosedChannelException CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new ClosedChannelException(), AbstractUnsafe.class, "close(...)");
    private static final ClosedChannelException WRITE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new ClosedChannelException(), AbstractUnsafe.class, "write(...)");
    private static final NotYetConnectedException FLUSH0_NOT_YET_CONNECTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new NotYetConnectedException(), AbstractUnsafe.class, "flush0()");//物理链路尚未建立异常

//聚合所有Channel使用到的能力对象
    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    private final DefaultChannelPipeline pipeline;//IO操作的实现
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);

    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    private volatile EventLoop eventLoop;
    private volatile boolean registered;
    private boolean closeInitiated;

    /** Cache for the string representation of this channel */
    private boolean strValActive;
    private String strVal;
    }

16.2.2 核心API源码
先看下网络读写操作。
IO操作会触发Netty事件,事件在ChannelPipeline中传播,由对应的ChannelHandler对事件拦截处理。
事件驱动的方式功能和AOP等价,但是性能高于AOP。
DefaultChannelPipeline中的ChannelHandler具体处理IO操作。
16.2.3 AbstractNioChannel源码分析

public abstract class AbstractNioChannel extends AbstractChannel {

    private static final InternalLogger logger =
            InternalLoggerFactory.getInstance(AbstractNioChannel.class);

    private static final ClosedChannelException DO_CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new ClosedChannelException(), AbstractNioChannel.class, "doClose()");

    private final SelectableChannel ch;//java.nio.socketChannel和java.nio.serverSocketChannel的公共父类
    protected final int readInterestOp;//channel监听的事件类型,比如,读、写、客户端链接服务端事件等
    volatile SelectionKey selectionKey;//Channel注册到EventLoop后的返回键
    boolean readPending;
    private final Runnable clearReadPendingRunnable = new Runnable() {
        @Override
        public void run() {
            clearReadPending0();
        }
    };

    /** * The future of the current connection attempt. If not null, subsequent * connection attempts will fail. */
    private ChannelPromise connectPromise;//链接操作的结果
    private ScheduledFuture<?> connectTimeoutFuture;//链接超时定时器
    private SocketAddress requestedRemoteAddress;//通信地址信息
    }

再看几个核心API:
doRegister():

@Override
    protected void doRegister() throws Exception {
        boolean selected = false;//是否注册成功
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);//参数1是要注册的channel,参数2是注册的channel感性的网络事件,0代表对任何事件都感兴趣(abstract只完成注册),参数3是指定的附件,这里将子类设置为附件,后续channel收到网络事件时可以从selectionKey中获取该附件
                return;
            } catch (CancelledKeyException e) {//如果当前返回的selectionKey已经被取消,抛出CancelledKeyException
                if (!selected) {//第一次操作该异常,从多路复用器中删掉selectionKey
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;//表示已经删掉失效的selectionKey
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;//JDK BUG?
                }
            }
        }
    }

public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

注册channel的时候需要指定监听的网络操作位来表示channel对哪几类网络事件感兴趣,具体定义如下:

    public static final int OP_READ = 1 << 0;//读操作位
    public static final int OP_WRITE = 1 << 2;//写操作位
    public static final int OP_CONNECT = 1 << 3;//客户端链接服务端操作位
    public static final int OP_ACCEPT = 1 << 4;//服务端链接客户端操作位

16.2.4 AbstractNioByteChannel 源码分析

//成员变量只有一个 负责写半包消息
private final Runnable flushTask = new Runnable() {
        @Override
        public void run() {
            // Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
            // meantime.
            ((AbstractNioUnsafe) unsafe()).flush0();
        }
    };

最重要的就是doWrite方法:

@Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = config().getWriteSpinCount();
        do {
            Object msg = in.current();
            if (msg == null) {//说明消息都已经发完了
                // Wrote all messages.
                clearOpWrite();//清除半包标识 
                // Directly return here so incompleteWrite(...) is not called.
                return;
            }
            writeSpinCount -= doWriteInternal(in, msg);//继续写
        } while (writeSpinCount > 0);

        incompleteWrite(writeSpinCount < 0);//处理半包发送任务的方法
    }
    
//清除半包标识 
protected final void clearOpWrite() {
        final SelectionKey key = selectionKey();
        // Check first if the key is still valid as it may be canceled as part of the deregistration
        // from the EventLoop
        // See https://github.com/netty/netty/issues/2104
        if (!key.isValid()) {
            return;
        }
        final int interestOps = key.interestOps();
        if ((interestOps & SelectionKey.OP_WRITE) != 0) {//当前channel的网络标志位与SelectionKey.OP_WRITE按位与!=0 说明可写
            key.interestOps(interestOps & ~SelectionKey.OP_WRITE);//置为不可写
        }
    }
//发送消息
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (!buf.isReadable()) {//可读字节为0 消息不可读 
                in.remove();//移除该消息
                return 0;
            }

            final int localFlushedAmount = doWriteBytes(buf);//尝试全量全量发送
            if (localFlushedAmount > 0) {//发送成功,但不表示全量都发送出去了
                in.progress(localFlushedAmount);//修改发送状态
                if (!buf.isReadable()) {//没有可发的数据
                    in.remove();
                }
                return 1;
            }
        } else if (msg instanceof FileRegion) {
            FileRegion region = (FileRegion) msg;
            if (region.transferred() >= region.count()) {
                in.remove();
                return 0;
            }

            long localFlushedAmount = doWriteFileRegion(region);
            if (localFlushedAmount > 0) {
                in.progress(localFlushedAmount);
                if (region.transferred() >= region.count()) {
                    in.remove();
                }
                return 1;
            }
        } else {
            // Should not reach here.
            throw new Error();
        }
        return WRITE_STATUS_SNDBUF_FULL;
    }
    //处理半包发送任务的方法
    //将selectKey置为可写,eventLoop就会轮训这个key,将其flush掉
protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();
        } else {
            // It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
            // use our write quantum. In this case we no longer want to set the write OP because the socket is still
            // writable (as far as we know). We will find out next time we attempt to write if the socket is writable
            // and set the write OP if necessary.
            clearOpWrite();

            // Schedule flush again later so other tasks can be picked up in the meantime
            eventLoop().execute(flushTask);
        }
    }

第十七章 ChannelPipeline和ChannelHandler

ChannelPipeline是对Channel的封装,ChannelPipeline持有时间拦截器ChannelHandler的链表,由ChannelHandler对I/O事件拦截和处理,可以通过新增和删除ChannelHandler来实现不同业务的逻辑定制。

17.1 ChannelPipeline的功能说明

ChannelPipeline是ChannelHandler的容器,负责ChannelHandler的管理和事件拦截
17.1.1 ChannelPipeline的事件处理
Netty中的事件分为inbound和outbound事件
inbound事件通常由IO线程触发,如tcp连接建立、断开、异常通知等
outbound事件通常是由用户主动发起的网络IO操作,如用户发起的绑定本地地址,发送消息等
17.1.2 自定义拦截器
通常ChannelHandler只需要继承ChannelHandlerAdapter类覆盖自己关心的方法即可。
17.1.3 ChannelPipeline的主要特性
ChannelPipeline支持动态添加或者删除ChannelHandler。使用场景(业务高峰期加入拥塞保护ChannelHandler,高峰过后删掉ChannelHandler)。
ChannelPipeline是线程安全的,但是ChannelHandler不是线程安全的。

17.1 ChannelPipeline源码

实际上是ChannelHandler 的容器,内部维护了一个ChannelHandler链表和迭代器。

第十八章 EventLoop和EventLoopGroup

EventLoop负责处理连接中的事件, 和EvenvLoop有关的几个概念是:Channel, EventLoopGroup。

  • 一个EventLoopGroup包含一个或多个EventLoop。
  • 一个EventLoop对应于一个线程,所有EventLoop处理的I/O事件都在这个线程中完成。
  • 一个Channel对应唯一个一个EventLoop。
  • 一个EventLoop可以对应多个Channel。

对于基于Netty的网络服务,Client端启动需要一个EventLoopGroup, Server端启动需要两个EventLoopGroup, 因为Server端需要两种Channel, 一种是ServerChannel, 只有一个,负责接受连接,另一种是用于处理连接的一组Channel。

EventLoop继承了concurrent包里的 ScheduledExecutorService,这使得它可以接受Callable或者Runnable并执行。EventLoop中的parent()方法返回包含这个EventLoop的EventLoopGroup

第十九章 Future和Promise

ChannelFuture是netty中异步Future。
netty中future的特点:

  • 操作结果分为success,fail,canceled三种;
  • 并且通过addlisteners()方法可以添加回调操作,即触发或者完成时需要进行的操作;
  • await()和sync(),可以以阻塞的方式等待异步完成;getnow()可以获得异步操作的结果,如果还未完成则返回Null;
    推荐使用
    netty中的promise扩展自future,相比之下多了 可写 的api

=结构和行业应用篇 Netty 高级特性==

你可能感兴趣的:(java,web)