您的位置 首页 java

Java,NIO,三大核心原理,Selector、多路复用器

Selector选择器:

Selector称为选择器,实际是:多路复用器,是Java NIO核心组件之一,用于检查一个或者多个NIO Channel(通道)的状态是否处于可读、可写,可以实现单线程管理多个Channel(通道),也可以管理多个网络连接。

Selector的创建:

Selector selector = Selector.open();

通过调用静态工厂方法Selector.open()方法创建一个Selector对象,open()方法实际上是向SPI1发出请求,通过默认的SelectorProvider对象获取一个新的Selector实例。

注册Channel到Selector:

channel.configure BLOCK ing( false ); —->设置Channel(通道)是非阻塞的

SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

监听四种不同类型的事件:Connect、Accept、Read、Write

1、SelectionKey.OP_CONNECT,int OP_CONNECT = 1 << 3; 可连接事件,成功连接到另一个服务器称为“连接就绪”;

2、SelectionKey.OP_ACCEPT,int OP_ACCEPT = 1 << 4; 可接受事件,Server socket Channel准备好接收新进入的连接称为“接收就绪”;

3、SelectionKey.OP_READ,int OP_READ = 1 << 0; 读事件,有数据可读的通道称为“读就绪”;

4、SelectionKey.OP_WRITE,int OP_WRITE = 1 << 2; 写事件,等待写数据的通道称为“写就绪”;

一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系:

1、key.attachment(); //返回SelectionKey的attachment(附加),attachment可以在注册channel的时候指定;

2、key.channel(); //返回该SelectionKey对应的channel;

3、key.selector(); //返回该SelectionKey对应的Selector;

4、key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask;

5、key.readyOps(); //返回一个bit mask,代表在相应channel上可以进行的IO操作;

选择键(SelectionKey):

 Set selectedKeys = selector.selectedKeys();
 iterator  keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
}  

代码案例:

 package com.what21.nio.java.selector.case01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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 NIOServer {
	
	// 标识数字
	private int flag = 0;
	// 缓冲区大小
	private int BLOCK = 4096;
	// 接收数据缓冲区
	private ByteBuffer sendBuffer = ByteBuffer. allocate (BLOCK);
	// 发送数据缓冲区
	private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);

	private Selector selector;

	public NIOServer(int port) throws IOException {
		/**
		 * 以下的所有说明均已linux系统底层进行说明: nio 的底层实现是 epoll
		 * 模式,采用多路复用技术,对nio的代码进行深入分析,结合epoll的底层实现 进行详细的说明 1.linux网络编程是两个进程之间的通信,跨集群合网络
		 * 2.开启一个socket线程,在linux系统上任何操作均以文件句柄数表示,默认情况下
		 * 一个线程可以打开1024个句柄,也就说最多同时支持1024个网络连接请求。阿里云默认打开65535个文件
		 * 句柄,通常情况下,1G内存最多可以打开10w个句柄数
		 *
		 *
		 */
		// 打开服务器套接字通道
		// 底层;在linux上面开启socker服务,启动一个线程,绑定ip地址和端口号
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 服务器配置为非阻塞
		serverSocketChannel.configureBlocking(false);
		// 检索与此通道关联的服务器套接字
		ServerSocket serverSocket = serverSocketChannel.socket();
		// 进行服务绑定
		serverSocket.bind(new InetSocketAddress(port));
		// 通过open()方法找到Selector
		// 底层:开启epoll,为当前socket服务创建epoll服务,epoll_create
		selector = Selector.open();
		// 注册到selector
		/**
		 * 底层: 1.将当前的epoll,服务器地址,端口号绑定,如果有连接请求,直接添加到epoll中,epoll的底层是红黑树,
		 * 可以快速的实现连接的查找和状态更新。如果有新的连接过来,直接存放到epoll中。如果有连接过期,中断, 会从epoll中删除。
		 * 2.通过epoll_ctl添加到epoll的同时,会注册一个 回调函数 给内核,当网卡有数据来的时候,会通知内核,内核
		 * 调用回调函数,将当前内核数据的事件状态添加到list链表中
		 */
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		 System .out.println("Server Start:" + port);
	}

	// 监听
	private void listen() throws IOException {
		while (true) {
			// 选择一组键,并且相应得通道已经打开
			/**
			 * epoll底层维护一个链表,rdlist,基于事件驱动模式,当网卡有数据请求过来,会发起硬件中断,通知内核已经有来了。内核调用
			 * 回调函数,将当前的事件添加到rdlist中,将当前可用的rdlist列表发送给用户态,用户去遍历rdlist中的事件,进行处理
			 */
			int readyChannels = selector.select();
			if (readyChannels == 0)
				continue;
			// 返回此选择器得已选择键集
			Set<SelectionKey> selectionKeys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = selectionKeys.iterator();
			while (iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				// 获取当前epoll的rdlist复制到用户态,遍历,同时删除当前rdlist中的事件
				iterator.remove();
				handleKEY(selectionKey);
			}
		}
	}

	// 处理请求
	private void handleKEY(SelectionKey selectionKey) throws IOException {
		// 接受请求
		ServerSocketChannel server = null;
		SocketChannel client = null;
		String receiveText;
		String sendText;
		int count = 0;
		// 测试此键的通道是否已准备好接受新的套接字连接
		if (selectionKey.isAcceptable()) {
			System.out.println("测试此键的通道是否已准备好接受新的套接字连接");
			// 返回为止创建此键的通道
			server = (ServerSocketChannel) selectionKey.channel();
			// 接受次通道套接字的连接
			// 此方法返回的套接字通道(如果有)将处于阻塞模式
			client = server.accept();
			// 配置为非阻塞
			client.configureBlocking(false);
			// 注册到selector,等待连接
			client.register(selector, SelectionKey.OP_READ);
		} else if (selectionKey.isReadable()) {
			// 返回为之创建此键的通道
			client = (SocketChannel) selectionKey.channel();
			// 将缓冲区清空以备下次读取
			receiveBuffer.clear();
			// 读取服务器发送来的数据到缓冲区
			count = client.read(receiveBuffer);
			if (count > 0) {
				receiveText = new String(receiveBuffer.array(), 0, count);
				System.out.println("服务器端接受客户端数据--:" + receiveText);
				client.register(selector, SelectionKey.OP_WRITE);
			}
		} else if (selectionKey.isWritable()) {
			// 将缓冲区清空以备下次写入
			sendBuffer.clear();
			// 返回为之创建此键的通道
			client = (SocketChannel) selectionKey.channel();
			sendText = "message form server --" + flag++;
			// 向缓冲区中输入数据
			sendBuffer.put(sendText.getBytes());
			// 将缓冲区个标志复位,因为李米娜put了数据标志被改变要想从中读取数据发向服务器,就要复位
			sendBuffer.flip();
			// 输出到通道
			client.write(sendBuffer);
			System.out.println("服务器端向客服端发送数据--:" + sendText);
			client.register(selector, SelectionKey.OP_READ);
		}
	}

	public static void main(String[] args) throws IOException {
		NIOServer server = new NIOServer(8888);
		server.listen();
	}
	
}  
 package com.what21.nio.java.selector.case01;

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 NIOClient {
	
	private static boolean isEnding = false;
	// 表示数字
	private static int flag = 0;
	// 缓冲区大小
	private static int BLOCK = 4096;
	// 接受数据缓冲区
	private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
	// 发送数据缓冲区
	private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);

	public static void main(String[] args) throws IOException {
		// 打开socker通道
		SocketChannel socketChannel = SocketChannel.open();
		// 设置为非阻塞模式
		socketChannel.configureBlocking(false);
		// 打开选择器
		Selector selector = Selector.open();
		// 注册连接服务端socket动作
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		// 连接
		InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost", 8888);
		socketChannel.connect(SERVER_ADDRESS);
		// 分配缓冲区内存大小
		Set<SelectionKey> selectionKeys;
		Iterator<SelectionKey> iterator;
		SelectionKey selectionKey;
		SocketChannel client;
		String receiveText;
		String sendText;
		int count = 0;
		while (!isEnding) {
			// 选择一组键,其对应的通道已为 I/O 操作准备就绪
			// 此方法执行处于阻塞模式的选择操作
			selector.select();
			// 返回此选择器的已选择的键集
			selectionKeys = selector.selectedKeys();
			iterator = selectionKeys.iterator();
			while (iterator.hasNext()) {
				selectionKey = iterator.next();
				if (selectionKey.isConnectable()) {
					System.out.println("client connect");
					client = (SocketChannel) selectionKey.channel();
					// 判断此通道伤是否正在进行连接操作
					// 完成套接字通道的连接过程
					if (client.isConnectionPending()) {
						client.finishConnect();
						System.out.println("完成连接!");
						sendbuffer.clear();
						sendbuffer.put("Hello,Server".getBytes());
						sendbuffer.flip();
						client.write(sendbuffer);
					}
					client.register(selector, SelectionKey.OP_READ);
				} else if (selectionKey.isReadable()) {
					client = (SocketChannel) selectionKey.channel();
					receiveBuffer.clear();
					count = client.read(receiveBuffer);
					if (count > 0) {
						receiveText = new String(receiveBuffer.array(), 0, count);
						System.out.println("客户端接受服务器端的数据--:" + receiveText);
						client.register(selector, SelectionKey.OP_WRITE);
					}
				} else if (selectionKey.isWritable()) {
					sendbuffer.clear();
					client = (SocketChannel) selectionKey.channel();
					sendText = "message from client--" + (flag++);
					sendbuffer.put(sendText.getBytes());
					// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
					sendbuffer.flip();
					client.write(sendbuffer);
					System.out.println("客户端向服务器端发送数据--:" + sendText);
					client.register(selector, SelectionKey.OP_READ);
					isEnding = true;
				}
			}
			selectionKeys.clear();
		}
		selector.close();
		socketChannel.close();
	}
}  

文章来源:智云一二三科技

文章标题:Java,NIO,三大核心原理,Selector、多路复用器

文章地址:https://www.zhihuclub.com/183658.shtml

关于作者: 智云科技

热门文章

网站地图