欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NIO

程序员文章站 2022-03-02 15:28:54
...

一、简述
是jdk1.4出现的新的流.
BIO - Blocking IO - 同步式阻塞式IO — UDP/TCP
NIO - New IO - 同步式非阻塞式IO
AIO - AsynchronousIO - 异步式非阻塞式IO - jdk1.8

二、BIO的缺点
1. 会产生阻塞行为 — receive/accept/connect/read/write
2. 一对一的连接:每连接一个客户端,在服务器端就需要开启一个线程去处理请求.在客户端较多的情况下,服务器端就会产生大量的线程 - 耗费内存
3. 连接建立之后如果不发生任何的操作.那么就会导致服务器中的这个线程依然被占用,耗费服务器的资源
4. 无法实现定点操作

三、NIO
三个基本的组件:Buffer-缓冲区, Channel-通道, Selector-多路复用选择器
1、Buffer - 缓冲区
容器 - 存储数据 - 在底层存储数据的时候实际上是以数组形式来存储
capacity - 容量位 - 指定缓冲区的容量
limit - 限制位 - 限制操作位所能达到的尺度
position - 操作位 - 指定要操作的位置
mark - 标记位 - 标记位置,认为标记位置之前的数据是已经操作过的没有错误的数据
mark <= position <= limit <= capacity
flip - 反转缓冲区:先将限制位挪到操作位上, 然后将操作位归零, 清空标记位
clear - 清空缓冲区: 将操作位归零,将limit挪到capacity,将标记位清空
reset - 重置缓冲区: 将操作位挪到标记位
rewind - 重绕缓冲区: 将操作位归零,将标记位清空 — 缓冲区多次读取
2、Channel - 通道
传输数据 - 是面向缓冲区的。在java中,Channel默认也是阻塞的,需要手动将其设置为非阻塞模式。
BIO: File、UDP - DatagramSocket、TCP - Socket, ServerSocket
NIO: FileChannel、UDP - DatagramChannel、TCP - SocketChannel, ServerSocketChannel
FileChannel - 操作文件,可以利用通道实现相同平台之间的零拷贝技术。
3、Selector - 多路复用选择器
进行选择 - 是面向通道进行操作。要求通道在使用的时候必须设置为非阻塞
客户端
可连接,可读、可写
服务端
可接受,可读,可写
通过Selector可以实现利用同一个服务器端来处理多个客户端的数据 — 可以用少量线程处理大量的请求 — 在底层处理的时候实际上依然是同步的

三、NIO的优势:
1. 非阻塞:提高传输效率
2. 一对多的连接:可以用一个或者少量的服务器中的线程来处理大量的请求,从而节省服务器的内存资源
3. 即使已经建立连接,只要没有对应的读写事件,那么依然不能够使用服务器来进行处理
4. 利用通道实现数据的双向传输
5.因为利用缓冲区来存储数据,所以可以对缓冲区中的数据实现定点操作

客户端:

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.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class selectorClient {
	public static void main(String[] args) throws IOException {
		// 创建客户端的通道
		SocketChannel sc = SocketChannel.open();

		// 获取选择器
		Selector selc = Selector.open();

		// 选择器管理的连接要求必须是非阻塞的
		sc.configureBlocking(false);

		// 将客户端注册到选择器上,并且申请一个可连接事件
		sc.register(selc, SelectionKey.OP_CONNECT);

		// 发起连接
		sc.connect(new InetSocketAddress("localhost", 8090));

		// 从这儿开始的代码针对多客户端来进行操作的
		while (true) {
			// 选择出注册过的通道
			selc.select();

			// 针对通道的不同事件类型进行处理
			Set<SelectionKey> keys = selc.selectedKeys();

			Iterator<SelectionKey> it = keys.iterator();
			while (it.hasNext()) {
				// 根据事件类型进行处理
				SelectionKey key = it.next();

				// 判断是否是可连接事件
				if (key.isConnectable()) {
					// 从当前事件中获取到对应的通道
					SocketChannel scx = (SocketChannel) key.channel();

					// 如果是可连接事件,判断连接是否成功
					while (!scx.finishConnect())
						;

					// 如果连接成功了,可能会向服务器端发数据或者读取数据
					scx.register(selc, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
				}

				// 判断是否是可写事件
				if (key.isWritable()) {
					// 从当前事件中来获取到对应的通道
					SocketChannel scx = (SocketChannel) key.channel();

					// 写出数据
					scx.write(ByteBuffer.wrap("hello...".getBytes()));
					// 去除掉可写事件 防止一直写
					scx.register(selc, key.interestOps() ^ SelectionKey.OP_WRITE);
				}

				// 判断是否是可读事件
				if (key.isReadable()) {
					// 从当前事件中获取到对应的通道
					SocketChannel scx = (SocketChannel) key.channel();

					// 读取数据
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					scx.read(buffer);
					buffer.flip();
					System.out.println(new String(buffer.array(), 0, buffer.limit()));

					// 需要去掉这个可读事件
					scx.register(selc, key.interestOps() ^ SelectionKey.OP_READ);
				}
				// 处理完这一大类事件之后
				it.remove();
			}
		}
	}
}

服务器端:

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;

public class SelectorServer {
	public static void main(String[] args) throws IOException {
		// 创建服务器端的通道
		ServerSocketChannel ssc = ServerSocketChannel.open();

		// 绑定要监听的端口号
		ssc.bind(new InetSocketAddress(8090));

		// 开启选择器
		Selector selc = Selector.open();

		// 手动开启非阻塞
		ssc.configureBlocking(false);

		// 将通道注册到选择器上,需要注册一个可接受事件
		ssc.register(selc, SelectionKey.OP_ACCEPT);

		while (true) {
			// 选择出已经注册的连接
			selc.select();

			// 根据事件的不同进行分别的处理
			Set<SelectionKey> keys = selc.selectedKeys();

			Iterator<SelectionKey> it = keys.iterator();
			while (it.hasNext()) {
				// 将事件取出来分别进行处理
				SelectionKey key = it.next();

				// 判断可接受事件
				if (key.isAcceptable()) {
					// 从事件中获取到对应的通道
					ServerSocketChannel sscx = (ServerSocketChannel) key.channel();

					// 接受连接
					SocketChannel sc = sscx.accept();
					sc.configureBlocking(false);

					// 注册读写事件
					sc.register(selc, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
				}

				// 判断可读事件
				if (key.isReadable()) {

					// 从事件中获取到对应的通道
					SocketChannel sc = (SocketChannel) key.channel();

					// 读取数据
					ByteBuffer buffer = ByteBuffer.allocate(1024);
					sc.read(buffer);
					buffer.flip();
					System.out.println(new String(buffer.array(), 0, buffer.limit()));

					// 需要去掉可读事件
					sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ);

				}

				// 判断可写事件
				if (key.isWritable()) {

					// 从事件中获取到对应的通道
					SocketChannel sc = (SocketChannel) key.channel();

					// 写出数据
					sc.write(ByteBuffer.wrap("收到".getBytes()));

					// 需要去掉可写事件
					sc.register(selc, key.interestOps() ^ SelectionKey.OP_WRITE);

				}

				// 去掉这一大类的事件
				it.remove();

			}

		}

	}
}
相关标签: NIO