NIO:将通道绑定端口,并且将通道注册到selector并且监听特定事件
与外围设备数据通信需要channel通道,如像外部输出数据,先将数据写入缓冲区通过通道输出。如果从外部读取数据,需要通过通道channel读取到缓冲区,然后从缓冲区数据获取处理。
Select 模型 的底层实现可以通过服务提供者配置,支持的方式有select、poll、epoll。在注册了感兴 趣的I/O 事件给操作系统内核后,便可以继续其它的操作。内核会通知这些事件的发生, 改变SelectionKey 维持的这些I/O 事件的状态,然后使用select/poll/epoll 方式便可以将 这些SelectionKey 提取出来,从而获取它们所绑定的SelectableChannel 类型通道,从而 在数据准备就绪的情况下进行I/O 操作。
Selector内部原理:实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙(SelectionKey表示 SelectableChannel 在 Selector 中的注册的标记。 )来读取这个channel的内容
缓冲区内部细节
1 初始化
2 第一次读取3个字节到缓冲区
3 第二次读取2个字节到缓冲区
4 调用Flip,将limit设置position,position设置为0 ,limit下一个不可用的索引
5 调用clear,将limit设置为capacity,position设置为0
NIO核心观念:通道和缓冲区(channel和Buffer)
1) 块操作
2) 非阻塞IO
复用机制采用select模型,注册感兴趣IO事件给操作系统后,一旦这些事件发生,内核会改变这些事件的状态,通过检查这些事件的状态,便可以查找到注册过的、准备就绪的I/O 事件,然后可以流畅地进行I/O操作。
传统IO阻塞调用:
流 I/O 是阻塞调用的。不论是read 方法还是write 方法都能阻塞(block)一个线程直到字节被真正地读取或者写入。这意味着如果流不能立即被读取或者写入字节(通常是因为网络连接繁忙),Java 就会挂起这个调用的线程,处于等待的状态。常用Socket 的读写操作都是阻塞式的,就是说每次read/write 掉用,在数据被读入/写出前,调用线程都处于阻塞的状态.
多线程耗费资源,特别是多线程间切换很耗费资源。
NIO特征:
l)面向块的I/O 操作
2)非阻塞的I/O 操作
3)字符集编码解码
4)内存映射文件
5)文件锁定
,
Reactor 模型:
Reactor 模型采用分而治之的思想,将一个客户端连接的事件分成两类:I/O 事件和非I/O 事件。前者需要等待I/O 准备就绪,后者可以立即执行,因此分别进行处理。I/O 事件包括:Read(读取请求信息)、Send(发送响应信息)。非I/O 事件包括encode、compute、decode。
异步连接池:
首先,用户处理线程调用连接池对象的某个方法(比如sendRequest),把一个能够标识本次请求的Request 对象扔给连接池。之后用户处理线程可以去做别的事情,比如,向其他连接池发送请求。最后当用户线程处理完能做的业务逻辑后,就可以等待连接池返回结果了。
(一) 读写IO:(按块缓冲区)
package java.nio.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Date;
/**
* @Project: NIO
* @Title: CopyFile.java
* @Package
* @Description: JAVA NIO 读取写入文件方面优势
* @author paul.wei2011@gmail.com
* @date May 16, 2013 9:51:13 AM
* @version V1.0
*/
//传统IO一直是性能瓶颈,因为每次按字节读取,耗费磁头寻址次数,磁头寻址时间是固定的,次数多耗费时间就长
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对象是线程安全的
FileChannel fcout = fout.getChannel();
// 每个FileChannel都有一个叫'file position'的概念,该position值决定文件中哪一处的数据接下来被读或写。
// FileChannel position是从底层文件描述符获取的, 当字节被read( ) 或write( ) 方法传输时,文件position会自动更新。
// MappedByteBuffer类使得我们可以通过ByteBuffer API来访问数据文件
byteBuffer(fcin,fcout); //2 秒
mapByteBuffer(fcin,fcout);//1秒
}
// 创建缓冲区
public static void byteBuffer(FileChannel fcin,FileChannel fcout) throws IOException{
ByteBuffer buffer = ByteBuffer.allocate(1024);
// MappedByteBuffer mapBuffer = fcin.map(FileChannel.MapMode.READ_ONLY, 0,fcin.size());
Date start = new Date();
while (true) {
// clear方法重设缓冲区,使它可以接受读入的数据
buffer.clear();
// 从输入通道中将数据读到缓冲区
int r = fcin.read(buffer);
// read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1
if (r == -1) {
break;
}
// flip方法让缓冲区可以将新读入的数据写入另一个通道 ,position赋值给limit,position=0
buffer.flip();
// 将缓冲区内容写入输出通道
fcout.write(buffer);
}
Date end = new Date();
System.out.println("150M byteBuffer spend:"+(end.getTime()-start.getTime())/1000+" sec");//150M 4秒
}
// 创建映射缓冲区 ,把文件的内容被映像到计算机虚拟内存的一块区域,
// 这样就可以直接操作内存当中的数据而无需操作的时候每次都通过I/O去物理硬盘读取文件
public static void mapByteBuffer(FileChannel fcin,FileChannel fcout) throws IOException{
MappedByteBuffer mapBuffer = fcin.map(FileChannel.MapMode.READ_ONLY, 0,fcin.size());
Date start = new Date();
fcout.write(mapBuffer);//将映射缓冲区直接写入输出通道
Date end = new Date();
System.out.println("150M mapByteBuffer spend:"+(end.getTime()-start.getTime())/1000+" sec");//150M 4秒
}
}
(二) 异步IO:
传统IO:
服务端:
public class SynchServerSocket {
private int port=8821;
private ServerSocket serverSocket;
private ExecutorService executorService;//线程池
private final int POOL_SIZE=10;//单个CPU线程池大小
public SynchServerSocket() throws IOException{
serverSocket = new ServerSocket(port);
//Runtime的availableProcessor()方法返回当前系统的CPU数目.
executorService=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*POOL_SIZE);
System.out.println("服务器启动");
}
public void service(){
while(true){
Socket socket=null;
try{
//接收客户连接,只要客户进行了连接,就会触发accept();从而建立连接,
//如果客户端未连接,服务端一直阻塞I/O等待连接,多线程从而耗费CPU及栈资源
socket=serverSocket.accept();
executorService.execute(new Handler(socket));
}catch(Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new SynchServerSocket().service();//服务启动一直等待连接
}
}
class Handler implements Runnable{
private Socket socket;
public Handler(Socket socket){
this.socket=socket;
}
private PrintWriter getWriter(Socket socket) throws IOException{ //根据socket 获取输出流
OutputStream socketOut=socket.getOutputStream();
return new PrintWriter(socketOut,true);
}
private BufferedReader getReader(Socket socket) throws IOException{//根据socket 获取输入流
InputStream socketIn=socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
public String echo(String msg){
return "echo:"+msg;
}
public void run(){
try {
System.out.println("New connection accepted "+socket.getInetAddress()+":"+socket.getPort());
BufferedReader br=getReader(socket);
PrintWriter pw=getWriter(socket);
String msg=null;
while((msg=br.readLine())!=null){
System.out.println(msg);
pw.println(echo(msg));
if(msg.equals("bye"))
break;
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端:
public class SynchClientSocket {
public static void main(String[] args) {
int numTasks = 10;
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < numTasks; i++) {
exec.execute(new Task(i));
}
}
}
class Task implements Runnable{
private Socket socket = null;
private int port=8821;
public final int taskId=0;
private int taskID;
public Task(int taskID){
this.taskID=taskID;
}
@Override
public void run() {
System.out.println("Task " + taskID + ":start");
try {
socket = new Socket("localhost", port);
// 发送关闭命令
OutputStream socketOut = socket.getOutputStream();
socketOut.write("shutdown\r\n".getBytes());
// 接收服务器的反馈
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = null;
while ((msg = br.readLine()) != null)
System.out.println("response:"+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
异步IO(多路复用,通知模式)
服务端:
public class AsynServer implements Runnable{
private ByteBuffer r_buff = ByteBuffer.allocate(1024);
private ByteBuffer w_buff = ByteBuffer.allocate(1024);
private static int port = 8848;
public AsynServer(){
new Thread(this).start();
}
@Override
public void run() {
try{
//多路复用:Selector 各种IO事件注册到Selector,事件发生通知,不同通道对象注册到Selector
Selector selector = Selector.open();
//生成一个服务端口通道ssc
ServerSocketChannel ssc = ServerSocketChannel.open();
//将侦听端设为异步方式
ssc.configureBlocking(false);
//将服务端口通道ssc绑定一个端口
ssc.socket().bind(new InetSocketAddress(port));
//将服务端口通道注册到selector上监听事件为OP_ACCEPT信号
ssc.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("echo server has been set up ......");
while(true){
int n = selector.select();
if (n == 0) {//没有指定的I/O事件发生(监听是否有数据读或写)n>0,才会有selectedKeys
continue;
}
//当有读或写等任何注册的事件发生 时,可以从Selector 中获得相应的SelectionKey,同时从SelectionKey
//中可以找到发生的事件和该事件所发生的具体的SocketChannle继承自SelectableChannel,以获得客户端发送过来的数据。
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
if (key.isAcceptable()) {//侦听端信号触发
ServerSocketChannel server = (ServerSocketChannel) key.channel();
//接受一个新的连接
SocketChannel sc = server.accept();
sc.configureBlocking(false);
//设置该socket的异步信号OP_READ:当socket可读时,
//触发函数DealwithData();
sc.register(selector,SelectionKey.OP_READ);
}else{
if (key.isReadable()) {//某socket可读信号
DealwithData(key);
}
it.remove();
}
// 删除处理过的选择键
it.remove();
}
}
}catch(Exception e){
}
}
public void DealwithData(SelectionKey key) throws IOException{
int count;
//由key获取指定socketchannel的引用
SocketChannel sc = (SocketChannel)key.channel();
r_buff.clear();
//读取数据到r_buff
while((count = sc.read(r_buff))> 0);
//确保r_buff可读
r_buff.flip();
//清空写缓存
w_buff.clear();
//将r_buff内容拷入w_buff
w_buff.put(r_buff);
w_buff.flip();//从写缓冲区向输出通道写之前调用flip
EchoToClient(sc);
w_buff.clear();
r_buff.clear();
}
public void EchoToClient(SocketChannel sc) throws IOException{
while(w_buff.hasRemaining())
sc.write(w_buff);
}
public static void main(String args[]){
if(args.length > 0){
port = Integer.parseInt(args[0]);
}
new AsynServer();
}
}
客户端:
public class AsynClient {
private SocketChannel sc;
private final int MAX_LENGTH = 1024;
private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH);
private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH);
private static String host ;
private static int port = 8848;
public AsynClient(){
try {
InetSocketAddress addr = new InetSocketAddress(host,port);
//生成一个socketchannel
sc = SocketChannel.open();
//连接到server
sc.connect(addr);
while(!sc.finishConnect())
;
System.out.println("connection has been established!...");
while(true){
//回射消息
String echo;
try{
System.err.println("Enter msg you'd like to send: ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//输入回射消息
echo = br.readLine();
//把回射消息放入w_buff中
w_buff.clear();
w_buff.put(echo.getBytes());
w_buff.flip();
}catch(IOException ioe){
System.err.println("sth. is wrong with br.readline() ");
}
//发送消息
while(w_buff.hasRemaining())
sc.write(w_buff);
w_buff.clear();
//进入接收状态
Rec();
//间隔1秒
Thread.currentThread().sleep(1000);
}
}catch(IOException ioe){
ioe.printStackTrace();
}
catch(InterruptedException ie){
ie.printStackTrace();
}
}
public void Rec() throws IOException{
int count;
r_buff.clear();
count=sc.read(r_buff);
r_buff.flip();
byte[] temp = new byte[r_buff.limit()];
r_buff.get(temp);
System.out.println("reply is " + count +" long, and content is: " + new String(temp));
}
public static void main(String args[]){
if(args.length < 1){//输入需有主机名或IP地址
try{
System.err.println("Enter host name: ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
host = br.readLine();
}catch(IOException ioe){
System.err.println("sth. is wrong with br.readline() ");
}
}
else if(args.length == 1){
host = args[0];
}
else if(args.length > 1){
host = args[0];
port = Integer.parseInt(args[1]);
}
new AsynClient();
}
}