java nio选择器

先贴个NIO服务端程序

package com.xf.nio;

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;

/**
 * NIO服务端
 */
public class NIOServer {
	// 通道管理器
	private Selector selector;

	/**
	 * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
	 * 
	 * @param port
	 *            绑定的端口号
	 * @throws IOException
	 */
	public void initServer(int port) throws IOException {
		// 获得一个ServerSocket通道
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		// 设置通道为非阻塞
		serverChannel.configureBlocking(false);
		// 将该通道对应的ServerSocket绑定到port端口
		serverChannel.socket().bind(new InetSocketAddress(port));
		// 获得一个通道管理器
		this.selector = Selector.open();
		// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
		// 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	/**
	 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
	 * 
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public void listen() throws IOException {
		System.out.println("服务端启动成功!");
		// 轮询访问selector
		while (true) {
			// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
			selector.select();
			// 获得selector中选中的项的迭代器,选中的项为注册的事件
			Iterator<?> ite = this.selector.selectedKeys().iterator();
			while (ite.hasNext()) {
				SelectionKey key = (SelectionKey) ite.next();
				// 删除已选的key,以防重复处理
				ite.remove();
				// 客户端请求连接事件
				if (key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel) key.channel();
					// 获得和客户端连接的通道
					SocketChannel channel = server.accept();
					// 设置成非阻塞
					channel.configureBlocking(false);

					// 在这里可以给客户端发送信息哦
					channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));
					// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
					channel.register(this.selector, SelectionKey.OP_READ);

					// 获得了可读的事件
				} else if (key.isReadable()) {
					read(key);
				}

			}

		}
	}

	/**
	 * 处理读取客户端发来的信息 的事件
	 * 
	 * @param key
	 * @throws IOException
	 */
	public void read(SelectionKey key) throws IOException {
		// 服务器可读取消息:得到事件发生的Socket通道
		SocketChannel channel = (SocketChannel) key.channel();
		// 创建读取的缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(100);
		channel.read(buffer);
		byte[] data = buffer.array();
		String msg = new String(data).trim();
		System.out.println("服务端收到信息:" + msg);
		ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
		channel.write(outBuffer);// 将消息回送给客户端
	}

	/**
	 * 启动服务端测试
	 * 
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		NIOServer server = new NIOServer();
		server.initServer(8000);
		server.listen();
	}

}

下面是最重要,含义也比较多的一句,建立了通道(Channel),选择器(Selector),选择键(SelectionKey)三者之间的关系

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

这句代码,表示把channel注册到选择器上面[但这个方法明有点歧义],可以阅读源代码,加深理解

1.选择器提供询问通道是否准备好了执行I/O操作的能力,包括接受新的链接,和读取数据,操作系统检测到有数据来到时,会更新通道状态,选择器就可以知道那些通道准备好了

2.选择键SelectKey在channel注册到selector上时创建,封装了通道和选择器的注册关系,其包含了对通道和选择器的引用

 

贴个openJDK源码地址,NIO好多源码都没有

http://www.docjar.com/html/api/sun/nio/ch/

 

一个好的策略是对所有的可选择通道使用一个选择器,并将对就绪通道的服务委托给其他线

程。您只用一个线程监控通道的就绪状态并使用一个协调好的工作线程池来处理共接收到的数据。

根据部署的条件,线程池的大小是可以调整的(或者它自己进行动态的调整)。对可选择通道的管

理仍然是简单的,而简单的就是好的。

第二个场景中,某些通道要求比其他通道更高的响应速度,可以通过使用两个选择器来解决:

一个为命令连接服务,另一个为普通连接服务。但这种场景也可以使用与第一个场景十分相似的办

法来解决。与将所有准备好的通道放到同一个线程池的做法不同,通道可以根据功能由不同的工作

线程来处理。它们可能可以是日志线程池,命令/控制线程池,状态请求线程池,等等。

 

posted on 2013-08-28 22:54  鸟枪变大炮  阅读(240)  评论(0)    收藏  举报