《Netty实战》第一部分

本文介绍了Netty实战中关于Channel的概念,强调了Channel的线程安全性和与EventLoop的绑定。讲解了ByteBuf的特性,如堆缓冲区、直接缓冲区和复合缓冲区,以及ByteBuf的操作方法。同时,提到了ChannelHandler及其相关接口,如ChannelInboundHandler和ChannelOutboundHandler,用于处理入站和出站事件,并介绍了ChannelPipeline和ChannelHandlerContext在数据传输中的作用。

前言

从开始打算学Netty,然后很自然的找到了《Netty实战》这本书,但是,对一个从来没写过的Netty程序的人而言,理解书中的概念确实很难呀。更何况,还几乎没有Nio网络编程的基础,所以一度书看不下去,但觉得写Netty程序不算什么很难的事情。
后来,由于一些需要,自己开始写Netty服务端和客户端,时常出现服务端发不出去消息,或者客户端收不到消息。并且用的Kotlin,异常都没有受检,就更加找不到错误了。这时回过头来看看《Netty实战》才感觉收获很多。

Channel

代表一个通道,连接客户端和服务端两头,两头都可以往Channel里面写/读东西。且Channel的读写是线程安全的。
一个Channel与一个EventLoop绑定,而一个EventLoop可以视为一个线程,用于处理Channel的回调事件。
ChannelRegistered   ->   ChannelActive
                              |
                              \/
ChannelUnregistered <-  ChannelInactive

这里写图片描述

这里写图片描述

由类图可以得到一些信息:Comparable接口保证Channel是独一无二的,AttributeMap有attr(AttrubuteKey< T >)和hasAttr(AttrubuteKey< T >)
通过localAddress()和remoteAddress()可以获取连接的信息,此外isXXX()很好理解,write(),read(),flush()也很常见。
此外很重要的是pipeline()和config()这两个方法, ChannelPipeline持有所有入站和出站的ChannelHandler. 而ChannelConfig,看下图吧

这里写图片描述

unsafe()返回一个Unsafe对象,可以更改Channel的基本信息,如bind一个新的端口,connect一个新的远程地址…

这里写图片描述

还有Channel的父接口ChannelOutboundInvoker, 根据文档,说明write()方法是通过代理ChannelHandlerContext对象来实现的

这里写图片描述

常用的Channel

名称包名描述
NIOio.netty.channel.socket.nio基于java.nio.channels的Selector
Epollio.netty.channel.epoll使用jni的eqoll()和非堵塞io,只在Linux可用的特性,速度更快
OIOio.netty.channel.socket.oio基于java.net的堵塞Stream实现
Localio.netty.channel.local在VM内部的管道传输
Embeddedio.netty.channel.embedded多用于测试ChannelHandler

ByteBuf

Netty的数据传输通过ByteBuf(抽象类)和ByteBufHolder(接口)来暴露。
相比于ByteBuffer而言,ByteBuf有以下优点:可自行扩展,复合缓冲器类型零拷贝,容量自动增长,读写切换不需要flip,读写使用不同的索引,支持链式调用,支持引用计数,支持池化。

这里写图片描述
这里写图片描述

Bytebuf有readerIndex和writerIndex, readerIndex不能超过可读的索引,否则会抛出异常。

缓冲区类型

堆缓冲区:分配释放很快,读写较慢
ByteBuf heapBuf = ...
if(heapBuf.hasArray()){               //只有堆缓存有支撑数组,判断是不是堆缓存
    byte[] array = heapBuf.array();   //如果有,获取数组的引用
    int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();//计算第一个字节的偏移量
    int length = heapBuf.readableBytes(); //获取可读字节
    handleArray(array, offset, length);   //获取数组,偏移量,长度来处理
}
直接缓冲区:分配释放很慢,读写很快。有时候需要复制到堆上,变成字节数组来处理。
ByteBuf directBuf = ...
if(!heapBuf.hasArray()){               //判断为直接缓冲区
    int length = directBuf.readableBytes();//获取可读字节数
    byte[] array = new byte[length];       //分配一个新的字节数组
    directBuf.getBytes(directBuf.readableIndex(), array);
    handleArray(array, 0, length);
}
复合缓冲区:将多个ByteBuf表示为单个CompositeByteBuf的虚拟表示。如有一个header的ByteBuf和body的ByteBuf
CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
ByteBuf headerBuf = ...;
ByteBuf bodyBuf = ...;
messageBuf.addComponents(headerBuf, bodyBuf);

字节操作

随机访问:
ByteBuf buf = ...;
for(int i = 0;i < buf.capacity();i++){
    byte b = buf.getByte(i);
    System.out.println((char)b);
}
顺序访问:0 <= readerIndex <= writerIndex <= capacity
可丢弃字节:discardReadBytes()
可读字节:readXXX(), readBytes(ByteBuf dst)
可写字节:writeXXX(), writeBytes(ByteBuf src)
标记:mark(int readlimit), reset(), markReaderIndex(), markWriterIndex(), resetWriterIndex(), resetReaderIndex()
查找操作:ByteBufProcessor只有一个process(byte val), 如
ByteBuf buffer = ...;
int index = buffer.forEachByte(ByteBufProcessor.FIND_CR);

派生缓冲区:

slice(int, int)
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty", utf8);
ByteBuf sliced = buf.slice(0, 5);
println(sliced.toString(utf8));
buf.setByte(0,(byte)'j');
assert buf.getByte(0) == sliced.getByte(0);     //证明数据是共享的,获取引用
copy(int, int)
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty", utf8);
ByteBuf copy = buf.copy(0, 5);
println(copy.toString(utf8));
buf.setByte(0,(byte)'j');
assert buf.getByte(0) != copy.getByte(0);     //证明数据是共享的,获取引用
get()/set()从索引开始写,保持索引不变。read()/write()从索引开始写,会调整索引。
其他操作,isReadable(), isWritable(), readableBytes(), writableBytes(), capacity(), maxCapacity(), hasArray(), array()

ByteBufHolder接口

这里写图片描述

content() 返回其持有的ByteBuf
copy()返回深拷贝,包括一个其所包含的ByteBuf的非共享拷贝
duplicate()返回浅拷贝,包括一个其所包含的ByteBuf的共享拷贝

ByteBufAllocator接口

这里写图片描述

    ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

    static {
        //判断是不是android,是则Unpooled, 否则默认是Pooled
        String allocType = SystemPropertyUtil.get(
                "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
        allocType = allocType.toLowerCase(Locale.US).trim();

        ByteBufAllocator alloc;
        if ("unpooled".equals(allocType)) {
            alloc = UnpooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else if ("pooled".equals(allocType)) {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
        }

        DEFAULT_ALLOCATOR = alloc;

        THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 64 * 1024);
        logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);

        MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
        logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
    }

Unpooled缓冲区

主要方法:
  • buffer(): 返回一个未池化的堆内存存储的ByteBuf
  • directBuffer(): 返回一个未池化的直接内存存储的ByteBuf
  • wrappedBuffer(): 返回一个包装了给定数据的ByteBuf
  • copiedBuffer(): 返回一个复制了给定数据的ByteBuf

ByteBufUtil: 用于操作ByteBuf的静态的辅助方法,

(咳咳,之前我白写吗?)
  • hexDump(ByteBuf)
  • decodeHexDump(CharSequence)
  • writeUtf8(ByteBuf, CharSequence)
  • copy()
  • encodeString()
  • decodeString()

引用计数

Channel ch = ...;
ByteBufAllocator allocater = ch.alloc();
...
ByteBuf buffer = allocator.directBuffer();
assert buffer.refCnt() == 1;

boolean released = buffer.release();

ChannelHandler

主要为ChannelInboundHandler, ChannelOutBoundHandler这两个接口,而可以使用的实现类有这些:编码器,解码器(Object msg的类型取决于编解码器), ChannelInboundHandlerApater抽象类, ChannelOutboundHandlerAdpater抽象类,还有一个客户端很容易使用的SimpleChannelInboundHandler类
此外,在Handler处理的时候,可能会有异常,这个异常会随着pipeline传播,在pipeline的最后需要处理异常。
典型用途是:
  • 将数据从一种格式变为另一种格式
  • 提供异常的通知
  • 提供Channel的生命周期的回调通知
  • 提供用户自定义事件的回调的通知
生命周期有:
  • handlerAdded: 把ChannelHandler添加到ChannelPipeline时调用
  • handlerRemoved: 把ChannelHandler从ChannelPipeline中移除
  • exceptionCaught: 在处理过程中在ChannelPipeline中有错误产生

ChannelInboundHandler, ChannelOutBoundHandler全是回调方法。

这里写图片描述
这里写图片描述

ChannelHandlerAdapter

只多了isSharable()方法,基本不变,两个HandlerAdapter抽象类的实现方法,全都是委托给了ChannelHandlerAdapter的fire前缀同名方法。fire表示发射,意思是发给pipeline中的下一个handler

资源泄露

现在可能还用不着,相关类为ResourceLeakDetector.java
释放资源为ReferenceUtil.release(msg);

ChannelPipeline

每一个Channel在创建时候,都会被分配一个新的ChannelPipeline对象,这是捆绑的,不能改也不能分。
入站事件,从pipeline头部传递到尾部, 经过所有InboundHandler
出站事件,从pipeline尾部传递到头部, 经过所有OutboundHandler
之后会分析其默认实现DefaultChannelPipeline, 底层还是使用AbstractChannelContext的方法。

这里写图片描述
这里写图片描述
这里写图片描述


ChannelHandlerContext

这里写图片描述

这里写图片描述

ChannelHandlerContext 、 Channel 和 ChannelPipeline

这里写图片描述

使用Pipeline来write()

会将数据从Pipeline开始向后传输

这里写图片描述

ChannelHandlerContext ctx = ..;
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));

使用ctx来write()

这里写图片描述

ChannelHandlerContext ctx = ..;
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));

异常处理

//入站异常
public class InboundExceptionHandler extends ChannelInboundHandlerAdapter {
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

这里写图片描述
这里写图片描述

    //出站异常
    ChannelFuture future = channel.write(someMessage);
    future.addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture f) {
            if (!f.isSuccess()) {
                f.cause().printStackTrace();
                f.channel().close();
            }
        }
    });

这里写图片描述


EventLoop和线程模型

这里写图片描述

JDK任务调度

这里写图片描述
这里写图片描述

Netty任务调度

这里写图片描述

这里写图片描述

这里写图片描述

线程管理

这里写图片描述

1.异步传输

这里写图片描述

2.阻塞传输

这里写图片描述


Bootstrap

这里写图片描述

ServerBootstrap会使用一个父Channel来接收来自客户端的连接,并创建子Channel来用于他们之间的连接。而Bootstrap只需要一个单独的,没有父Channel的Channel来用于所有的网络的交互。

这里写图片描述
这里写图片描述

引导客户端

这里写图片描述

这里写图片描述
这里写图片描述


ServerBootstrap

这里写图片描述

这里写图片描述


服务器接收请求并作为客户端

这里写图片描述
这里写图片描述

添加多个ChannelHandler

这里写图片描述
这里写图片描述

ChannelOption和属性

这里写图片描述
这里写图片描述

这里写图片描述

这里写图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值