聊一聊Netty

从NIO开始

Channel Buffer Selector三大金刚
推荐 易百教程

Channel
  • FileChannel: 只能通过IO流getChannel()创建对象
  • DatagramChannel: DatagramChannel.open(); DatagramChannel.close();
  • SocketChannel: 打开
    1
    2
    SocketChannel open = SocketChannel.open();
    open.connect(new InetSocketAddress("", ...));

关闭

1
2
SocketChannel close = SocketChannel.close();
close.connect(new InetSocketAddress("", ...));

  • ServerSocketChannel

Buffer (capacity,limit,position)

1
2
3
4
5
6
7
8
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (channel.read(buffer) != -1) {
buffer.flip(); //类似c指针的操作, 移动读取指针到适当的位置开始读取
while (buffer.hasRemaining()) {//是否还有可读取的内容
dest.write(buffer);
}
buffer.clear();
}
  • 分配缓冲区 allocate(size)/allocateDirect(size)
  • 从缓冲区读取数据 : get() 读入到通道channel.write(buffer);
  • 将数据写入缓冲区: put() 写入到通道channel.write(buffer);

Channel通道间数据传递

  • channel.transferTo()
  • channel.transferFrom()

Selector

  • Selector.open();
  • 选择通道 select(),selectedKeys()…..

管道

  • pipe.open();
  • pipe.source() 源通道读数据 pipe.sink() 往SinkChannel写入数据

Netty

电子书 Netty实战精髓篇

实体书 Netty权威指南 ,推荐看这本书 (像我这样智商高达8位数的人才只能看懂这本书)

重要的组件

  • Channel
  • Callback回调机制
  • Future
  • Event 和 Handler

ChannelOption 原文

  • ChannelOption.SO_BACKLOG

    1
    2
    ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列
    服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
  • ChannelOption.SO_REUSEADDR

    1
    2
    3
    ChanneOption.SO_REUSEADDR对应于套接字选项中的SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口
    某个服务器进程占用了TCP的80端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用
    比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置SO_REUSEADDR就无法正常使用该端口。
  • ChannelOption.SO_KEEPALIVE

    1
    2
    3
    Channeloption.SO_KEEPALIVE参数对应于套接字选项中的SO_KEEPALIVE,该参数用于保持TCP连接
    当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。
    当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。
  • ChannelOption.SO_SNDBUFChannelOption.SO_RCVBUF

    1
    2
    3
    ChannelOption.SO_SNDBUF参数对应于套接字选项中的SO_SNDBUF,ChannelOption.SO_RCVBUF参数对应于套接字选项中的SO_RCVBUF这两个参数用于操作接收缓冲区和发送缓冲区的大小
    接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功
    发送缓冲区用于保存发送数据,直到发送成功
  • ChannelOption.SO_LINGER

    1
    2
    3
    ChannelOption.SO_LINGER参数对应于套接字选项中的SO_LINGER, Linux内核默认的处理方式是当用户调用close()方法的时候,函数返回,在可能的情况下,尽量发送数据
    不一定保证会发生剩余的数据,造成了数据的不确定性
    使用SO_LINGER可以阻塞close()的调用时间,直到数据完全发送
  • ChannelOption.TCP_NODELAY

    1
    2
    3
    ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关
    Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时
    而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

启动server

ServerBootstrap–>group(NioEventLoopGroup)NIO线程组,负责处理连接与数据–>channel(NioServerSocketChannel)
–>option(ChannelOption)配置TCP连接参数–>childHandler()事件处理器

ChannelInitializer(socketChannel pipeline管道添加具体的事件处理器) ChannelHandlerAdapter(负责具体的数据处理)

ServerBootstrap–>bind(port)监听端口成功返回–>ChannelFuture

Unpooled ByteBuf字节缓冲区操作类

一大堆的静态方法, 用到哪个去文档查一下 注意: 是netty的ByteBuf而不是NIO的ByteBuffer

Netty对Nio的大部分类都进行了拓展,功能更强大

TCP粘包与拆包

粘包: 多次数据粘在一起 拆包: 一次数据过大被拆开多次接收

解决: pipeline管道先添加两个解码器 LineBasedFrameDecoder() StringDecoder()

  • LineBasedFrameDecoder 按行处理消息
  • DelimiterBasedFrameDecoder 分隔符解码器,要指定最大长度,超过长度还没找到分隔符就抛异常
  • FixedLengthFrameDecoder 定长解码器
  • LengthFieldBasedFrameDecoder

    1
    2
    3
    4
    5
    参数如下:
    lengthFieldOffset = 0;//长度字段的偏差
    lengthFieldLength = 2;//长度字段占的字节数
    lengthAdjustment = 0;//添加到长度字段的补偿值
    initialBytesToStrip = 2; //从解码帧中第一次去除的字节数
  • LengthFieldPrepender

    1
    通过LengthFieldPrepender可以将待发送消息的长度写入到ByteBuf的前2个字节,编码后的消息组成为长度字段+原消息的方式

编解码 MessagePack

  • jdk序列化性能太差劲
  • 谷歌Protobuf
  • Facebook Thrift
  • jboss Marchalling
  • MessagePack

ByteBuf

NIO 的ByteBuffer有局限性

  • 依然是一个byte字节数组缓冲区
  • readerIndexwriterIndex缓冲区读写分离互不影响
  • read方法:
    1
    2
    3
    4
    5
    6
    7
    readInt()...等等等
    ByteBuf old.readBytes(10) 读取固定大小的,返回新的ByteBuf
    readBytes(ByteBuf dst) 读取数据到新的缓冲区里
    readBytes(ByteBuf dst,int length) 读取固定长度数据到新的缓冲区里
    readBytes(ByteBuf dst,int startIndex,int length)
    readBytes(byte[] dst) 读取数据到新的字节数组里
    reatBytes(OutputStream os,int length) 读取数据到os流
  • write方法:

    1
    2
    3
    ByteBuf writeInt(xxx)...等基本类型  int类型数据写入到新的ByteBuf里
    ByteBuf writeBytes(ByteBuf src) src写入到当前的ByteBuf
    ....
  • discardReadBytes 重用0–readerIndex这部分已经处理过的空间

  • clear操作并不会清空缓冲区,而是移动readerIndex与writerIndex到初始位置
  • Mark与Reset 重设读写指针位置
  • 查找方法:

    1
    2
    3
    indexOf(.....)
    bytesBefore(xxx) 在readerIndex--writerIndex之间查找
    ......
  • 缓冲区复制

    1
    2
    3
    duplicate: 依然指向原有缓冲区,操作会改变原有数据
    copy: 复制一份
    slice: 把原有缓冲区readerIndex--writerIndex之间的数据切割出来

本文标题:聊一聊Netty

文章作者:啪啪啪的指针

发布时间:2018年08月28日 - 16:08

最后更新:2018年08月31日 - 15:08

原始链接:https://www.bootvue.com/2018/08/28/Netty/

转载说明: 转载请保留原文链接及作者。