欢迎大家访问我的个人博客:L_SKH’Blog
一、首先要知道NIO的三大组件以及他们之间的关系:
Selector 、 Channel 和 Buffer 的关系图
关系图的说明:
1.每个channel 都会对应一个Buffer
2.Selector 对应一个线程, 一个线程对应多个channel(连接)
3.该图反应了有三个channel 注册到 该selector //程序
程序切换到哪个channel 是由事件决定的, Event 就是一个重要的概念
Selector 会根据不同的事件,在各个通道上切换
4.Buffer 就是一个内存块 , 底层是有一个数组
数据的读取写入是通过Buffer, 这个和BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换
channel 是双向的, 可以返回底层操作系统的情况, 比如Linux , 底层的操作系统通道就是双向的.
二、基本流程
说明:
1.当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel
2.Selector 进行监听 select 方法, 返回有事件发生的通道的个数.
3.将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel
4.注册后返回一个 SelectionKey, 会和该Selector 关联(集合)
5.进一步得到各个 SelectionKey (有事件发生)。在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()
6.最后可以通过 得到的 channel , 完成业务处理
三、代码实现
3.1NIOServer
package org.skh.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
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;
/**
* @Created IntelliJ IDEA.
* @Author L_SKH
* @Date 2019/11/26 17:19
*/
/**
* 要注意这个buffer 客户端和channel之间存在
* 服务器与通道之间也存在
*/
public class T07_NIOServer {
public static void main(String[] args) throws IOException {
//1.创建ServerSocketChannel 相当于ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.开启一个selector对象
Selector selector = Selector.open();
//3.绑定端口 在服务器监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//4.设置为非阻塞
serverSocketChannel.configureBlocking(false);
//5.把serverChannel注册到Selector 关注accept事件 客户端连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.循环等待客户端连接
while (true) {
//让selector阻塞式的监听一秒看是否有事件发生
if (selector.select(1000) == 0) {
System.out.println("逝去的一秒内无客户端连接....");
continue;
}
//!=0说明有事件发生 所以我们要取得selectionKey集合
//得到关注事件的集合 进而得到对应的channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//使用迭代器遍历
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) { //更具相应的key的事件来进行处理
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
//连接事件 生成socketchannel 并注册到selector
//设置其事件为只读 并关联一个Buffer
SocketChannel socketChannel = serverSocketChannel.accept();
//将socketchannel设置为非阻塞模型
socketChannel.configureBlocking(false) ;
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {
//通过key反向获取对应的channel
SocketChannel channel = (SocketChannel) key.channel();
//得到关联的buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer); //服务器与客户端之间也有buffer 所以要读取channel到buffer
System.out.println("From 客户端: " + new String(buffer.array(),0,buffer.position()));
}
//处理完之后就消除 避免重复处理
keyIterator.remove();
}
}
}
}
3.2NIOClient
package org.skh.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Created IntelliJ IDEA.
* @Author L_SKH
* @Date 2019/11/26 18:49
*/
public class T08_NIOClient {
public static void main(String[] args) throws IOException {
//得到一个channel
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false) ;
InetSocketAddress address = new InetSocketAddress("localhost", 6666);
if (!socketChannel.connect(address)){
//建立连接的过程需要时间 但因为是非阻塞的所以不会停留等待
//可以去做其他事情 所以我们写一个while循环 以避免还没有连接就去发送数据
//同时也可以很好的看到非阻塞的效果
while (!socketChannel.finishConnect()){
System.out.println("Doing Others While Connecting!!!");
}
}
//已建立好连接 向客户端发送数据
String msg = "Rush_SKH" ;
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer) ;
System.in.read() ;
}
}
3.3Console:
逝去的一秒内无客户端连接....
逝去的一秒内无客户端连接....
From 客户端: Rush_SKH
逝去的一秒内无客户端连接....
逝去的一秒内无客户端连接....
逝去的一秒内无客户端连接....
逝去的一秒内无客户端连接....