NIO2 重要
/**
* IO模型
* BIO 同步阻塞IO
* NIO 同步非阻塞IO
* IO多路复用 一个select/epoll系统调用
* AIO 异步非阻塞IO
*
* Java中NIO
* 提供了选择器(Selector)类似操作系统提供的select/epoll,也叫做IO多路复用器
* 作用是检查一个channel(通道)的状态是否是可读、可写、可连接、可接收的状态,为了
* 实现单线程管理多个channel,其实也就是管理多个网络请求
*
* Channel通道
* java.nio.channels 即可以读,又可以写,不直接和数据源打交道,主要和缓冲区Buffer进行交互
* ServerSocketChannel 服务器端
* SocketChannel 客户端
*
* Buffer 缓冲区
* IO流中的数据通过缓冲区交给Channel
*
* 服务器端:
* 1、实例化 ServerSocketChannel
* 2、绑定端口 通过ServerSocketChannel调用bind()方法
* 3、设置ServerSocketChannel为非阻塞 configir....
* 4、实例化Selector选择器
* 5、将ServerSocketChannel注册到选择器上 ServerSocketChannel.register()
* 6、监听是否有新的事件 接收(连接)事件/读写事件/ Selector.select()
* 7、获取已完成事件的集合,对于这个集合进行遍历,如果发现是Accept事件
* 8、则进行accept调用,获取SocketChannel,注册到Selector上,关注read事件
* 9、监听是否有read读事件
* 10、通过SocketChananel通道来读取数据,其中通过buffer作为传输介质
* 11、关闭资源 SocketChannel Selector ServerSocketChannel
*
* 客户端:
* 1、实例化 SocketChannel
* 2、设置 SocketChannel 为非阻塞
* 3、实例化 Selector
* 4、连接服务器connect,在这个方法中提供ip地址和端口号,注意:这个方法不是一个阻塞方法,如果连接
* 失败返回false,连接成功返回true
* 5、如果是false,则将SocketChannel注册到Selector选择器中,监听connect可连接事件
* 6、监听selector中是否可完成事件,遍历可完成事件的集合,判断该事件是否是可连接事件
* 7、connect方法就会返回true
* 8、给服务器端发送消息,channel.write
* 9、关闭资源,selector SocketChannel
*
NIO的实现
服务器端
public class MyNIOServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
Selector selector = null;
try {
//创建ServerSocket实例
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9999));
System.out.println("服务器端启动了...");
//设置当前serverSocketChannel为非阻塞模式
serverSocketChannel.configureBlocking(false);
//实例化Selector
selector = Selector.open();
//将serverSocketChannel注册到选择器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//进行监听,调用selector.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("可接受请求...");
//这个返回当前事件所创建的channel
ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
//接收这个事件获取SocketChannel
SocketChannel socketChannel = channel.accept();
//设置socketChannel为非阻塞的
socketChannel.configureBlocking(false);
//将socketChannel注册到选择器中
socketChannel.register(selector, SelectionKey.OP_READ);
}
//判断是否是读事件
if(selectionKey.isReadable()){
System.out.println("读请求...");
//获取可读事件的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//进行读取操作
ByteBuffer buffer = ByteBuffer.allocate(100);
//通过channel将数据读取到buffer中
socketChannel.read(buffer);
//进行读写模式切换
buffer.flip();
//将数据从buffer中读取
byte[] bytes = new byte[buffer.remaining()];
//获取buffer缓冲区中的数据到byte数组中
buffer.get(bytes);
System.out.println("客户端:"+socketChannel.getRemoteAddress()+" 发送的消息为: "+new String(bytes, 0, bytes.length));
buffer.clear();
if(socketChannel.read(buffer) == -1){
socketChannel.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(serverSocketChannel != null){
try {
serverSocketChannel.close();
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端
public class MyNIOClient {
public static void main(String[] args) {
SocketChannel socketChannel = null;
try {
//创建SocketChannel通道
socketChannel = SocketChannel.open();
//设置SocketChananel为非阻塞
socketChannel.configureBlocking(false);
//实例化Selector
Selector selector = Selector.open();
System.out.println("客户端启动了...");
if(!socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999))){
//将这个通道注册到选择器上
socketChannel.register(selector, SelectionKey.OP_CONNECT);
//监听Selector选择器
selector.select();
//遍历已完成事件的集合
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("连接建立成功了...");
//连接成功,给服务器端发送信息
ByteBuffer buffer = ByteBuffer.allocate(100);
//将所要的数据写入到buffer中
//buffer.put(("hello NIOServer\n").getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String info = reader.readLine();
buffer.put((info+"\n").getBytes());
//mark position limit capacity
//进行读写模式切换
buffer.flip();
//使用通道发送buffer
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socketChannel != null){
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
多线程实现NIO 服务器端
class NIOServerHandler implements Runnable{
private SocketChannel socketChannel;
//创建Selector实例
private Selector selector;
public NIOServerHandler(SocketChannel socketChannel){
this.socketChannel = socketChannel;
if(selector == null){
try {
selector = Selector.open();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
//关注socketChannel通道的读写就绪事件,进而处理
try {
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
while(selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, 0, bytes.length);
System.out.println(Thread.currentThread().getName()+", 客户端:"+channel.getRemoteAddress()+", 消息: "+msg);
buffer.clear();
buffer.put(("hello client\n").getBytes());
buffer.flip();
channel.write(buffer);
if("".equals(msg) || "exit".equals(msg)){
System.out.println(Thread.currentThread().getName()+"客户端:"+channel.getRemoteAddress()+"下了 ");
key.cancel();
channel.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MyMultiThreadNIOServer {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
//创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9998));
//将SerSocketChannel置为非阻塞
serverSocketChannel.configureBlocking(false);
//创建Selector选择器
Selector selector = Selector.open();
//将serverSocketChannel注册到选择器上,关注可接受事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//使用固定数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
while(selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if(key.isAcceptable()){
//是可接受事件
System.out.println(Thread.currentThread().getName()+"关注可接受事件");
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel();
SocketChannel channel = serverSocketChannel1.accept();
System.out.println("客户端:"+channel.getRemoteAddress()+"已连接");
//将SocketChannel channel提交给子线程
pool.submit(new NIOServerHandler(channel));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO中重要组件
* NIO中重要组件
* Channel
* 类似流,即可以从通道中读取数据,也可以写数据到通道中
* 通道可以异步地读写,通道中的数据先要读取到一个Buffer中,或者从一个Buffer中写入
* 读数据:将数据从channel读到buffer
* 写数据:从buffer中写入到channel通道中
* 常用实现类:
* DatagramChannel 通过UDP的方式读写网络中的数据通道
* SocketChannel 通过TCP的方式读写网络中的数据,一般用于客户端
* ServerSocketChanneel 通过TCP的方式读写网络中的数据,一般用于服务器端
* FileChannel 用于读写操作文件的通道
*
* Buffer
* 缓冲区,与NIO Channel交互,数据是从通道读取进入缓冲区,从缓冲区写入到通道中
*
* 使用Buffer注意点:
* 1、写数据到buffer
* 2、调用buffer.flip
* 3、从Buffer中读取数据
* 4、buffer.clear/buffer.compact
* 当从buffer中读取数据,要么调用clear方法清空buffer中的数据,要么调用compact方法啊清空已经读过
* 的数据,任何未读到数据会被移动缓冲区的起始位置,新写入的数据将放到缓冲区未读数据的后面
*
* Buffer实现依赖3个指针 position limit capacity
* position:取决于Buffer处于读模式还是写模式,
* · 写数据到Buffer中,position表示当前位置,初始的值为0
* · 读数据时,从某个额特定位置开始去读,需要将buffer从写模式切换为读模式,position会被重置为0
* limit:
* · 写模式下,表示最多能往里写多少数据
* · 读模式下,表示最多能读到的数据
* capacity:
* 作为buffer内存块,有一个固定的大小
*
* 示例:ByteBuffer.allocate(100);
* 1、position = 0 limit = capacity = 100
* 2、buffer.put("hello\n") position=6 limit = capacity = 100
* 3、buffer.flip() position=0 limit = position capacity = 100
* 4.buffer.get() 3 position=3 limit = 6 capacity = 100
*
* Buffer的方法:
* ByteBuffer.allocate() 分配空间
* ByteBuffer.allocateDirect() 在堆外分配空间
* ByteBuffer.wrap(byte[] bytes) 通过byte数据创建一个缓冲区
* flip()
* capacity()
* limit()
* positio()
*
* Selector
* 选择器,也叫做多路复用器,作用是检查一个或多个channel通道是否处于可读、可写、可连接(管理多个网络请求)
* 优势:
* 单线程管理多个网络连接,相比于之前的多线程使用了更多的线程,效率反而更高,减少了线程上下文切换
*
* Selector的使用
* 1、Selector.open();
* 2、channel.register(xxx, SelectionKey的四种事件)
* 3、selector.select() 这个方法是一个阻塞方法,如果有事件就绪则返回
*
* Selector维护三种类型的selectionKey集合
* selector.selectedKeys() 已选择键的集合
* selector.keys() 已注册键的集合
* selector.cancelKey() 已取消键的集合
*
* SelectionKey的四种事件
* 一个通道可以注册多个事件 SelectionKey.OP_READ | SelectionKey.OP_WRITE
*
* 思考:NIO和BIO的区别