Netty学习笔记(二)java NIO Buffer、Channel、Selector学习及使用,搭建nio群聊系统

缓冲区(Buffer)

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。
Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。

在NIO中,Buffer是一个顶级父类,它是一个抽象类,类的层级关系如图:(每个buffer实现类中都有对应数据类型的数组用来存储数据(所以buffer实际上数据是存在该数组中的))

通过allocate()获取缓冲区

//创建一个IntBuffer,大小为5,可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate( 5 );

Buffer中重要的四个属性:

Capacity:容量,即缓冲区可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit:界限,表示缓冲区的当前终点(可操作数据的大小),不能对缓冲区超过极限的位置进行读写操作。且界限是可以修改的。
Position:位置,下一个要被读或写的元素的索引(正在操作数据的位置),每次读写缓冲区数据时都会改变改值,为下次读写作准备
Mark:标记,表示记录当前Position的位置,可以通过reset()方法恢复到mark的位置
0<=Mark<=Position<=Limit<=Capacity

buffer类相关方法:

方法名 描述
rewind() 可重复读,position重新回到0读取(也有可能有别的方法,可以回到指定位置重复读)
clear() 回到最初状态,position为0,limit=capacity,但其中的数据并没有清空
mark() 标记当前Position位置,mark=Position
reset() 恢复到Position位置,此时Position=mark
hasRemaining() 判断缓冲区中是否还有可操作剩余数据
remaining() 获取缓冲区中还可以操作的数量

public abstract class Buffer {
//JDK1.4时,引入的api
public final int capacity( )//返回此缓冲区的容量
public final int position( )//返回此缓冲区的位置
public final Buffer position (int newPositio)//设置此缓冲区的位置
public final int limit( )//返回此缓冲区的限制
public final Buffer limit (int newLimit)//设置此缓冲区的限制

public final Buffer mark( )//在此缓冲区的位置设置标记
public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
public final Buffer flip( )//反转此缓冲区

public final Buffer rewind( )//重绕此缓冲区
public final int remaining( )//返回当前位置与限制之间的元素数
public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区

//JDK1.6时引入的api
public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
public abstract Object array();//返回此缓冲区的底层实现数组

public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}

ByteBuffer
从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应,最常用的自然是ByteBuffer 类(二进制数据),该类的主要方法如下:

public abstract class ByteBuffer {
//缓冲区创建相关api
public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量

public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
//构造初始化位置offset和上界length的缓冲区
public static ByteBuffer wrap(byte[] array,int offset, int length)
//缓存区存取相关API
public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
public abstract byte get (int index);//从绝对位置get
public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
public abstract ByteBuffer put (int index, byte b);//从绝对位置上put

}

通道channel

1)NIO的通道类似于流,但有些区别如下:
通道可以同时进行读写,而流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲读数据,也可以写数据到缓冲:

2)BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
3)Channel在NIO中是一个接口public interface Channel extends Closeable{}
4)常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】
5)FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。

服务端会先创建ServerSocketChanne,客户端连接请求过来,服务端会通过ServerSocketChanne分配给客户端一个与之对应的SocketChannel

FileChannel
主要用来对本地文件进行IO操作,常见的方法有:
1.public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
2.public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
3.public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
4.public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道

关于Buffer 和 Channel的注意事项和细节
1)ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。

2)可以将一个普通Buffer 转成只读Buffer
3)NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成。
4)前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持 通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering。

选择器selector

  1. Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  3. 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
  4. 避免了多线程之间的上下文切换导致的开销

特点再说明:
1)Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。
2)当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
3)线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
4)由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
5)一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

selector的相关方法
流程:
channel先注册到selector上,selector监听channel上发生的事件
当发生事件时,selector会将发生事件的channel对应的selectionKey添加到其内部存储set集合中
处理时,selector会通过其内部set集合中取出selectionKey,通过selectionKey反向得到对应的channel

selector是一个抽象类,其常用方法:

public abstract class Selector implements Closeable { public static Selector open();//得到一个选择器对象public int select(long timeout);//(毫秒)监控所有注册的通道,当其中有 IO 操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间(阻塞的时间)select()方法,表示会一直监听,直到发生关注的事件,(是阻塞的)selectNow(),如果有事件发生会立刻返回,(非阻塞的)wakeup(),唤醒selector,使阻塞的selector立刻返回public Set selectedKeys();//从内部集合中得到所有的 SelectionKey }

NIO 非阻塞 网络编程相关的(Selector、SelectionKey、ServerScoketChannel和SocketChannel) 关系梳理图

处理流程:

  1. 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel(ServerSocketChannel 需要先绑定注册到服务器端selector,然后服务器端创建与之关联的SocketChannel)
  2. Selector 进行监听 select 方法, 返回有事件发生的通道的个数.
  3. 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
  4. 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
  5. 进一步得到各个 SelectionKey (有事件发生)
  6. 再通过 SelectionKey 反向获取 SocketChannel , 方法 channel()
  7. 可以通过 得到的 channel , 完成业务处理

代码示例:

package com.ywb.javaroad.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author: yangwen-bo
 *
 * NIO 非阻塞 网络编程处理流程(服务端)
 *
 * 处理流程:
 * 当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
 * Selector 进行监听  select 方法, 返回有事件发生的通道的个数.
 * 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
 * 注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
 * 进一步得到各个 SelectionKey (有事件发生)
 * 在通过 SelectionKey  反向获取 SocketChannel , 方法 channel()
 * 可以通过  得到的 channel  , 完成业务处理
 */
public class NioServer {
   
    public static void main(String[] args) throws IOException {
   
        //创建ServerSocketChannel-当客户端连接发生关注监听事件后会产生得到对应的SocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //selector
        Selector selector = Selector.open();

        //绑定端口,让服务器端监听(为ServerSocketChannel分配一个端口)
        serverSocketChannel.socket().bind(
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值