IO基础和NIO

40 阅读4分钟

一、IO模型

1、5种IO模型
  1. 阻塞IO模型

  1. 非阻塞IO模型:一般会轮询检查有没有数据到来,没有就返回一个EWOULDBLOCK错误。

  1. IO复用模型

多个进程的IO可以注册到一个复用器上,然后用一个进程调用该select,select会监听所有注册进来的IO。

当没有IO在内核缓存区有数据时,select会阻塞,当任一IO有数据了,select就返回,并且自己或者通知另外的进程(注册进程)来再次发起读取IO,读取内核区准备好的数据

  1. 信号驱动IO模型:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数。当数据准备就绪时,就为该进程生成了一个SIGIO信号,通过信号回调通知应用程序来读取数据
  2. 异步IO:告知内核启动某个操作,并让内核在整个操作完成后通知我们。

2、IO多路复用模型

通过将多个IO的阻塞复用到同一个select的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。

目前支持IO多路复用的系统调用有select,pselect,poll,epoll

特性

  1. 支持一个进程打开的socket描述符不受限制
  2. IO效率不会随着FD数目的增加而线性下降,传统select、poll会因为socket集合大时,因为每次都要扫描整个集合,导致效率会下降。但是epoll不存在这个问题
  3. 使用mmap加速内核与用户空间的消息传递
  4. epoll的API更加简单

二、NIO

NIO到底是什么?

有人说NIO是New IO,因为他对比于以前的I/O库是新增的,但是NIO类库的目标是让Java支持非阻塞I/O,所以也有人称之为Non-block I/O。

2.1 缓冲区Buffer

在NIO库中,所有的数据都是在Buffer中处理的。读取数据时,是直接读取到缓冲区中;写数据时,又是写入缓冲区中。缓冲区实质上是一个数组,但不仅仅是一个数组,还维护了一些对数据的结构化访问以及维护读写位置(limit)等信息。

2.2 通道Channel

Channel是一个通道,是全双工的,同时支持读写操作。

2.3 多路复用器Selector

多路复用器是Java NIO编程的基础。简单来讲Selector会不断轮询注册在上面的Channel,如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SeletionKey可以获取就绪的Channel的集合,进行后续的IO处理。

三、NIO服务器、客户端

3.1 NIO实现一个TimeServer

TimeServer:

package com.mmc.springbootstudy.nio;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class MultiplexerTimeServer implements Runnable {

    private Selector selector;

    private ServerSocketChannel serverSocketChannel;


    private volatile boolean stop;

    public MultiplexerTimeServer(int port) {
        try{
            selector=Selector.open();
            serverSocketChannel=ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            stop=false;
            System.out.println("The time server is start port:"+port);

        }catch (IOException e){
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop(){
        stop=true;
    }

    @Override
    public void run() {
        while (!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                SelectionKey selectionKey=null;
                while (iterator.hasNext()){
                    selectionKey = iterator.next();
                    iterator.remove();
                    try {
                        handleInput(selectionKey);
                    } catch (Exception e) {
                        e.printStackTrace();
                        if(selectionKey!=null){
                            selectionKey.cancel();
                            if(selectionKey.channel()!=null){
                                selectionKey.channel().close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //多路复用关闭后,注册在上面的Channel和Pipe都会自动关闭
        if(selector!=null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()){
            if(key.isAcceptable()){
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(selector,SelectionKey.OP_READ);
            }

            if(key.isReadable()){
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int read = sc.read(readBuffer);
                if(read>0){
                    readBuffer.flip();
                    byte[] bytes=new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "utf-8");
                    System.out.println("the time server receive order:"+body);
                    if(body.equals("query")){
                        String date = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
                        doWrite(sc,date);
                    }
                }else if(read<0){
                    key.cancel();
                    sc.close();
                }else {
                    //读到0字节,忽略
                }
            }
        }
    }


    private void doWrite(SocketChannel channel,String content) throws IOException {
        if(StringUtils.isNotEmpty(content)){
            byte[] bytes = content.getBytes();
            ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer);
        }
    }
}

package com.mmc.springbootstudy.nio;

public class TimeServer {

    public static void main(String[] args) {
        new Thread(new MultiplexerTimeServer(8899)).start();
    }
}

TimeClient:

package com.mmc.springbootstudy.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 TimeClientHandle implements Runnable {

    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;

    public TimeClientHandle(String host,int port){
        this.host=host;
        this.port=port;
        try {
            selector=Selector.open();
            socketChannel=SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

    }

    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
        }

        while (!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                SelectionKey key=null;
                while (iterator.hasNext()){
                    key=iterator.next();
                    iterator.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if(key!=null){
                            key.cancel();
                            if(key.channel()!=null){
                                key.channel().close();
                            }
                        }
                        e.printStackTrace();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(selector!=null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()){
            SocketChannel socketChannel= (SocketChannel) key.channel();
            if(key.isConnectable()){
                if(socketChannel.finishConnect()){
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    doWrite(socketChannel);
                }else {
                    System.exit(1);
                }
            }

            if(key.isReadable()){
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int read = socketChannel.read(readBuffer);
                if(read>0){
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "utf-8");
                    System.out.println("now is :"+body);
                    this.stop=true;
                }else if(read<0){
                    key.cancel();
                    socketChannel.close();
                }
            }
        }
    }

    private void doConnect() throws IOException {
        //如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
        if(socketChannel.connect(new InetSocketAddress(host,port))){
            socketChannel.register(selector,SelectionKey.OP_READ);
            doWrite(socketChannel);
        }else {
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
        }
    }

    private void doWrite(SocketChannel sc) throws IOException {
        byte[] req="query".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        socketChannel.write(writeBuffer);
        if(!writeBuffer.hasRemaining()){
            System.out.println("Send order 2 server succeed");
        }

    }
}

package com.mmc.springbootstudy.nio;

public class TimeClient {

    public static void main(String[] args) {
        new Thread(new TimeClientHandle("127.0.0.1",8899)).start();
    }
}

3.2 代码示例2

服务端:

package com.mmc.concurrentcystudy.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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Server {

	private Selector selector;
	
	private ByteBuffer readBuffer=ByteBuffer.allocate(1024);     
	private ByteBuffer sendBuffer=ByteBuffer.allocate(1024);     
	
	String str;
	
	public void start() throws IOException {
		//打开服务器套接字
		ServerSocketChannel ssc=ServerSocketChannel.open();
		//设置为非阻塞
		ssc.configureBlocking(false);
		//进行地址绑定
		ssc.bind(new InetSocketAddress("localhost", 8080));
		//通过open找到Selector
		selector=Selector.open();
		//注册到selector
		ssc.register(selector, SelectionKey.OP_ACCEPT);
		while(!Thread.currentThread().isInterrupted()) {
			selector.select();
			Set<SelectionKey> keys = selector.selectedKeys();
			Iterator<SelectionKey> iterator = keys.iterator();
			while(iterator.hasNext()) {
				SelectionKey key = iterator.next();
				if(!key.isValid()) {
					continue;
				}
				if(key.isAcceptable()) {
					accept(key);
				}else if(key.isWritable()) {
					write(key);
				}
				else if(key.isReadable()) {
					read(key);
				}
				iterator.remove();   //该事件已处理,可以移除
			}
		}
	}
	
	private void write(SelectionKey key) throws IOException {
		SocketChannel socketChannel=(SocketChannel)key.channel();
		System.out.println("write:"+str);
		
		sendBuffer.clear();
		sendBuffer.put(str.getBytes());
		sendBuffer.flip();
		socketChannel.write(sendBuffer);
		socketChannel.register(selector,SelectionKey.OP_READ);
		
	}
	
	private void read(SelectionKey key) throws IOException {
		SocketChannel socketChannel=(SocketChannel)key.channel();
		readBuffer.clear();
		int numRead;
		try {
			numRead=socketChannel.read(readBuffer);
		} catch (IOException e) {
			key.cancel();
			socketChannel.close();
			return;
		}
		str=new String(readBuffer.array(),0,numRead);
		System.out.println(str);
		socketChannel.register(selector,SelectionKey.OP_WRITE);
	}
	
	private void accept(SelectionKey key) throws IOException {
		ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 
        SocketChannel clientChannel = ssc.accept(); 
        clientChannel.configureBlocking(false); 
        clientChannel.register(selector, SelectionKey.OP_READ); 
        System.out.println("a new client connected "+clientChannel.getRemoteAddress()); 
	}
	
	public static void main(String[] args) throws IOException { 
        System.out.println("server started..."); 
        new Server().start(); 
    } 
}


客户端:

package com.mmc.concurrentcystudy.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.Scanner;
import java.util.Set;

public class Client {
	ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    
    public void start() throws IOException {
    	//打开socket通道
    	SocketChannel sc=SocketChannel.open();
    	//设置为非阻塞
    	sc.configureBlocking(false);
    	//连接服务器地址
    	sc.connect(new InetSocketAddress("localhost",8080));
    	Selector selector=Selector.open();
    	sc.register(selector, SelectionKey.OP_CONNECT);
//    	Scanner scanner=new Scanner(System.in);
    	
    	while(true) {
    		//此方法执行处于阻塞模式
    		selector.select();
    		Set<SelectionKey> keys = selector.selectedKeys();
    		Iterator<SelectionKey> iterator = keys.iterator();
    		while(iterator.hasNext()) {
    			SelectionKey key=iterator.next();
    			iterator.remove();
    			//判断此通道是否进行连接操作
    			if(key.isConnectable()) {
    				sc.finishConnect();
    				sc.register(selector, SelectionKey.OP_WRITE);
    				System.out.println("server conneted.........");
    				break;
    			}else if(key.isWritable()) {  //写操作
//    				System.out.print("please input message");
//    				String message=scanner.nextLine();
    				String message="服务端你好,我是Balla_兔子";
    				
    				writeBuffer.clear();
    				writeBuffer.put(message.getBytes());
    				writeBuffer.flip();
    				sc.write(writeBuffer);
    				
    				sc.register(selector, SelectionKey.OP_READ);
                    sc.register(selector, SelectionKey.OP_WRITE);
                    sc.register(selector, SelectionKey.OP_READ);
    			}else if(key.isReadable()) {
    				 System.out.print("receive message:");
                     SocketChannel client = (SocketChannel) key.channel();
                     //将缓冲区清空以备下次读取 
                     readBuffer.clear();
                     int num = client.read(readBuffer);
                     System.out.println(new String(readBuffer.array(),0, num));
                     sc.register(selector, SelectionKey.OP_WRITE);
    			}
    		}
    	}
    }
    
    public static void main(String[] args) throws IOException { 
        new Client().start(); 
    } 
}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情