志宇-NIO之Selector

Selector

selector作用
Selector中可以注册对应的事件(如接收请求、接收数据)的处理;
Selector类可以避免使用非阻塞式客户端中浪费资源忙等待,提高效率;
Selector可以管理多个Channel,监听多个Channel中的事件;
在没有引入Selector的情况下要进行一系列空循环非空判断,如果引入了selector后selector中有select方法可以进行阻塞;
在这里插入图片描述
Selector常识
将多个Channel注册到Selector上
将管道设置成非阻塞的,注册到Selector上,这个管道必须是SelectableChannel类型,所以FileChannel是不能注册到Selector上的;
在这里插入图片描述
Channel注册到Selector上,Selector会有关心的就绪事件,如果有多个关心的事件可以使用位运算符(|)把你关心的操作连接起来:
//SelectionKey.OP_CONNECT, 指某个通道连接到服务器
//SelectionKey.OP_ACCEPT, 只有SeverSocktChannel有这个事件,查看是否有新的连接
//SelectionKey.OP_READ, 是否有可读的通道就绪
//SelectionKey.OP_WRITE, 写数据的通道是否就绪
注册完会返回一个SelectionKey对象,这个选择键表示一个通道与一个选择器之间的注册关系;可以通过SelectionKey获得对应的Channel和Selector,也可以解除Channel和Selector的对应关系,判断两者是否有对应关系,也可以返回你关心的操作(用来判断是连接还是读写);
Selector 选择器维护着注册过的通道集合,维护着三个集合
1、已注册的键的集合: keys()方法返回这个已注册的键的集合, 这个集合不能修改
2、没有移除的键的集合:selectedKeys()方法返回, 该集合中的每个成员都是相关的通道被选择器判断已经准备好的, 并且包含了键的 interest 集合中的操作, 键可以从集合中移除,不能添加
3、已取消的键的集合:这个集合包含了调用过 cancel()方法的键
Selector使用
首先创建一个Selector,创建一个Channel设置成非阻塞,将Channel注册到Selector上,注册完判断是否有就绪的通道(如果没有就绪通道会阻塞),如果有则根据返回的SelectionKey 根据对应事件进行相应处理;

package com.lizhiyu.com;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;

public class Server {
   public static void main(String[] args) throws IOException {
	   //创建ServerSocketChannel
	   ServerSocketChannel ssc=ServerSocketChannel.open();
	   ssc.bind(new InetSocketAddress(8888));
	   //设置成阻塞,如果不设置成阻塞则会抛异常
	   ssc.configureBlocking(false);
	   //创建Selector
	   Selector selector = Selector.open();
	   //Register()方法将Channel注册到选择器中
	   //第一个参数表示通道注册的选择器
	   //第二个参数表示关心通道的哪个操作
//第二个参数值含义
//	   SelectionKey.OP_CONNECT, 指某个通道连接到服务器
//	   SelectionKey.OP_ACCEPT, 只有SeverSocktChannel有这个事件,查看是否有新的连接
//	   SelectionKey.OP_READ, 是否有可读的通道就绪
//	   SelectionKey.OP_WRITE, 写数据的通道是否就绪
	   SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
	   //使用位运算符把你关心的操作连接起来
	   //SelectionKey selectionKey2 = ssc.register( selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);  
	   //可以用来判断是否有就绪通道
	   //这个selector如果没有接收到就绪通道就会阻塞
	   int select = selector.select();
	   System.out.println(select);
   }
}

SelectionKey 使用
向 Selector 注册一个 Channel 通道时,就会返回一个 SelectionKey 选择键对象, 这个选择键表示一个通道与一个选择器之间的注册关系
SelectionKey 相关的方法:
channel()方法, 返回与该键对应的通道
selector()方法, 返回通道注册的选择器
cancel()方法,终结这种特定的注册关系
isValid()方法判断注册关系是否有效
interestOps()方法返回你关心的操作, 是以整数的形式进行编码的比特掩码, 可以使用
位运算检查所关心的操作,如:
Boolean isAccept = interestops & SelectionKey.OP_ACCEPT == SelectionKey.OP_ACCEPT
Boolean isConnect = interestops & SelectionKey.OP_CONNECT == interestops & SelectionKey.OP_CONNECT
还可以使用isReadable(), isWritable(), isConnectable(), isAccetable()等方法检测 这些些比特值, 上面一行检测 write 就绪的操作可以使用以面一行代替
if ( selctionKey.isWritable() ){ }
再谈selector.select()方法
Selector 类的核心就是 select()选择, 该方法调用时,执行以下步骤:

  1. 检查已取消键的集合, 如果该集合非空, 就把该集合中的键从另外的两个集合中移除, 注销相关的通道, 这个步骤结束后, 已取消键的集合应该是空的;
  2. 检查已注册键的集合中所有键的 interest 集合, 确定每个通道所关心的操作是否已经就绪;
  3. Select()方法返回的是从上一次调用 select()方法后进入 就绪状态的通道的数量;
    如果客户端已经关闭,服务端没有将接收的SocketChannel关闭,会进行isReadable方法空循环
    Selector的select方法在没有准备好的IO操作时,一直处于阻塞状态;
    模拟NIO使用
    服务端
package com.lizhiyu.com;

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 lizhiyu
 */
public class NIOServer {
	private ServerSocketChannel server;
	private ByteBuffer sendBuffer;
	private ByteBuffer recvBuffer;
	private Selector selector;
	private int port = 8888;

	// 初始化服务器
	NIOServer(int port) {
		this.port = port;
		try {
			recvBuffer = ByteBuffer.allocate(1024);
			sendBuffer = ByteBuffer.allocate(1024);
			server = ServerSocketChannel.open();
			server.socket().bind(new InetSocketAddress(port));
			server.configureBlocking(false);
			selector = Selector.open();
			server.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	NIOServer() {
		this(8888);
	}
	public void start() {
		try {
			listener();
		} catch (IOException e) {
			System.out.println("监听端口IO异常");
		}
	}
	public void listener() throws IOException {
		while (true) {
//			System.out.println("-----------------------------------------------------");
//			System.out.println("1.selectedKeys的值:"+selector.selectedKeys().size());
//			System.out.println("1.registe的值:"+ selector.keys().size());
			//如果没有链接select会一直阻塞
			int n = selector.select();
//			System.out.println("----------------------fengexian1---------------------");
//			System.out.println("2.select返回值:"+n);
//			System.out.println("2.selectedKeys的值:{}"+ selector.selectedKeys().size());
//			System.out.println("2.registe的值:"+selector.keys().size());
//			System.out.println("-------------------------------------------------------");
			if (n == 0) {
				continue;
			}
			Set<SelectionKey> eventKeys = selector.selectedKeys();
			Iterator<SelectionKey> it = eventKeys.iterator();
			while (it.hasNext()) {
				SelectionKey eventKey = it.next();
				it.remove();
				// 处理SelectionKey绑定的channel的连接,读,写等;
				handleKey(eventKey);
			}
		}
	}
	// 处理IO口连接,读写等函数
	public void handleKey(SelectionKey eventKey) throws IOException {
		//接收连接将接收的Channel注册到Selector上
		int interestOps = eventKey.interestOps();
		Boolean isAccept =(interestOps & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
		if(isAccept){
	    //相同的判断等效果
		//if (eventKey.isAcceptable()) {
			SocketChannel sc = server.accept();
			System.out.println("新的客户端已经连接成功");
			sc.configureBlocking(false);
			sc.register(selector, SelectionKey.OP_READ);
		}
		//Boolean isRead =(interestOps & SelectionKey.OP_READ) == SelectionKey.OP_READ;
		else if (eventKey.isReadable()) {
			//相同的判断方法
			//if(isRead){
			SocketChannel sc = (SocketChannel) eventKey.channel();
			System.out.println("进行读操作");
			String content = "";
			int n;
			recvBuffer.clear();
			try {
				//将数据写入到缓冲区中
				//这里缓冲区的大小有限制
				while ((n = sc.read(recvBuffer)) > 0) {
					content = content + new String(recvBuffer.array(), 0, n);
				}
			} catch (IOException e) {
				eventKey.cancel();
				sc.close();
				return;
			}
			//如果接收不到数据可能是客户端已经挂了,要将对应的Channel和SelectionKey解除绑定,比如客户端只连接不发送数据后直接关闭连接,服务端是不知道客户端没有连接的只能通过空循环isReadable来判断客户端是否有还要发送数据
//			if (n == -1) {
//				eventKey.channel().close();
//				eventKey.cancel();
//				System.out.println("客户端已经关闭:"+ sc.socket().getRemoteSocketAddress());
//				return;
//			}
			System.out.println("receive client input Stirng : "+ content);
			if (content.length() > 0) {
				//清空发送缓冲区
				sendBuffer.clear();
				//将接收的数据放到发送缓冲区
				sendBuffer.put(content.getBytes());
				//缓冲区切换成读模式
				sendBuffer.flip();
				//将缓冲去中的数据写入到SocketChannel
				sc.write(sendBuffer);
			}
			//要进行关闭这个ServerChannel在传输完数据后,如果不关闭会一直进行isReadable方法中无限循环
			eventKey.channel().close();
			eventKey.cancel();
		}
//		if (eventKey.isWritable()) {
//			System.out.println("sendBuffer可写");
//			SocketChannel sc = (SocketChannel) eventKey.channel();
//			//将缓冲区中的数据写入到Channel中
//			sc.write(sendBuffer);
//		}
	}

	public static void main(String[] args) {
		new NIOServer().start();
	}
}

客户端

package com.lizhiyu.com;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class Client {
   public static void main(String[] args) throws IOException {
    SocketChannel sc = SocketChannel.open();
	boolean connect = sc.connect(new InetSocketAddress(8888));
	//向服务器发送消息
	ByteBuffer buffer = ByteBuffer.wrap("hello, I am from client socket".getBytes());
	while( buffer.hasRemaining()){
	sc.write(buffer);
	}
    //获得服务器发送给客户端的消息
	InputStream inputStream = sc.socket().getInputStream();
	ReadableByteChannel newChannel = Channels.newChannel(inputStream);
	buffer.clear();
	newChannel.read(buffer);
	buffer.flip();
	CharBuffer decode = Charset.defaultCharset().decode(buffer);
	System.out.println(decode);
	sc.close();
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值