简介
- 概念:在IO多路复用模型中,引入了一种新的系统调用select/epoll,查询IO的就绪状态。通过该系统调用可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓存区可读/可写),内核能够将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行相应的IO系统调用。在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程,可以不断轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。
- 理解:餐厅安装了电子屏幕用来显示点餐的状态,这样我和女友逛街一会,回来就不用去询问服务员了,直接看电子屏幕就可以了。这样每个人的餐是否好了,都直接看电子屏幕就可以了,这就是典型的IO多路复用,
过程
特点
-
优点:使用一个查询就绪状态的线程就可以同时同时轮询成千上万个连接。系统不必要创建和维护大量的线程,大大减小了系统的开销。
-
缺点:select/epoll调用都是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是这个读写过程时阻塞的。
应用
- 连接数目比较多以及连接时间短的系统架构,例如聊天服务器
简单实现
- 服务器
public class MyNIOServer {
static class SocketChannelHandler extends Thread{
private SocketChannel socketChannel;
public SocketChannelHandler(SocketChannel socketChannel){
this.socketChannel = socketChannel;
}
@Override
public void run(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
try {
socketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//将socketChannel注册到选择器上,让其关注读事件
socketChannel.register(selector,SelectionKey.OP_READ);
//调用select方法去监听
while (selector.select() > 0){
//通过SelectedKeys拿到选择器集合的迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//遍历集合
while (iterator.hasNext()){
//获取当前事件
SelectionKey selectionKey = iterator.next();
//将此事件移除
iterator.remove();
//判断当前是事件是不是读事件
if (selectionKey.isReadable()){
//拿到当前channel
socketChannel = (SocketChannel) selectionKey.channel();
//进行读取操作
socketChannel.read(buffer);
//读写转换
buffer.flip();
//从缓冲区读取数据
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
//清空缓冲区
buffer.clear();
//数据打印到控制台
System.out.println("客户端"+socketChannel.getRemoteAddress()+"发送的数据为: "+ new String(bytes));
//将channel注册到选择器上 让其关注写事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
//判断当前是事件是不是读事件
if (selectionKey.isWritable()){
//拿到当前channel
socketChannel = (SocketChannel) selectionKey.channel();
//从控制台获取数据
String s = scanner.next();
//将数据装入到缓冲区
buffer.put(s.getBytes());
//读写转换
buffer.flip();
//发送数据
socketChannel.write(buffer);
//清空buffer
buffer.clear();
//将channel注册到选择器上 让其关注读事件
socketChannel.register(selector,SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
//创建ServerSocketChannel通道
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(2222));
System.out.println("服务器已启动...");
//将通道设置为非阻塞的
serverSocketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//将ServerSocketChannel注册到选择器上,让其关注可接受连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//调用选择器的select方法监听 select为阻塞方法,有事件发生时才会但会
while (selector.select() > 0){
//通过SelectedKeys拿到选择器集合的迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//遍历集合
while (iterator.hasNext()){
//获取当前事件
SelectionKey selectionKey = iterator.next();
//将此事件移除
iterator.remove();
//判断当前是事件是不是 请求连接事件
if (selectionKey.isAcceptable()){
//有客户端的请求连接
//拿到注册到选择器中的ServerSocketChannel
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
//建立连接 拿到连接的SocketChannel
SocketChannel socketChannel = channel.accept();
System.out.println("与客户端"+socketChannel.getRemoteAddress()+"建立连接成功...");
//将SocketChannel交给子线程 启动子线程去通信
new SocketChannelHandler(socketChannel).start();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (serverSocketChannel != null){
serverSocketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 客户端
public class MyNIOClient {
public static void main(String[] args) {
SocketChannel socketChannel = null;
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
//创建SocketChannel通道,绑定ip端口
socketChannel = SocketChannel.open();
//设置通道为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端已启动...");
//创建一个选择器
Selector selector = Selector.open();
//连接服务器
if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",2222))){
//连接失败注册到选择器上关注连接时间
socketChannel.register(selector,SelectionKey.OP_CONNECT);
//监听
while (selector.select() > 0) {
//遍历已完成事件的集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//判断是否存在可连接事件
if (selectionKey.isConnectable()) {
//获取selectionKey的channel
SocketChannel channel = (SocketChannel) selectionKey.channel();
//完成建立连接
channel.finishConnect();
System.out.println("与服务器"+socketChannel.getRemoteAddress()+"连接建立成功...");
//将channel注册到选择器上 让其关注写事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
//判断当前是事件是不是写事件
if (selectionKey.isWritable()) {
//拿到当前channel
socketChannel = (SocketChannel) selectionKey.channel();
//从控制台获取数据,将数据装入到缓冲区
buffer.put(new Scanner(System.in).next().getBytes());
//读写转换
buffer.flip();
//发送数据
socketChannel.write(buffer);
//清空buffer
buffer.clear();
//将channel注册到选择器上 让其关注读事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
//判断当前是事件是不是读事件
if (selectionKey.isReadable()) {
//拿到当前channel
socketChannel = (SocketChannel) selectionKey.channel();
//将数据读取到缓冲区中
socketChannel.read(buffer);
//读写转换
buffer.flip();
//从缓冲区读取数据
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
//清空缓冲区
buffer.clear();
//数据打印到控制台
System.out.println("服务器" + socketChannel.getRemoteAddress() + "发送的数据为: " + new String(bytes));
//将channel注册到选择器上 让其关注写事件
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if (socketChannel != null){
socketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}