NIO中提供了选择器(Selector 类似底层操作系统提供的IO复用器:select、poll、epoll),也叫做多路复用器,作用是检查一个或者多个NIO Channel(通道)的状态是否是可读、可写。。。可以实现单线程管理多个channel,也可以管理多个网络请求
**Channel:**通道,用于IO操作的连接,在Java.nio.channels包下定义的,对原有IO的一种补充,不能直接访问数据需要和缓冲区Buffer进行交互
通道主要实现类:
SocketChannel:通过TCP读写网络中的数据,一般客户端的实现
ServerSocketChannel:监听新进来的TCP连接,对每一个连接都需要创建一个SocketChannel。一般是服务端的实现
Buffer:缓冲区,IO流中的数据需要经过缓冲区交给Channel
public class NIOServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
//创建ServerSocketChannel通道实例
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9998));
System.out.println("服务端启动了");
//将serverSocketChannel设置为非阻塞 configureBlocking设置阻塞非阻塞 false:非阻塞 true:阻塞
serverSocketChannel.configureBlocking(false);
//创建selector选择器
Selector selector = Selector.open();
//将通道serverSocketChannel注册到选择器selector,关注可接受事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//等待监听结果,调用选择器的select阻塞等待,直到有事件发生才返回
while (selector.select() > 0) {
Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//是否是可接受事件
if (selectionKey.isAcceptable()) {
System.out.println("可接受事件");
//有新用户连接
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
//接受客户端的连接,通过accept(不在阻塞)接受一个SocketChannel通道
SocketChannel socketChannel = serverSocketChannel1.accept();
//设置socketChannel为非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到选择器selector选择器,关注可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
//是否是可读事件
if (selectionKey.isReadable()) {
System.out.println("可读事件");
//获取SocketChannel通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//创建Buffer
ByteBuffer buffer = ByteBuffer.allocate(100);
//进行读取操作
socketChannel.read(buffer);
//进行读写模式的切换
buffer.flip();
//将数据从Buffer中读取
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
//打印结果
System.out.println("客户端:"+socketChannel.getRemoteAddress()+new String(bytes,0,bytes.length));
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
try {
if (serverSocketChannel != null) {
serverSocketChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1、实例化通道:ServerSocketChannel
2、绑定端口:通过ServerSocketChannel实例调用bindI()方法绑定端口
3、将ServerSocketChannel设置为非阻塞
4、实例化选择器(IO复用器)Selector
5、将ServerSocketChannel注册给选择器,并且关注accept事件
6、监听事件是否完成,selector.select,如果事件未完成则一直阻塞直到事件完成
7、获取已完成事件的集合并遍历,判断是否是accept事件,是,则调用accept方法,获取SocketChannel通道
8、设置SocketChannel为非阻塞,并将SocketChannel注册到选择器Selector,并关注read事件
9、监听事件是否完成,若有事件完成,则判断是否是read读事件
10、通过SocketChannel通道读取数据(Buffer中),读完数据循环事件监听,即步骤6
11、关闭资源:ServerSocketChannel,SocketChannel,Selector
public class NIOClient {
public static void main(String[] args) {
SocketChannel socketChannel = null;
try {
//创建SocketChannel通道
socketChannel = SocketChannel.open();
//设置socketChannel为非阻塞
socketChannel.configureBlocking(false);
//创建Selector选择器
Selector selector = Selector.open();
//主动的进行连接,connect操作不在会阻塞,会直接返回,如果连接成功返回true ,连接还未完成返回false
if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",9998))) {
//当前连接操作未完成
//将SocketChannel注册到选择器,并关注可连接事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//等待连接完成
selector.select();
Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//是否是可连接事件
if (selectionKey.isConnectable()) {
//可连接事件完成
SocketChannel channel = (SocketChannel) selectionKey.channel();
//连接操作完成
channel.finishConnect();
}
}
}
//连接成功,给服务端发送消息
ByteBuffer buffer = ByteBuffer.allocate(100);
//将发送的数据写到Buffer中
buffer.put("hello tulun\n".getBytes());
//读写模式的切换
buffer.flip();
socketChannel.write(buffer);
//关闭资源
selector.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socketChannel != null) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1、实例化通道:SocketChannel
2、设置SocketChannel为非阻塞
3、实例化复用器:Selector
4、连接服务器connect()(该方法不会阻塞直接返回结果,返回为Boolean,是否连接成功)
5、若返回为false,则将SocketChannel注册到复用器中,并监听connect可读事件
6、监听复用器事件是否完成(Selector.select),判断完成集合中是否有可连接事件,将可连接事件完成(channel.finishConnet())
7、给服务端发送消息,channel.write()操作
8、关闭资源:selector、SocketChannel
在BIO中connect是一个可阻塞方法,在NIO中设置为非阻塞,交给IO复用器来监听事件是否完成(connect可连接事件),内核帮助监听事件是否完成,必须先触发事件发生,发生之后内核才能监听事件是否完成。客户端来主动连接服务端(Connect),在NIO中将SocketChannel通道设置为非阻塞,当前connect操作会立即返回(true:连接已经完成 false:当前事件已经触发,当并没有完成连接,就需要将连接等待的过程交给内核来关注),即将为完成的连接注册到选择器上。
public static void main(String[] args) {
SocketChannel socketChannel = null;
try {
//创建SocketChannel通道
socketChannel = SocketChannel.open();
//设置socketChannel为非阻塞
socketChannel.configureBlocking(false);
//创建Selector选择器
Selector selector = Selector.open();
//主动的进行连接,connect操作不在会阻塞,会直接返回,如果连接成功返回true ,连接还未完成返回false
if (!socketChannel.connect(new InetSocketAddress("127.0.0.1",9998))) {
//当前连接操作未完成
//将SocketChannel注册到选择器,并关注可连接事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//等待连接完成
selector.select();
Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//是否是可连接事件
if (selectionKey.isConnectable()) {
//可连接事件完成
SocketChannel channel = (SocketChannel) selectionKey.channel();
//连接操作完成
channel.finishConnect();
}
}
}
//注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
Scanner scanner = new Scanner(System.in);
//连接成功,给服务端发送消息
ByteBuffer buffer = ByteBuffer.allocate(100);
while (scanner.hasNext()) {
String msg = scanner.nextLine();
buffer.put((msg+"\n").getBytes());
//读写模式的切换
buffer.flip();
socketChannel.write(buffer);
//关注服务端的返回 read
selector.select();
Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
//将数据读取到Buffer中
buffer.clear();
channel.read(buffer);
//读写模式切换
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String s = new String(bytes);
System.out.println(s);
}
}
if ("".equals(msg) || "exit".equals(msg)) break;
//Buffer是重复使用,需要进行清空
buffer.clear();
}
//关闭资源
selector.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socketChannel != null) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NIO中一个selector可以关注多个用户的连接(即一个线程可以同时处理多个用户的通信),为了并发用户量能够处理更多,可以使用NIO+多线程的形式来处理。
NIO+多线程的处理思路:主线程主要来接收客户端的连接(accept),子线程处理用户的IO操作
主线程接收到客户端连接socketchannel通道,将SocketChannel交给子线程。selector选择器需要给子线程嘛?
如果子线程和主线程共用一个选择器:主线程注册的是可接受事件。子线程注册可读事件,即主线程处理可接受事件,子线程处理可读事件,选择器有返回结果,假如当前子线程获取到就绪事件(可读事件、可连接事件),
主线程和子线程分别使用各自的选择器。
服务端是固定数量为2的线程池,当进行IO操作是,子线程还在一致执行,意味着没有空闲的线程,引起结果就是两个用户请求可以处理,新用户请求没法继续处理了。
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
//创建ServerSocketChannel通道实例
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9998));
System.out.println("服务端启动了");
//将serverSocketChannel设置为非阻塞 configureBlocking设置阻塞非阻塞 false:非阻塞 true:阻塞
serverSocketChannel.configureBlocking(false);
//创建selector选择器
Selector selector = Selector.open();
//将通道serverSocketChannel注册到选择器selector,关注可接受事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//子线程以线程池的形式提供
ExecutorService executorService = Executors.newFixedThreadPool(10);
//等待监听结果,调用选择器的select阻塞等待,直到有事件发生才返回
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//是否是可接受事件
if (selectionKey.isAcceptable()) {
System.out.println("可接受事件");
//有新用户连接
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
//获取新用户channel
SocketChannel socketChannel = serverSocketChannel1.accept();
System.out.println(Thread.currentThread().getName()+":客户端:"+socketChannel.getRemoteAddress()+" 连接上。。。");
//将通道交给子线程
executorService.submit(new NIOServerHandler(socketChannel));
}
}
}
} catch (Exception e) {
}
}
public class NIOServerHandler implements Runnable {
//通过主线程将socketChannel获取到
private SocketChannel socketChannel;
//创建selector实例
private Selector selector = null ;
public NIOServerHandler(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
try {
//在一个子线程中只需要创建一个selector实例
if (selector == null)
selector = Selector.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到选择器中,并且关注可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
int num;
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
//是否是可读事件
if (selectionKey.isReadable()) {
//获取SocketChannel通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//创建Buffer
ByteBuffer buffer = ByteBuffer.allocate(100);
//进行读取操作
socketChannel.read(buffer);
//进行读写模式的切换
buffer.flip();
//将数据从Buffer中读取
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, 0, bytes.length);
//给客户端回复消息
buffer.clear();
buffer.put(("echo:"+msg).getBytes());
//读写模式切换
buffer.flip();
//回复消息
socketChannel.write(buffer);
//打印结果
System.out.println(Thread.currentThread().getName()+"客户端:"+socketChannel.getRemoteAddress()+" 消息:"+msg);
if ("".equals(msg)|| "exit".equals(msg)){
System.out.println(Thread.currentThread().getName()+"客户端:"+socketChannel.getRemoteAddress()+" 下线");
//当前注册的感兴趣事件取消
selectionKey.cancel();
//关闭通道
socketChannel.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}