NIO:(
No Blocking IO
或者New IO
),非阻塞IO,是相对于传统IO来说,其意义在于网络IO的非阻塞性。
其工作流程:
Buffer :数据的载体
Channel:客户端到服务端的通道,负责数据(Buffer)的传输
Selector:多路复用器,用于监听Channel的事件(可对应多个Channel)
使用NIO模拟一套客户端->服务端的群组聊天功能,具体实现:
1.Server端
public static void chatServer() throws IOException {
//开启一个socket channel
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//通道绑定端口9999
socketChannel.bind(new InetSocketAddress(9999));
//设置当前channel为非阻塞(核心)
socketChannel.configureBlocking(false);
//开启一个多路复用器
Selector selector = Selector.open();
//当前服务端channel注册到多路复用器,监听accept事件
socketChannel.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();
//根据选择键事件类型 Acceptable事件为新的客户端连接到当前channel
if (selectionKey.isAcceptable()) {
//把新连接到当前channel的客户端channel注册到当前多路复用器, 并监听read事件
SocketChannel channel = socketChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
//如果当前选择键事件为read,说明客户端有数据可以读取
if (selectionKey.isReadable()) {
SelectableChannel channel = selectionKey.channel();
if (channel instanceof SocketChannel) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
((SocketChannel) channel).read(buffer);
buffer.flip();
//将接受到的消息转发给所有客户端(除当前消息的发送端)
//这里要使用 selector.keys() 获取当前多路复用器注册的所有channel
for (SelectionKey key : selector.keys()) {
if (!key.equals(selectionKey) && key.channel() instanceof SocketChannel) {
SelectableChannel channel1 = key.channel();
//转发buffer之前先mark,便于发送完后reset到开始位置
//否则会导致 buf只能被发送一次
buffer.mark();
((SocketChannel) channel1).write(buffer);
buffer.reset();
}
}
buffer.clear();
}
}
//已经处理过的选择键要及时移除,否则会导致多次重复处理导致异常
selectionKeys.remove(selectionKey);
}
}
}
2.Client端
public static void chatClient() throws IOException {
//创建连接通道
SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
//设置通道为非阻塞
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
//新开一个线程用于接收别的客户端发送的消息 实现群聊功能
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ByteBuffer recBuf = ByteBuffer.allocate(1024);
int read = 0;
try {
read = channel.read(recBuf);
} catch (IOException e) {
e.printStackTrace();
}
if (read > 0) {
recBuf.flip();
byte[] bytes = new byte[recBuf.limit()];
recBuf.get(bytes);
recBuf.clear();
System.out.println(new String(bytes));
}
}
}
}).start();
while (true){
String s = scanner.nextLine();
if (s.equalsIgnoreCase("exit")){
channel.close();
break;
}else {
String send = "user" + index + ":" + s;
buffer.put(send.getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();
}
}
}