NIO网络编程三大核心理念

  1. Buffer缓冲区
  2. Channel通道
  3. Selector选择器

1 Buffer缓冲区

本质是一个可以写入数据的内存块(类似于数组),可以更轻松地使用内存块,相对于直接对数组操作,buffer api更加容易操作和管理。

使用Buffer进行数据写入与读取,需要进行四个步骤:

  1. 将数据写入缓冲区
  2. 调用buffer.flip(),转换为读取模式
  3. 缓冲区读取数据
  4. 调用buffer.clear()或buffer.compact()清除缓冲区

在BIO网络模型中,数据的操作都是基于Stream对象,而输入输出流都是阻塞的;在NIO网络模型中,所有对于数据的操作,都是在缓冲区进行的,常用的缓冲区有ByteBuffer,其它的基本数据类型也有对应的缓冲区(除了bool类型)。

Buffer的三个属性:

  1. Capacity:缓冲区的内存大小
  2. Position:当前读写的位置
  3. Limit:读模式下为写入的位置,写模式下为Capacity值

ByteBuffer提供比其他类型缓冲区更特有的一些操作,是最常用的缓冲区。

2 Channel通道

分为两大类:

  1. 用于网络读写的SelectableChannel
  2. 用于文件操作的FileChannel

SocketChannel和ServerSocketChannel为SelectableChannel的子类;
Channel是全双工模式的,Stream只能在一个方向上。

2.1 SocketChannel

SocketChannel用于建立TCP网络连接,类似java.net.Socket。有两种创建SocketChannel形式:

  1. 客户端主动发起和服务器的连接
  2. 服务端获取的新连接
// 客户端主动发起连接的方式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); //设置为非阻塞模式
socketChannel.connect(new InetSocketAddress("http://163.com", 80));

channel.write(byteBuffer); // 发送请求数据 - 向通道写入数据
int bytesRead = socketChannel.read(byteBuffer); //读写服务端返回 - 读取缓冲区的数据
socketChannel.close(); // 关闭连接

write():在尚未写入任何内容时就可能返回了,需要在循环中调用write()。
read():可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节。

2.2 ServerSocketChannel
// 创建网络服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口
while(true){
	SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新tcp连接通道
	if(socketChannel!=null){
		// tcp请求读取/相应
	}
}

serverSocketChannel.accept():如果该通道处于非阻塞模式,那么如果没有挂起的连接,该方法将立即返回null,必须检查返回的SocketChannel是否为null。

3 Selector选择器

不断轮询注册在Selector上的Channel,可以检查一个或多个Channel,只需要一个线程负责Selector的轮询,就可以处理成千上万的请求。

一个线程使用Selector监听多个Channel的不同事件:

  1. Connect连接(SelectionKey.OP_CONNECT)
  2. Accept准备就绪(OP_ACCEPT)
  3. Read读取(OP_READ)
  4. Write写入(OP_WRITE)
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectorKey key = channel.register(selector, SelectionKey.OP_READ); // 注册感兴趣的事件
while(true){
	int readyChannels = selector.select(); // select收到新的事件,方法才会返回
	if(readyChannels == 0) continue;
	Set<SelectionKey> selectedKeys = selector.selectedKeys();
	Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
	while(keyIterator.hasNext()){
		SelectionKey key = keyIterator.next();
		// 判断不同的事件类型,执行对应的逻辑处理
		// key.isAcceptable()
		// key.isConnectable()
		// key.isReadable()
		// key.isWritable()
		// keyIterator.remove()
	}
}

4 NIO服务端处理流程

  1. 打开serverSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道;
  2. 绑定监听的端口,设置连接为非阻塞模式;
  3. 创建reactor线程,创建多路复用器并启动线程;
  4. 将serverSocketChannel注册到reactor线程的多路复用器上,监听accept事件;
  5. selector在run方法的无限循环中轮询准备就绪的key,selector通过selectionKey可以获取就绪channel的集合;
  6. selector监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路;
  7. 设置客户端链路为非阻塞模式;
  8. 将新接入的客户端连接注册到reactor线程的多路复用器上,监听读操作,用来读取客户端发送的网络消息;
  9. 异步读取客户端的请求到缓冲区;
  10. 对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将编解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排;
  11. 将pojo对象encode成ByteBuffer,调用socketChannel的异步write接口,将消息异步发送到客户端(注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓存区)

参考文章

网易云课堂《Java高级开发工程师》

结语

本人所有博客仅用于学习记录,不做任何商业用途,如涉及侵权,还请联系删除,感谢阅读,欢迎留言,一起进步~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值