`

(四)NIO-Selector

    博客分类:
  • NIO
阅读更多
一、
seletor连接器,多路复用器,类似于路由;是用一个selector管理多个channel

0.主要方法

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

	//将通道注册到选择器中让选择器管理这个通道
	channle.regeister(selc,ops)

	//检查已经注册在选择器上的通道关心的操作是否有已经就绪可以处理的
	int select() //检查注册在选择器上的通道们关心的事件是否已经有就绪可以处理的,如果有此方法返回,返回值为可以处理的数量,如果没有可处理的则此方法阻塞。

	//获取已经就绪的键的集合
	Set<SelectionKey> selectedKeys()



1.selector 实现读写

服务端
public class SelectorServiceDemo {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		// 服务端channel创建
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 获取连接
		Selector selector = Selector.open();
		// 注册选择器
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		while(true){
			// 当前已经就绪的数量
			int readyCount = selector.select();
			if(readyCount > 0 ){
				
				Iterator iterator = selector.selectedKeys().iterator();
				while(iterator.hasNext()){
					
					SelectionKey key = (SelectionKey) iterator.next();
					if(key.isAcceptable()){
						
						ServerSocketChannel serverSocketChannelConnectable = (ServerSocketChannel) key.channel();
						SocketChannel socketChannelConnectable = serverSocketChannelConnectable.accept();
						socketChannelConnectable.configureBlocking(false);
						// 接受连接已完成,再注册一个读数据
						socketChannelConnectable.register(selector, SelectionKey.OP_READ);
						
					}else if(key.isReadable()){
						
						SocketChannel socketChannelReadable = (SocketChannel) key.channel();
						socketChannelReadable.configureBlocking(false);
						
						ByteBuffer temp = ByteBuffer.allocate(1);
						String head = "" ;
						while(!head.endsWith("\r\n")){
							socketChannelReadable.read(temp);
							head += new String(temp.array());
							temp.clear();
						}
						
						int len = Integer.valueOf(head.substring(0,head.length()-2)).intValue();
						ByteBuffer byteBuffer = ByteBuffer.allocate(len);
while(byteBuffer.hasRemaining()){ // 保证读入完毕						socketChannelReadable.read(byteBuffer);
}
						String content = new String(byteBuffer.array());
						System.out.println("服务端接收到的客户端发送的数据内容:"+content);
						
						
					}
					// 已操作完毕的,删除掉,否则会报空指针异常
					iterator.remove();
				}
			}
		}
		
	}

}



客户端
public class SelectorClientDemo {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		
		Selector selector = Selector.open();
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		
		while(true){
			int readyCount = selector.select();
			if(readyCount > 0 ){
				
				Iterator iterator = selector.selectedKeys().iterator();
				while(iterator.hasNext()){
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					
					if(selectionKey.isConnectable()){
						
						SocketChannel socketChannelConnectable = (SocketChannel) selectionKey.channel();
						socketChannelConnectable.configureBlocking(false);
						socketChannelConnectable.finishConnect();
						socketChannelConnectable.register(selector, SelectionKey.OP_WRITE);
						
					}else if(selectionKey.isWritable()){
						
						SocketChannel socketChannelWriteable = (SocketChannel) selectionKey.channel();
						socketChannelWriteable.configureBlocking(false);
						
						String content = "hello world! i am coming !" ;
						StringBuilder sb = new StringBuilder();
						sb.append(content.length()+"").append("\r\n").append(content);
						
						ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes());
while(byteBuffer.hasRemaining()){// 保证写入完毕						socketChannelWriteable.write(byteBuffer);
}
						
						System.out.println("客户端发送的数据内容:"+sb.toString());
					}
				}
				// 删除已处理过的
				iterator.remove();
				
			}
		}
	}

}



运行发现,代码一直执行,循环输出
原因:
客户端发起连接
服务端接受连接
客户端写
服务端读
客户端,isWriteable 因为数据已经读出,发现还可以写,继续写
服务端,isReadable  因为数据已经写入,发现还可以读,继续读
...

2.
将多次读写,改为一次读写

Selector 维护的三个集合
  • 已注册:  register到selector 上的channel集合
  • 已就绪:  selector() 方法执行时,已准备好做某事件的channel 集合
  • 待删除:  不能直接删除,防止此channel在使用中,引发其他情况;待删除,在下次
  • selector方法执行前删除


修改客户端代码,增加 cancel代码
表示,将已注册到selector上的channel 集合中的当前 channel 移动 待删除集合中
待下次执行 seletor() 方法前,删除掉

						
						String content = "hello world! i am coming !" ;
						StringBuilder sb = new StringBuilder();
						sb.append(content.length()+"").append("\r\n").append(content);
						
						ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes());
						while(byteBuffer.hasRemaining()){
							socketChannelWriteable.write(byteBuffer);
						}
						
						System.out.println("客户端发送的数据内容:"+sb.toString());
						
						// 取消,在上面的代码的基础上,增加此行
						selectionKey.cancel();



3.NIO的优势

操作:
3.1 启动服务端
3.2 多次启动客户端

模拟实现  服务端同时为多个客户端服务

4.发送汉字

string content = "你好!全世界!";

发现接收到内容丢失!

原因:str.length 指的是 字符串的字符个数;而传递的内容是 bytes ,而按照 UTF-8编码,汉字占用三个字节,即使是GBK编码格式,汉字也占用两个字节

客户端,发送内容的长度,改为

content.getBytes().length

5.
异常

远程主机强制关闭连接

强制关闭客户端,则服务端报错;或相反;
处理:
对整个while(true)内部进行异常捕获,
catch 部分 continue ;

6.selectionKey

  • OP_READ = 1 << 0
  • OP_WRITE = 1 << 2
  • OP_CONNECT = 1 << 3;
  • OP_ACCEPT = 1 << 4


四个常量值,对应四个不同的值转换为2进制的形式,对应不同的开关
int 类型为 4B = 32bit

read00000000 00000000 00000000 00000001
write00000000 00000000 00000000 00000100
connect00000000 00000000 00000000 00001000
accept00000000 00000000 00000000 00010000

由此得出:
若同时注册多个,即是几种情况的组合,若取消注册的某个值,则将这个值取反,再与操作

6.1 组合
read & write & accept
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000100
00000000 00000000 00000000 00010000

00000000 00000000 00000000 00010101

6.2 取消 write

00000000 00000000 00000000 00000100--写
11111111 11111111 11111111 11111011--取反
00000000 00000000 00000000 00010101--当前的组合结果
00000000 00000000 00000000 00010001--与操作 -- 去掉了写,剩余 read 与 accept 组合

7.同时注册多个

服务端
public class SelectorServerDemo {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		// 创建channel,设置非阻塞模式,绑定端口号
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		// 创建选择器,注册channel到选择器
		Selector selector = Selector.open();
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		// 死循环,时刻等待客户端发起连接
		while(true){
			// 当前已注册的 channel 集合中,已准备就绪的channel数量
			int readCount = selector.select();
			if(readCount > 0 ){
				// 遍历当前已就绪的channel集合
				Iterator iterator = selector.selectedKeys().iterator();
				while(iterator.hasNext()){
					// selectionKey 连接  channel 与 selector
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					// 是否接收请求就绪
					if(selectionKey.isAcceptable()){
						// 取出的channel与注册的channel是同一个
						ServerSocketChannel serverSocketChannelConnect = (ServerSocketChannel) selectionKey.channel();
						SocketChannel socketChannel = serverSocketChannelConnect.accept();
						socketChannel.configureBlocking(false);
						// 接收连接后,进行读取客户端数据并响应操作
						socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
					// 是否读取请求就绪
					}else if(selectionKey.isReadable()){
						// 获取当前的channel
						SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
						socketChannel.configureBlocking(false);
						// 读取客户端发送的内容的头部信息
						String head = "" ;
						ByteBuffer temp = ByteBuffer.allocate(1);
						while(!head.endsWith("\r\n")){
							socketChannel.read(temp);
							head += new String(temp.array());
							temp.clear();
						}
						int len = Integer.valueOf(head.substring(0, head.length() - 2)).intValue();
					    // 读取客户端发送的内容部分
						ByteBuffer byteBuffer = ByteBuffer.allocate(len);
						while(byteBuffer.hasRemaining()){
							socketChannel.read(byteBuffer);
						}
						String content = new String(byteBuffer.array());
						System.out.println("client:"+content);

					}else if (selectionKey.isWritable()){
						// 此处可不获取,与注册的channel是同一个
						SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
						String content = "hi client,you send date already accept" ;
						StringBuffer sb = new StringBuffer();
						// 发送数据的长度+发送内容
						sb.append(content.getBytes().length+"").append("\r\n").append(content);
						ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes());
					    // 确保数据已全部写入 
						while(byteBuffer.hasRemaining()){
							socketChannel.write(byteBuffer);
						}
						// 写入操作完毕,取消注册到selector上的write 
						// 方式与	op_write 的结果取反,再进行与操作
						socketChannel.register(selector, selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
						
					}
					
					iterator.remove();
					
				}
			}
			
		}
		
	}

}



客户端
public class SelectorClentDemo {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
		// 直接操作 socketChannel 同时注册多个
		Selector selector = Selector.open();
		socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE | SelectionKey.OP_READ );
		
		while(true){
			
			int readyCount = selector.select();
			if(readyCount >0 ){
				
				Iterator iterator = selector.selectedKeys().iterator();
				while(iterator.hasNext()){
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					
					if(selectionKey.isConnectable()){
						SocketChannel socketChannelConnect = (SocketChannel) selectionKey.channel();
						socketChannelConnect.configureBlocking(false);
						
						socketChannelConnect.finishConnect();
						
					}else if(selectionKey.isWritable()){
						
						SocketChannel socketChannelWrite = (SocketChannel) selectionKey.channel();
						socketChannelWrite.configureBlocking(false);
						
						String content = "hello world " ;
						StringBuilder sb = new StringBuilder();
						sb.append(content.length()+"").append("\r\n").append(content);
						
						ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes());
						while(byteBuffer.hasRemaining()){
							socketChannelWrite.write(byteBuffer);
						}
						System.out.println("send:"+sb.toString());
						
//						selectionKey.cancel();
						// 运行后,取消所有事件
						// 实际此时只需去掉写事件
						socketChannel.register(selector, selectionKey.interestOps() & ~SelectionKey.OP_WRITE);
					}else if (selectionKey.isReadable()){
						
						SocketChannel socketChannelRead = (SocketChannel) selectionKey.channel();
						socketChannel.configureBlocking(false);
						
						String head = "" ;
						ByteBuffer temp = ByteBuffer.allocate(1);
						while(!head.endsWith("\r\n")){
							socketChannelRead.read(temp);
							head += new String(temp.array());
							temp.clear();
						}
						int len = Integer.valueOf(head.substring(0, head.length()-2)).intValue();
						
						ByteBuffer byteBuffer = ByteBuffer.allocate(len);
						while(byteBuffer.hasRemaining()){
							socketChannelRead.read(byteBuffer);
						}
						String content = new String(byteBuffer.array());
						System.out.println("content:"+content);
					}
					iterator.remove();
				}
			}
		}
		
	}

}

分享到:
评论

相关推荐

    Java_NIO-Selector.rar_java nio_selector

    Selector是Java NIO框架中的核心组件,它使得单个线程能够管理多个通道(Channels),从而提高系统资源利用率并优化性能。下面我们将详细探讨Java NIO中的Selector机制。 1. **Selector的作用** Selector的主要功能...

    【IT十八掌徐培成】Java基础第27天-04.NIO-Selector-Server-Client.zip

    课程中的视频文件"Java基础第27天-04.NIO-Selector-Server-Client.avi"很可能包含了实际的代码演示和讲解,帮助学习者直观理解Selector的工作原理以及如何在Java中实现NIO服务器和客户端的交互。通过观看视频和动手...

    【IT十八掌徐培成】Java基础第27天-03.NIO-Selector.zip

    本教程由知名IT教育专家徐培成讲解,主要聚焦于NIO中的Selector组件,它是NIO系统中的核心部分,用于管理多个通道(Channels)并监听它们的事件。 Selector允许单线程处理多个通道,极大地提高了程序的并发性能。在...

    Java NIO实战开发多人聊天室

    01-Java NIO-课程简介.mp4 05-Java NIO-Channel-FileChannel详解(一).mp4 06-Java NIO-Channel-FileChannel详解(二).mp4 08-Java NIO-Channel-...23-Java NIO-Selector-示例代码(客户端).mp4 24

    httpcore-nio-4.3.jar包

    NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector)。通道可以读写数据,缓冲区用于临时存储数据,选择器则用于监听多个通道的事件,实现了多路复用,从而实现非阻塞I/O。 3. **HttpCore NIO...

    基于matrix-nio和nio-template构建的管理员和个人协助.zip

    2. **Selector**:选择器允许单个线程监控多个通道,当通道准备好进行读写操作时,选择器会通知我们。 3. **Buffer**:缓冲区用于存储数据,NIO中的缓冲区提供了更高效的数据存取方式。 4. **Pipe**:管道用于在两个...

    android-socket-nio-master.zip

    在Socket通信中,NIO主要通过Selector和Channel两个核心组件来实现。Selector负责监听多个Channel的读写事件,而Channel则代表了与操作系统进行I/O操作的通道。当数据准备好时,Selector会返回一个包含就绪通道的...

    httpcore-nio-4.4.6.zip

    它利用Java NIO的Selector机制,实现非阻塞I/O,能够同时处理多个连接请求,提高了系统并发能力。 3. **Entity Encoders/Decoders**:实体编码器和解码器用于处理HTTP消息体,支持各种内容编码格式,如GZIP压缩、...

    JAVA-NIO-DEMO

    - **选择器使用**:通过Selector监听多个通道的就绪状态,实现多路复用,提高程序并发性能。 - **非阻塞I/O**:展示如何使用非阻塞模式避免线程被长时间阻塞,提高系统资源利用率。 这个Demo将帮助你理解如何在实际...

    nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio

    在NIO服务端编程中,通常会使用`Selector`来监听多个通道的事件,当有新的连接、数据可读或写操作完成时,`Selector`会通知程序,从而避免了传统IO模型中必须为每个连接创建一个线程的开销。 标签“nio nio-socket ...

    java网络编程NIO视频教程

    Java NIO-Selector-示例代码(服务端) - **主要内容**:提供服务端使用Selector的示例代码。 - **学习目标**:掌握服务端多路复用的实现。 #### 25. Java NIO-Selector-示例代码(完善) - **主要内容**:进一步优化...

    java-NIO-demo

    选择器(Selector)是NIO中的多路复用机制。通过一个选择器,我们可以监控多个通道的状态,例如是否有数据可读、可写或者有异常发生。这样,一个单独的线程就可以处理多个连接,大大提高了服务器的并发能力。 在...

    Java NIO——Selector机制解析三(源码分析)

    本文将深入探讨Java NIO中的Selector机制,并通过源码分析来理解其实现原理。 Selector机制是Java NIO中的核心组件,它允许单线程同时监控多个通道(Channels)的状态变化,例如连接就绪、数据可读或可写等。这种...

    socket-nio-single-reactor.zip

    与传统的I/O模型(BIO)不同,NIO不依赖于线程池来处理并发连接,而是通过选择器(Selector)来轮询和监听通道(Channel)上的事件。这使得NIO在资源利用率和性能上有了显著提升。 在"socket-nio-single-reactor....

    Java_NIO类库Selector机制解析.doc

    Java_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.doc

    Java-NIO类库Selector机制解析.docx

    四、Selector Selector是NIO类库中的核心机制,用于实现多个异步I/O操作的集中管理。Selector可以看作是一个面向对象的select()函数,用于管理多个Channel的I/O操作。Selector提供了register()、select()和close()...

    java nio Selector的使用-客户端

    Selector是Java NIO中的核心组件之一,它允许单个线程处理多个通道(channels)的读写事件,极大地提高了服务器的并发能力。本篇文章将深入探讨如何在Java NIO中使用Selector处理客户端的I/O请求。 首先,我们需要...

    Java-NIO-系列教程

    #### 四、Selector 详解 ##### 1. Selector 功能 Selector 是 Java NIO 中用于处理多个 Channel 的核心组件。它可以监听多个 Channel 上的 I/O 事件,并且当某个 Channel 准备好进行 I/O 操作时,会通知应用。 ###...

    NIO-实践-多线程实例

    NIO用于高性能Socket编程由来已久,网络也有较为丰富的原理和源代码。我这里主要介绍几点经验总结: 1.Selector.select()在筛选就绪的SelectionKey的时候,采用的是阻塞模式。同时只要在就绪的SelectionKey列表中有...

Global site tag (gtag.js) - Google Analytics