Selector
在新IO中Selector是一个非常重要的概念,在NIO之前通过IO和Socket构造网络通信程序时,所有的服务端将以阻塞式与客户端进行连接,本章内容将介绍通过Selector构造一个非阻塞式网络服务,在学习如何使用Selector构造非阻塞网路之前,我们先看下相关的API。
Selector类的常用方法:
public static Selector open() throws IOException Opens a selector
public abstract int select(long timeout) throws IOException; Selects a set of keys whose corresponding channels are ready for I/O operations.
public abstract Set selectedKeys()Returns this selector’s selected-key set.
在使用NIO开发时,需要使用SelectableChannel类向Select类注册,在网络编程中,我们可以使用其子类,ServerSocketChannel和SocketChannel,以下是关于SelectableChannel的常用方法(阻塞模式与注册):
public abstract SelectableChannel configureBlocking(boolean block) Adjusts this channel’s blocking mode.
public final SelectionKey register(Selector sel, int ops) Registers this channel with the given selector, returning a selection key.
public static ServerSocketChannel open() Opens a server-socket channel.
public abstract ServerSocket socket(); Retrieves a server socket associated with this channel.
在register中需要指定一个Selector对象,这个对象可以通过Selector.open方法获得,而Selector操作集合(ops)则需要从SelectionKey中获取,如下:
public static final int OP_READ = 1 << 0; Operation-set bit for read operations.
public static final int OP_WRITE = 1 << 2; Operation-set bit for write operations.
public static final int OP_CONNECT = 1 << 3; Operation-set bit for socket-connect operations.
public static final int OP_ACCEPT = 1 << 4; Operation-set bit for socket-accept operations.
下面通过一个TCP服务端与客户端通信的实例演示如何运用上述API完成非阻塞方式通信:
服务端程序代码:
package com.ray.nio.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Set;
/**
* 使用NIO的服务器端不会发生阻塞,所以多个客户端可以连接服务器。
* @author xuleilei
*
*/
public class SelectorServer {
public static void main(String[] args) {
Selector selector = null;
ServerSocketChannel serverSocketChannel = null;
try {
// 创建一个Selector
selector = Selector.open();
// 创建一个ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
// 启动端口监听
InetSocketAddress ip = new InetSocketAddress(10001);
serverSocketChannel.socket().bind(ip);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 监听事件
while (selector.select() > 0) {
// 事件来源列表
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 删除当前事件
iterator.remove();
// 判断事件类型
if (selectionKey.isAcceptable()) {
// 连接事件
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接:" + clientChannel.socket().getInetAddress().getHostName() + ":" + clientChannel.socket().getPort());
} else if (selectionKey.isReadable()) {
// 读取数据事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 读取数据
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
ByteBuffer byteBuffer = ByteBuffer.allocate(50);
socketChannel.read(byteBuffer);
byteBuffer.flip();
String msg = decoder.decode(byteBuffer).toString();
System.out.println("收到" + msg);
// 写入数据
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
socketChannel.write(encoder.encode(CharBuffer.wrap("server" + msg)));
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭
try {
selector.close();
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端代码实现:
package com.ray.nio.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Set;
/**
* Client程序有两个线程,一个线程负责监听键盘,第二个线程负责将键盘的输入内容通过NIO发送出去。
* @author xuleilei
*
*/
public class SelectorClient {
/**
* 主线程
* @param args
*/
public static void main(String[] args) {
ClientRunnable clientRunnable = new ClientRunnable();
Thread thread = new Thread(clientRunnable);
thread.start();
//输入,输出流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String readline = "";
try {
while((readline = bufferedReader.readLine())!=null){
if(readline.equals("bye")){
clientRunnable.close();
System.exit(0);
}
clientRunnable.sendMessage(readline);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientRunnable implements Runnable{
private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
private Selector selector = null;
private SocketChannel socketChannel = null;
private SelectionKey selectionKey = null;
public ClientRunnable() {
//创建selector
try {
selector = Selector.open();
//创建Socket并注册
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
selectionKey = socketChannel.register(selector,SelectionKey.OP_CONNECT);
//连接到远程地址
InetSocketAddress ip = new InetSocketAddress("localhost",10001);
socketChannel.connect(ip);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
//监听事件
while(selector.select() > 0){
//事件来源列表
Set<SelectionKey> keySet = selector.keys();
Iterator<SelectionKey> iterator = keySet.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
//删除当前事件
iterator.remove();
//判断事件类型
if(key.isConnectable()){
//连接事件
//取得当前SocketChannel对象
SocketChannel channel = (SocketChannel)key.channel();
if(channel.isConnectionPending()){
channel.finishConnect();
}
channel.register(selector, SelectionKey.OP_READ);
System.out.println("连接服务器端成功!");
}else if(key.isReadable()){
//读取数据事件
SocketChannel channel = (SocketChannel)key.channel();
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(50);
channel.read(buffer);
String msg = decoder.decode(buffer).toString();
System.out.println("收到:"+msg);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭
try {
selector.close();
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发送消息
* @param msg
*/
public void sendMessage(String msg){
try {
SocketChannel client = (SocketChannel)selectionKey.channel();
client.write(encoder.encode(CharBuffer.wrap(msg)));
} catch (CharacterCodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 关闭客户端
*/
public void close(){
try {
selector.close();
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}