Java NIO之多个Selector的实现

欢迎大家讨论,我也是接触时间不长,有问题欢迎大家指正。欢迎转载,转载请注明出处

楔子

最近在研究JAVA NIO的相关知识,发现网上多是三种类型的研究文章,一是单Reactor单Selector,二是主从Reactor单Selector,三就是无Reactor单Selector,有一篇是一个Selector绑定两个地址的文章。但是随着链接数增多,单Selector肯定不能满足对于系统性能的要求,所以必然会出现多个selector配合工作的情况。可是我找了很久,也只找到一篇讨论Mina和Grizzly的多selector的实现,并没有自己去实现一个简单的多selector的例子(文章地址: 点击打开链接)。这些天闲来无事,写成一个多selector配合工作的例子,与大家共享。本实例只是关注如何进行多selector配合工作,例子有很多不完美的地方,比如对于包拆分和包合并的判断处理还没有实现,如果错误之处,还请大家指正。

背景

直接来。最基本的Java NIO流程图,如下:


我们知道,如果连接数增多,并且读写量很大,那么服务器的压力会非常大。于是大师们研究出使用reactor模式来搞定。于是出现了下面的实现:


(图片来源: 点击打开链接
Reactor负责Select,如果发现是OP_ACCEPT,则交给acceptor处理,如果发现是读写,则交给Thread Pool来处理。读写新开了线程,不会阻塞整个主线程的运行。我们今天的例子就是从这个架构发展而来。

实例

多selector,顾名思义,多个selector来配合完成NIO的操作。这样做的好处是可以最大限度的榨取服务器的资源。我们使用一个selector来处理accept的工作,完成绑定,监听,一个selector完成读写,发送的工作。我们称这个版本为V1.1。
首先来看Server.Java,这是Server端main函数的所在地,代码如下:
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;

public class Server {
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		 int port = 8008;          
		 new Thread(new ServerReactor(port)).start();
	}
}
主要是新开一个ServerReactor的线程,主要负责初始化和绑定。
来看ServerReactor.java:
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

public class ServerReactor implements Runnable {
	static Logger logger = Logger.getLogger(ServerReactor.class);
	private SelectorProvider selectorProvider = SelectorProvider.provider();  
	private ServerSocketChannel serverSocketChannel;
	public ServerReactor(int port) throws IOException {  
        serverSocketChannel = selectorProvider.openServerSocketChannel(); //ServerSocketChannel.open();  
        ServerSocket serverSocket = serverSocketChannel.socket();  
        serverSocket.bind(new InetSocketAddress("localhost", port), 1024);  
        serverSocketChannel.configureBlocking(false);  
        logger.debug(String.format("Server : Server Start.----%d", port));
    }  

    public void run() {  
    	try {
			new ServerDispatcher(serverSocketChannel, SelectorProvider.provider()).execute();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }  
}
首先创建了ServerSocketChannel,然后将localhost和8008端口绑定在channel上,然后设置为非阻塞接着创建了ServerDispatcher对象,调用Execute。
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;

import org.apache.log4j.Logger;

public class ServerDispatcher{
	private ServerSocketChannel ssc;
	private Selector[] selectors = new Selector[3];

	private SelectorProvider selectorProvider;
	
	private final static Logger logger = Logger.getLogger(ServerDispatcher.class);
	public ServerDispatcher(ServerSocketChannel ssc, SelectorProvider selectorProvider) throws IOException {
		this.ssc = ssc;
		this.selectorProvider = selectorProvider;
		for (int i = 0; i < 3; i++)
		{
			selectors[i] = selectorProvider.openSelector();
		}
	}
	
	public Selector getAcceptSelector()
	{
		return selectors[0];
	}
	
	public Selector getReadSelector()
	{
		return selectors[1];
	}
	
	public Selector getWriteSelector()
	{
		return selectors[1];
	}
	
	public void execute() throws IOException
	{
		SocketHandler r = new SocketAcceptHandler(this, ssc, getAcceptSelector());
		new Thread(r).start();
		
		r = new SocketReadHandler(this, ssc, getReadSelector());
		new Thread(r).start();

		r = new SocketWriteHandler(this, ssc, getWriteSelector());
		new Thread(r).start();
	}
	
	
}
大家可以看到,ServerDispatcher管理着3个selector,然后启动了三个线程,ServerAcceptHandler,ServerReadHandler和ServerWriteHandler。accept使用了一个selector,read和write使用了一个selector。
这三个类继承自SocketHandler类,我们来看这个基类的方法:
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
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;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.log4j.Logger;

public abstract class SocketHandler implements Runnable{
	protected Selector selector;
	protected SocketChannel socketChannel = null;
	protected ServerSocketChannel serverSocketChannel;
	protected ServerDispatcher dispatcher;
	protected final static Logger logger = Logger.getLogger(SocketHandler.class);
	private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();;
	public SocketHandler(ServerDispatcher dispatcher, ServerSocketChannel sc, Selector selector) throws IOException{
		this.selector = selector;
		this.serverSocketChannel = sc;
		this.dispatcher = dispatcher;
	}
	
	public abstract void runnerExecute(int readyKeyOps) throws IOException;
	
	public final void run()
	{
		while(true)
		{
			readWriteLock.readLock().lock();
			try {
				int keyOps = this.Select();

				runnerExecute(keyOps);
				
				Thread.sleep(1000);
			} catch (Exception e) {
				// TODO: handle exception
				logger.debug(e.getMessage());
			}
			finally {
				readWriteLock.readLock().unlock();
			}
		}
	}
	
	private int Select() throws IOException
	{	
		int keyOps = this.selector.selectNow();
		
		boolean flag = keyOps > 0;
		
		if (flag)
		{
			Set
    
    
     
     readyKeySet = selector.selectedKeys();
			Iterator
     
     
      
       iterator = readyKeySet.iterator();
			while (iterator.hasNext()) {
				SelectionKey key = iterator.next();
				iterator.remove();
				keyOps = key.readyOps();
				if (keyOps == SelectionKey.OP_READ || keyOps == SelectionKey.OP_WRITE)
				{
					socketChannel = (SocketChannel)key.channel();
					socketChannel.configureBlocking(false);
				}
			}
		}
		
		return keyOps;
	}
}

     
     
    
    
大家看到这里,就会比较清楚了,每一个线程启动后,都会调用selector.selectNow方法来监听SelectionKey,找到了以后,就会进行处理。可是怎么叫accept处理OP_ACCEPT, read处理OP_READ,Write处理OP_WRITE呢。我都是放到了具体的子类的runnerExecute里面进行判断。
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import org.apache.log4j.Logger;

public class SocketAcceptHandler extends SocketHandler{
	private static final Logger logger = Logger.getLogger(SocketAcceptHandler.class);
	
	public SocketAcceptHandler(ServerDispatcher dispatcher, ServerSocketChannel sc, Selector selector)
			throws IOException {
		super(dispatcher, sc, selector);
		serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT, this);
	}
	
	@Override
	public void runnerExecute(int readyKeyOps) throws IOException {
		// TODO Auto-generated method stub
		if (readyKeyOps == SelectionKey.OP_ACCEPT)
		{
			socketChannel = serverSocketChannel.accept();
			socketChannel.configureBlocking(false);
			logger.debug("Server accept");
			
			socketChannel.register(dispatcher.getReadSelector(), SelectionKey.OP_READ);
		}
	}
}
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
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;

import org.apache.log4j.Logger;

public class SocketReadHandler extends SocketHandler{
	static Logger logger = Logger.getLogger(SocketReadHandler.class);
	private SelectionKey selectionKey;
	private  int BLOCK = 4096;    
	private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
	
	public SocketReadHandler(ServerDispatcher dispatcher, ServerSocketChannel sc, Selector selector) throws IOException{
		super(dispatcher, sc, selector);
	}
	
	@Override
	public void runnerExecute(int readyKeyOps) throws IOException {
		// TODO Auto-generated method stub
		int count = 0;
		if (SelectionKey.OP_READ == readyKeyOps)
		{
            receivebuffer.clear();
            count = socketChannel.read(receivebuffer);   
            if (count > 0) {  
            	logger.debug("Server : Readable.");  
            	receivebuffer.flip();  
	            byte[] bytes = new byte[receivebuffer.remaining()];
	            receivebuffer.get(bytes);
	            String body = new String(bytes, "UTF-8"); 
	            logger.debug("Server : Receive :" + body);  	            
	            socketChannel.register(dispatcher.getWriteSelector(), SelectionKey.OP_WRITE);
            }  
		}
	}
}
package com.jjzhk.NIOPractice.MultiSelector;

import java.io.IOException;
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;

import org.apache.log4j.Logger;

public class SocketWriteHandler extends SocketHandler{
	static Logger logger = Logger.getLogger(SocketReadHandler.class);
	private  int BLOCK = 4096;  
	private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
	private static int Index = 1;
	public SocketWriteHandler(ServerDispatcher dispatcher, ServerSocketChannel sc, Selector selector) throws IOException{
		super(dispatcher, sc, selector);
	}
	
	@Override
	public void runnerExecute(int readyKeyOps) throws IOException {
		// TODO Auto-generated method stub
		if (readyKeyOps == SelectionKey.OP_WRITE)
		{
            logger.debug("Server : Writable.");  
        	String data = String.format("%d", Index);
        	byte[] req = data.getBytes();
            sendbuffer.clear();  

            logger.debug(String.format("Server : Write %s", data));
                                
            sendbuffer = ByteBuffer.allocate(req.length);
			sendbuffer.put(req);
			sendbuffer.flip();        			
            socketChannel.write(sendbuffer);    
            Index++;
            socketChannel.register(dispatcher.getReadSelector(), SelectionKey.OP_READ);
		}
	}
}

扩展

         首先,可以将Read和Write的Selector分开,这样理论上会更快,但是会出现一个问题。那就是服务器处理完OP_ACCEPT和第一次的read之后,就会一直处理写,直到接收到客户端的数据才开始读,这样客户端读取的东西就是错误的。我们需要加一个变量来控制,如果上一次是写,那么必须处理完读之后,才能开始第二次的写。
         其次,我们可以改造ServerDispatcher,现在维护的是3个selector,其实,应该维护3个SocketHandler,每个Handler里面自己创建新的selector,或者维护两个SocketHandler,一个accept,一个Read/Write,那么这就是什么的架构?这就是Grizzly的核心架构。如果我们维护两个线程池,第一个池子里面 只有一个线程--accept,第二个池子里面,放入读写的Handler,那么这就是Mina或者是Netty的基础架构。对于Netty来说,accept就是父类的handler,读写Handler就是子类的handler。

参考及代码

代码地址:https://github.com/JJZHK/NIOPractice.git
参考了以下图书及文章:
1. 《Netty权威指南》
2.http://www.iteye.com/topic/482269
3.http://blog.csdn.net/thomescai/article/details/6865406






  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
### 回答1: Java NIO 中的 Selector 可以用于多路复用 I/O,它可以同时监控多个 Channel 的 IO 状态,如读写就绪等,从而让你的程序可以同时处理多个网络连接。 使用 Selector 的基本流程如下: 1. 创建 Selector 对象:使用 `Selector.open()` 方法。 2. 创建并配置 Channel:每个 Channel 都必须注册到 Selector 上。 3. 向 Selector 注册感兴趣的事件:使用 `SelectionKey` 对象将 Channel 和感兴趣的事件绑定。 4. 通过 `select()` 方法监控 Channel:该方法会阻塞,直到至少有一个 Channel 处于就绪状态。 5. 处理就绪的 Channel:通过 `selectedKeys()` 方法获取所有就绪的 Channel,然后遍历每一个 Key,并根据 Key 的事件状态进行相应的处理。 6. 关闭 Selector:使用 `close()` 方法关闭 Selector。 以上就是 Selector 的基本使用方法。希望这些信息能帮助你理解和使用 Java NIO 中的 Selector。 ### 回答2: Java NIO(New Input/Output)提供了一种非阻塞I/O的能力,其中的selector是一种重要的组件。它允许程序通过一个单线程来监听多个通道上的事件并做出相应的处理。 使用Selector主要包括以下步骤: 1. 创建Selector实例: Selector selector = Selector.open(); 2. 创建Channel并设置为非阻塞模式: 在使用Selector之前,需要确保Channel处于非阻塞模式,例如SocketChannel或ServerSocketChannel: SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); 3. 将Channel注册到Selector上: 通过SelectionKey来表示Channel的注册状态,包括感兴趣的操作集合及其附加的数据。可以使用以下方法将Channel注册到Selector上: SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ); 4. 进行事件监听: 使用Selector的select()方法进行事件监听,它会阻塞,直到有一个或多个事件发生: int readyChannels = selector.select(); if (readyChannels == 0) { continue; } 5. 获取已就绪的事件集合: 通过调用selector.selectedKeys()方法获取已经就绪的事件集合: Set<SelectionKey> selectedKeys = selector.selectedKeys(); 6. 遍历已就绪的事件集合并处理: 遍历selectedKeys集合,处理每一个就绪的事件: Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { // 可读事件处理逻辑 } if (key.isWritable()) { // 可写事件处理逻辑 } keyIterator.remove(); // 处理完毕后需要手动移除该事件,避免重复处理 } 7. 关闭Selector: 使用完Selector后需要及时关闭: selector.close(); 使用Selector可以实现多个通道的事件监听和处理,极大地提高了应用程序的性能和资源利用率。需要注意的是,在使用Selector时,一个线程可以管理多个Channel,但要谨慎处理每个Channel上的事件,以避免阻塞整个Selector处理线程。 ### 回答3: Java NIO(New I/O)是一种非阻塞I/O操作的Java API。它提供了一组用于高效处理I/O操作的类和接口。其中,SelectorNIO的核心组件之一,用于实现非阻塞I/O。 Selector是一个类似于调度员的对象,它可以同时监视多个通道的I/O事件。使用Selector可以实现单线程同时管理多个通道的I/O操作,提高了系统的效率。 使用Selector的主要步骤如下: 1. 创建一个Selector对象:通过调用Selector.open()方法创建一个Selector对象。 2. 将通道注册到Selector上:将需要监视的通道注册到Selector上,例如SocketChannel、ServerSocketChannel等。通过调用通道的register()方法完成注册。 3. 设置通道的非阻塞模式:通过调用通道的configureBlocking(false)方法将通道设置为非阻塞模式。 4. 选择通道:通过调用Selector的select()方法选择通道,并返回已准备就绪的通道的数量。 5. 处理选择的通道:通过调用Selector的selectedKeys()方法获取选择的通道集合,可以通过遍历通道集合进行相应的读写操作。 6. 取消选择的通道:通过调用SelectionKey的cancel()方法取消选择的通道的注册。 示例代码如下: ```java Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("example.com", 80)); socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isConnectable()) { // 处理连接就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); buffer.flip(); // 处理读取到的数据 } keyIterator.remove(); } } ``` 以上是一个简单的Selector的使用示例,通过这些步骤,可以实现多个通道的非阻塞I/O操作的监视和处理。需要注意的是,Selector是基于事件驱动的,可以实现高效的I/O操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值