阻塞和非阻塞:
阻塞和非阻塞是进程在访问数据的时候,数据内是否准备就绪的一种处理方式
阻塞
往往需要等待缓冲区中的数据准备好之后才处理其他事情,否则一直等待
非阻塞
当我们进程访问我们的数据缓冲区的时候,数据没有准备好的时候, 直接返回,不需要等待。数据有的时候也直接返回。
同步和异步方式
同步和异步都是基于应用程序和操作系统处理IO时间所采用的方式,比如同步应用程序要直接参与IO读写操作
同步
同步的方式在处理IO的时候,必须阻塞在某个方法上面,等待我们的IO时间完成(阻塞IO事件或者通过轮循IO时间的方式)
阻塞到IO事件,阻塞到read或者write,这个时候我们完全不能做自己的事儿。
解决1:让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。
解决2:IO事件的轮循,多路复用技术(select模式)
读写事件交给一个单独的线程来处理, 这个完成io事件的注册功能。还有就是不断的轮循我们的读写缓冲区,看是否有数据准备好, 如果准备好了就通知相应的读写线程。这样的话,以前的读写线程就可以做其他的事情。这个时候,阻塞的不是IO线程,而是select这个线程。
异步
所有的IO读写交给操作系统去处理,然后去做其他事,病不需要去完成真正的IO操作,当操作完成IO后, 给我们的应用程序一个通知就可以。
Java的IO模型
BIO
Jdk1.4之前,我们使用的都是BIO(阻塞IO),阻塞到我们的读写方法,阻塞线程来提供性能。对于线程的开销本来就是性能的浪费。
NIO
jdk1.4之后,linux多路复用技术(select模式)实现IO事件的轮循方式。同步非阻塞的模式(select+非阻塞),是目前的主流方式。例如:Mina,netty。
原理:通过selector(选择器)就相当于管家,管理所有的IO事件,Connection accept 客户端和服务端的读写等。
Selctor(选择器)如何进行管理IO事件:当IO事件注册给我们的选择器的时候,选择器会给我们分配一个key(可以简单的理解成一个事件的标签),当Io事件完成时,会通过key找到相应的管道,然后通过管道发送、接收数据。
数据缓冲区:通过bytebuffer这个类来实现。这个类提供了很多读写的方法,比如get(),put()。
那后台是怎么操作的呢?
服务端:ServerSocketChannel
客户端:SocketChannel
选择器:Selector selector =Select.open();打开选择器。
selectionKey:可以通过他来判断IO事件是否已经就绪。
key.isAccptable:判断是否可以接受客户端的连接。
Key.connectionable:是否可以连接服务端。
Key.isreadable():缓冲区是否可读
key.iswriteable():缓冲区是否可写。
如何获取事件的key?
SelectionKey keys = Selector.selectedKeys();
如何注册?
Channel.regist(selector,Selectionkey.OP_Write);
Channel.regist(selector,Selectionkey.OP_Read);
Channel.regist(selector,Selectionkey.OP_connect);
Channel.regist(selector,Selectionkey.OP_Accept);
AIO
Jdk1.7之后,AIO也叫作NIO2 ,这才是实现真正的异步,学习linux epoll模式。
原理:对于网络通信而言,AIO并没有改变网络通信的基本步骤,只是在其原来的基础上(serversocket,socket)做了一个改进。
对于读和写采用抽象的管道的概念:
Channel:是一个在TCP连接之间的抽象,一个TCP可以对应多个管道,不是以前的方式,只有一个通信管道。减少了TCP连接的次数。
AIO后台是怎么操作的呢?
服务端:AsynchronousServerSocketChannel
客户端:AsynchronousSocketChannel
用户处理器:CompletionHandler借口, 这个接口实现应用程序向操作系统发起IO请求, 当完成后处理具体逻辑,否则做自己该做得事情。主要有两个方法:completed();failed();
总结:
相比于普通IO,NIO的性能要好的多,其中一个原因就是NIO是非阻塞的,利用Selector一个线程可以管理多个通道可以提高CPU的使用率。还有就是,ByteBuffer.allocateDirector()分配的内存使用的是本机内存而不是Java堆上的内存,每一次分配内存时会调用操作系统的os::malloc()函数,直接ByteBuffer产生的数据如果和网络或者磁盘交互都在操作系统的内核空间中发生,不需要将数据复制到Java内存中,很显然执行这种IO操作要比一般的从操作系统的内核空间到Java堆上的切换操作快得多,因为它们可以避免在Java堆与本机堆之间复制数据。
一个服务端代码:
package com.concurrent.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;
public class NIOServer {
/*标识数字*/
private int flag = 0;
/*缓冲区大小*/
private int BLOCK = 4096;
/*接受数据缓冲区*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public NIOServer(int port) throws IOException {
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 检索与此通道关联的服务器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 进行服务的绑定
serverSocket.bind(new InetSocketAddress(port));
// 通过open()方法找到Selector
selector = Selector.open();
// 注册到selector,等待连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----8888:");
}
// 监听
private void listen() throws IOException {
while (true) {
// 选择一组键,并且相应的通道已经打开
selector.select();
// 返回此选择器的已选择键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 处理请求
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受请求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count=0;
// 测试此键的通道是否已准备好接受新的套接字连接。
if (selectionKey.isAcceptable()) {
// 返回为之创建此键的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的连接。
// 此方法返回的套接字通道(如果有)将处于阻塞模式。
client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
// 注册到selector,等待连接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
客户端代码:
package com.concurrent.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOClient {
/*标识数字*/
private static int flag = 0;
/*缓冲区大小*/
private static int BLOCK = 4096;
/*接受数据缓冲区*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
/*服务器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 8888);
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 打开socket通道
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞方式
socketChannel.configureBlocking(false);
// 打开选择器
Selector selector = Selector.open();
// 注册连接服务端socket动作
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 连接
socketChannel.connect(SERVER_ADDRESS);
// 分配缓冲区大小内存
Set<SelectionKey> selectionKeys;
Iterator<SelectionKey> iterator;
SelectionKey selectionKey;
SocketChannel client;
String receiveText;
String sendText;
int count=0;
while (true) {
//选择一组键,其相应的通道已为 I/O 操作准备就绪。
//此方法执行处于阻塞模式的选择操作。
selector.select();
//返回此选择器的已选择键集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
selectionKey = iterator.next();
if (selectionKey.isConnectable()) {
System.out.println("client connect");
client = (SocketChannel) selectionKey.channel();
// 判断此通道上是否正在进行连接操作。
// 完成套接字通道的连接过程。
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("完成连接!");
sendbuffer.clear();
sendbuffer.put("Hello,Server".getBytes());
sendbuffer.flip();
client.write(sendbuffer);
}
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count=client.read(receivebuffer);
if(count>0){
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("客户端接受服务器端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendbuffer.clear();
client = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客户端向服务器端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}
执行后,服务端客户端可以相互通信。
最后上一个NIO操作文件的代码
package com.concurrent.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Channel 和 buffer 是 NIO 是两个最基本的数据类型抽象。
Buffer:
– 是一块连续的内存块。
– 是 NIO 数据读或写的中转地。
Channel:
– 数据的源头或者数据的目的地
– 用于向 buffer 提供数据或者读取 buffer 数据 ,buffer 对象的唯一接口。
– 异步 I/O 支持
* @author Administrator
*
*/
public class CopyFile {
public static void main(String[] args) throws Exception {
String infile = "C:\\copy.sql";
String outfile = "C:\\copy.txt";
// 获取源文件和目标文件的输入输出流
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 获取输入输出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// clear方法重设缓冲区,使它可以接受读入的数据
buffer.clear();
// 从输入通道中将数据读到缓冲区
int r = fcin.read(buffer);
// read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1
if (r == -1) {
break;
}
// flip方法让缓冲区可以将新读入的数据写入另一个通道
buffer.flip();
// 从输出通道中将数据写入缓冲区
fcout.write(buffer);
}
}
}