本文将介绍NIO最重要的部分Selector,并最终完成一个简易的没有图形界面的多人聊天室。
一、简介
Selector像一个注册中心,把各个可选择通道注册在上面。
SelectableChannel就是可以注册在Selector上的节点。
SelectionKey是维护两者之间的注册关系。
二、使用
1.实例化Selector对象
Selector selector = Selector.open();
2.将channel注册到selector上,此时channel必须是非阻塞模式的
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
第二个参数表示Selector感兴趣的操作集合,可以选择下面四种操作中的若干个,使用位或 | 连接:
SelectionKey.OP_CONNECT //连接 1<<3
SelectionKey.OP_ACCEPT //接受 1<<4
SelectionKey.OP_READ //读 1
SelectionKey.OP_WRITE //写 1<<2
SelectionKey中的interestOps是Selector感兴趣的操作集合
int interestSet=selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
3.为SelectionKey绑定附加对象
(1)在注册的时候直接绑定
SelectionKey key=channel.register(selector,SelectionKey.OP_READ,theObject);
(2)在绑定完成之后附加
selectionKey.attach(theObject);
取出附加对象
selectionKey.attachment();
清除附加对象
selectionKey.attach(null);
4.通过Selector选择通道
Selector维护三种类型SelectionKey集合:
已注册的键的集合(Registered key set)
所有与选择器关联的通道所生成的键的集合称为已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过keys()方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发UnsupportedOperationException。
已选择的键的集合(Selected key set)
已经准备好的,并且包含于键的interest集合中的操作。这个集合通过selectedKeys()方法返回。键可以直接从这个集合中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出UnsupportedOperationException。
已取消的键的集合(Cancelled key set)
包含了cancel()方法被调用过的键(这个键已经被无效化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。
三、NIO聊天室实例
服务端:
package com.haiziwang.nio.chatroom.advanced;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @author haopeng.yuan@haiziwang.com
* @version V1.0
* Copyright 2019 Kidswant Children Products Co., Ltd.
* @Title:
* @Description:
* @date
*/
public class NIOGroupChatServer{
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private static final int PORT = 6667;
private NIOGroupChatServer(){
try{
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
}
}
private void listen(){
try{
while (true){
int count = selector.select();
if (count > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("监听到"+socketChannel.getRemoteAddress()+"上线");
}
if(selectionKey.isReadable()){
readData(selectionKey);
}
iterator.remove();
}
} else {
System.out.println("服务端正在等待...");
}
}
}catch (IOException e){
e.printStackTrace();
}
}
private void readData(SelectionKey selectionKey){
SocketChannel socketChannel = null;
try{
socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = socketChannel.read(buffer);
if (count>0){
String msg = new String(buffer.array());
System.out.println("收到客户端"+socketChannel.getRemoteAddress()+"消息:"+msg);
//转发消息给其他客户端
sendMsg2Others(msg, socketChannel);
}
}catch(IOException e){
try {
System.out.println(socketChannel.getRemoteAddress()+"下线了..");
//取消注册
selectionKey.cancel();
socketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
private void sendMsg2Others(String msg, SocketChannel self) throws IOException{
System.out.println("服务端转发消息中...");
for (SelectionKey selectionKey : selector.keys()) {
Channel targetChannel = selectionKey.channel();
if (targetChannel instanceof SocketChannel && targetChannel != self) {
SocketChannel dest = (SocketChannel) targetChannel;
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
dest.write(buffer);
}
}
}
public static void main(String[] args) {
NIOGroupChatServer groupChatServer = new NIOGroupChatServer();
groupChatServer.listen();
}
}
客户端:
package com.haiziwang.nio.chatroom.advanced;
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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author haopeng.yuan@haiziwang.com
* @version V1.0
* Copyright 2019 Kidswant Children Products Co., Ltd.
* @Title:
* @Description:
* @date
*/
public class NIOGroupChatClient {
private final String HOST = "127.0.0.1";
private final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
private NIOGroupChatClient() throws IOException{
selector = Selector.open();
socketChannel = socketChannel.open(new InetSocketAddress(HOST, PORT));
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + "准备就绪");
}
private void sendMsg(String msg){
msg = username + "说:" + msg;
try{
socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
}catch(IOException e){
e.printStackTrace();
}
}
private void readMsg(){
try{
int count = selector.select();
if (count>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove();
} else {
System.out.println("没有可以使用的通道...");
}
} catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException{
NIOGroupChatClient client = new NIOGroupChatClient();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
while(true){
client.readMsg();
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
});
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String s = scanner.nextLine();
client.sendMsg(s);
}
service.shutdown();
}
}
把服务端起起来,客户端起多个的时候要勾选 Allow Parallel Run
运行效果:
参考文章: