IO(BIO),NIO,AIO的深度解析和区别

IOBIO),NIOAIO的深度解析和区别

  

IO

  

概念:

Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求。

 

Socket和ServerSocket类库位于java.net包中,serverSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话,对于一个网络连接来说,套接字是平等的,不因为在服务器端或在客户端而产生不同的级别,不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的

 

套接字之间链接过程分为四步:

1)服务器监听

 服务器监听:是服务端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络的状态

2)客户端请求服务器

 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后就想服务器端套接字提出连接请求

3)服务器确认

 服务器端连接确认,是指当服务器端套接字监听到或者说接受到客户端套接字的连接请求,他就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端。

4)客户端进行通信

客户端连接确认:一旦客户端确认了此描述,连接就建立好了,双方开始通信。而服务器端套接字继续处于监听状态,继续接受其他客户端套接字的连接请求

 

代码示例

ServerHandler方法

public classServerHandler implementsRunnable{
   private Socket socket ;
   public ServerHandler(Socket socket){
      this.socket = socket;
   }
   @Override
   public void run() {
      BufferedReader in = null;
      PrintWriter out = null;
      try {
         in = new BufferedReader(newInputStreamReader(this.socket.getInputStream()));
         out = new PrintWriter(this.socket.getOutputStream(),true);
         String body = null;
         while(true){
            body = in.readLine();
            if(body == null) break;
            System.out.println("Server :"+ body);
            out.println("服务器端回送响的应数据.");
         }     
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         if(in != null){
            try {
               in.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
         if(out != null){
            try {
               out.close();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
         if(socket != null){
            try {
               socket.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
         socket = null;
      }   
   }

}

 

Server方法

public classServer {
   final static int PROT =8765;
   public static void main(String[] args) {
      ServerSocket server = null;
      try {
         server = new ServerSocket(PROT);
         System.out.println(" server start .. ");
         //进行阻塞
         
Socket socket = server.accept();
         //新建一个线程执行客户端的任务
         
newThread(new ServerHandler(socket)).start();
         
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
            if (server !=null) {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
   }

Client方法

public classClient {
   final static String ADDRESS ="127.0.0.1";
   final static int PORT =8765;
   public static void main(String[] args) {
      Socket socket = null;
      BufferedReader in = null;
      PrintWriter out = null;
      try {
         socket = new Socket(ADDRESS, PORT);
         in = new BufferedReader(newInputStreamReader(socket.getInputStream()));
         out = new PrintWriter(socket.getOutputStream(),true);
         //向服务器端发送数据
         
out.println("接收到客户端的请求数据...");
         out.println("接收到客户端的请求数据1111...");
         String response = in.readLine();
         System.out.println("Client: " + response);
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         if(in != null){
            try {
               in.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
         if(out != null){
            try {
               out.close();
            } catch (Exception e) {
               e.printStackTrace();
            }
         }
         if(socket != null){
            try {
               socket.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
         socket = null;
      }
   }
}

 

网络编程的基本模型Client/Server模型,也就是两个进程直接进行相互通信,其中服务端提供配置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接成功,则双方即可进行通信(网络套接字socket)

 

优化方法:使用连接池和任务队列创建伪异步IO

 

 

采用线程池和任务队列可以实现一种伪异步的IO通信框架。

我们学过连接池的使用和队列的使用,其实就是将客户端的socket封装成一个task任务(实现runnable接口的类)然后投递到线程池中去,配置相应的队列进行实现。

 

代码示例

ServerHandler方法

public class ServerHandler implements Runnable {

private Socket socket;

public ServerHandler (Socket socket){

this.socket = socket;

}

@Override

public void run() {

BufferedReader in = null;

PrintWriter out = null;

try {

in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));

out = new PrintWriter(this.socket.getOutputStream(), true);

String body = null;

while(true){

body = in.readLine();

if(body == null) break;

System.out.println("Server:" + body);

out.println("Server response");

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if(in != null){

try {

in.close();

} catch (Exception e1) {

e1.printStackTrace();

}

}

if(out != null){

try {

out.close();

} catch (Exception e2) {

e2.printStackTrace();

}

}

if(socket != null){

try {

socket.close();

} catch (Exception e3) {

e3.printStackTrace();

}

}

socket = null;

}

}

}

 

HandlerExecutorPool方法

public class HandlerExecutorPool {

private ExecutorService executor;

public HandlerExecutorPool(int maxPoolSize, int queueSize){

this.executor = new ThreadPoolExecutor(

Runtime.getRuntime().availableProcessors(),

maxPoolSize,

120L,

TimeUnit.SECONDS,

new ArrayBlockingQueue<Runnable>(queueSize));

}

public void execute(Runnable task){

this.executor.execute(task);

}

}

Server方法

public class Server {

final static int PORT = 8765;

 

public static void main(String[] args) {

ServerSocket server = null;

BufferedReader in = null;

PrintWriter out = null;

try {

server = new ServerSocket(PORT);

System.out.println("server start");

Socket socket = null;

HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);

while(true){

socket = server.accept();

executorPool.execute(new ServerHandler(socket));

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if(in != null){

try {

in.close();

} catch (Exception e1) {

e1.printStackTrace();

}

}

if(out != null){

try {

out.close();

} catch (Exception e2) {

e2.printStackTrace();

}

}

if(server != null){

try {

server.close();

} catch (Exception e3) {

e3.printStackTrace();

}

}

server = null;

}

}

}

Client方法

public class Client {

final static String ADDRESS = "127.0.0.1";

final static int PORT =8765;

public static void main(String[] args) {

Socket socket = null;

BufferedReader in = null;

PrintWriter out = null;

try {

socket = new Socket(ADDRESS, PORT);

in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

out = new PrintWriter(socket.getOutputStream(), true);

out.println("Client request");

String response = in.readLine();

System.out.println("Client:" + response);

}  catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

} finally {

if(in != null){

try {

in.close();

} catch (Exception e1) {

e1.printStackTrace();

}

}

if(out != null){

try {

out.close();

} catch (Exception e2) {

e2.printStackTrace();

}

}

if(socket != null){

try {

socket.close();

} catch (Exception e3) {

e3.printStackTrace();

}

}

socket = null;

}

}

}

关于同步异步,阻塞非阻塞,同步阻塞和同步非阻塞,异步阻塞和异步非阻塞

推荐地址:http://mp.weixin.qq.com/s/7izjL2obMxctn2B4n6yAiQ

 

NIO

 

在介绍NIO之前,先澄清一个概念,有的人叫NIO为new IO,有的人把NIO叫做Non-block IO,这里我们还是习惯说后者,即非阻塞IO

 

NIO的本质

NIO的本质就是避免原始的TCP建立连接使用3次握手的操作,减少连接的开销

 

概念

(1)Buffer-缓冲区

(2)Channel(管道。通道)

(3)Selector(选择器,多路复用器)

 

                                                                                                                   NIO原理图

 

Buffer是一个对象,它包含一些要写入或者要读取的数据。在NIO类库中加入Buffer对象,体现了新库与原IO的一个重要的区别。在面向流的IO中,可以将数据直接写入或读取到Stream对象中。在NIO库中,所有的数据都是用缓冲区处理的(读写)。缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他类型的数组,这个数组为缓冲区提供了数据的访问读写等操作属性,如位置,容量,上限等概念,参考API文档。Buffer类型:我们最常用的就是ByteBuffer,实际上每一种java基本类型都对应了一种缓冲区(除了Boolean类型)

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBUffer

FloatBuffer

DoubleBUffer

 

通道(Channel),它就像自来水管道一样,网络数据通过Channel读取和写入,通道与流不同之处在于通道是双向的,而流只是一个方向上移动(一个流必须是inputStream或者outputStream的子类),而通道可以用于读,写或者二者同时进行,最关键的是可以与多路复用器结合起来,有多种的状态位,方便多路复用器去识别。事实上通道分为两大类,一类是网络读写的(SelectableChannel),一类是用于文件操作的(FileChannel),我们使用的SocketChannel和ServerSockerChannel都是SelectableChannel的子类

 

 

多路复用器(seletor),他是NIO编程的基础,非常重要,多路复用器提供选择已经就绪的任务的能力。

简单说,就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。

 

 

Selector线程就类似一个管理者(Master),管理了成千上万个管道,然后轮询哪个管道的数据已经准备好,通知CPU执行IO的读取或写入操作。

Selector模式:当IO事件(管理)注册到选择器以后,selector会分配给每个管道一个key值,相当于标签。selector选择器是以轮询的方式进行查找注册的所有IO事件(管道)

当我们的IO事件(管道)准备就绪后,select就会识别,会通过key值来找到相应的管道,进行相关的数据处理操作(从管道里读或写数据,写道我们的数据缓冲区中)。

 

每个管道都会对选择器进行注册不同的事件状态,以便选择器查找。

SelectionKey.OP_CONNECT          连接状态

SelectionKey.OP_ACCEPT            阻塞状态

SelectionKey.OP_READ              可读状态

SelectionKey.OP_WRITE             可写状态

 

代码如下:

public class Server implements Runnable{

//1 多路复用器(管理所有的通道)

private Selector seletor;

//2 建立缓冲区

private ByteBuffer readBuf = ByteBuffer.allocate(1024);

//3

private ByteBuffer writeBuf = ByteBuffer.allocate(1024);

public Server(int port){

try {

//1 打开路复用器

this.seletor = Selector.open();

//2 打开服务器通道

ServerSocketChannel ssc = ServerSocketChannel.open();

//3 设置服务器通道为非阻塞模式

ssc.configureBlocking(false);

//4 绑定地址

ssc.bind(new InetSocketAddress(port));

//5 把服务器通道注册到多路复用器上,并且监听阻塞事件

ssc.register(this.seletor, SelectionKey.OP_ACCEPT);

System.out.println("Server start, port :" + port);

} catch (IOException e) {

e.printStackTrace();

}

}

 

@Override

public void run() {

while(true){

try {

//1 必须要让多路复用器开始监听

this.seletor.select();

//2 返回多路复用器已经选择的结果集

Iterator<SelectionKey>keys=this.seletor.selectedKeys().iterator();

//3 进行遍历

while(keys.hasNext()){

//4 获取一个选择的元素

SelectionKey key = keys.next();

//5 直接从容器中移除就可以了

keys.remove();

//6 如果是有效的

if(key.isValid()){

//7 如果为阻塞状态

if(key.isAcceptable()){

this.accept(key);

}

//8 如果为可读状态

if(key.isReadable()){

this.read(key);

}

//9 写数据

if(key.isWritable()){

//this.write(key); //ssc

}

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

private void write(SelectionKey key){

//ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();

//ssc.register(this.seletor, SelectionKey.OP_WRITE);

}

 

private void read(SelectionKey key) {

try {

//1 清空缓冲区旧的数据

this.readBuf.clear();

//2 获取之前注册的socket通道对象

SocketChannel sc = (SocketChannel) key.channel();

//3 读取数据

int count = sc.read(this.readBuf);

//4 如果没有数据

if(count == -1){

key.channel().close();

key.cancel();

return;

}

//5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)

this.readBuf.flip();

//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据

byte[] bytes = new byte[this.readBuf.remaining()];

//7 接收缓冲区数据

this.readBuf.get(bytes);

//8 打印结果

String body = new String(bytes).trim();

System.out.println("Server : " + body);

// 9..可以写回给客户端数据

} catch (IOException e) {

e.printStackTrace();

}

}

 

private void accept(SelectionKey key) {

try {

//1 获取服务通道

ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();

//2 执行阻塞方法

SocketChannel sc = ssc.accept();

//3 设置阻塞模式

sc.configureBlocking(false);

//4 注册到多路复用器上,并设置读取标识

sc.register(this.seletor, SelectionKey.OP_READ);

} catch (IOException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

new Thread(new Server(8765)).start();;

}

}

 

Client方法

public class Client {

 

//需要一个Selector

public static void main(String[] args) {

//创建连接的地址

InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);

//声明连接通道

SocketChannel sc = null;

//建立缓冲区

ByteBuffer buf = ByteBuffer.allocate(1024);

try {

//打开通道

sc = SocketChannel.open();

//进行连接

sc.connect(address);

while(true){

//定义一个字节数组,然后使用系统录入功能:

byte[] bytes = new byte[1024];

System.in.read(bytes);

//把数据放到缓冲区中

buf.put(bytes);

//对缓冲区进行复位

buf.flip();

//写出数据

sc.write(buf);

//清空缓冲区数据

buf.clear();

}

} catch (IOException e) {

e.printStackTrace();

} finally {

if(sc != null){

try {

sc.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

 

创建NIO服务器的主要步骤

    (1)打开ServerSocketChannel,监听客户端连接

    2)绑定监听端口,设置连接为非阻塞模式

    3)创建Reactor线程,创建多路复用器并启动线程

     4)ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件

     5)Selector轮询准备就绪的key

     6)Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,简历物理链路

     7)设置客户端链路为非阻塞模式

     8)将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息

     9)异步读取客户端消息到缓冲区

     10)Buffer编解码,处理半包消息,将解码成功的消息封装成Task

     11)将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端

 

    因为应答消息的发送,SocketChannel也是异步非阻塞的,所以不能保证一次能吧需要发送的数据发送完,此时就会出现写半包的问题。我们需要注册写操作,不断轮询Selector将没有发送完的消息发送完毕,然后通过Buffer的hasRemain()方法判断消息是否发送完成。

 

推荐NIO底层讲解网址:http://mp.weixin.qq.com/s/_vcT6KJNgcy1FruhSlA5zg

https://mp.weixin.qq.com/s/BZJiUMR60YwpW1krN3Me_g

 

 

AIO

 

AIO编程,在NIO基础之上引入了异步通道的概念。并提供异步文件和异步套接字通道的实现,从而在真正意义上实现了异步非阻塞,之前我们学过的NIO只是非阻塞而非异步。而AIO它不需要通过多路复用器对注册的通道的进行轮训操作即可实现异步读写,从而简化了NIO编程模型。也可以称为NIO2.0,这种模式才是真正的属于异步非阻塞的模型。

 

AsynchronousServerScoketChannel

AsynchronousScoketChanel

 

代码

Server端代码

 

public class Server {

//线程池

private ExecutorService executorService;

//线程组

private AsynchronousChannelGroup threadGroup;

//服务器通道

public AsynchronousServerSocketChannel assc;

public Server(int port){

try {

//创建一个缓存池

executorService = Executors.newCachedThreadPool();

//创建线程组

threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);

//创建服务器通道

assc = AsynchronousServerSocketChannel.open(threadGroup);

//进行绑定

assc.bind(new InetSocketAddress(port));

System.out.println("server start , port : " + port);

//进行阻塞

assc.accept(this, new ServerCompletionHandler());

//一直阻塞 不让服务器停止

Thread.sleep(Integer.MAX_VALUE);

} catch (Exception e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

Server server = new Server(8765);

}

}

 

 

ServerCompletionHandler端代码

 

Public class ServerCompletionHandlerimplements CompletionHandler<AsynchronousSocketChannel, Server> {

 

@Override

public void completed(AsynchronousSocketChannel asc, Server attachment) {

//当有下一个客户端接入的时候 直接调用Server的accept方法,这样反复执行下去,保证多个客户端都可以阻塞(没有递归上限),1.7以后AIO才实现了异步非阻塞

attachment.assc.accept(attachment, this);

read(asc);

}

 

private void read(final AsynchronousSocketChannel asc) {

//读取数据

ByteBuffer buf = ByteBuffer.allocate(1024);

asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {

@Override

public void completed(Integer resultSize, ByteBuffer attachment) {

//进行读取之后,重置标识位

attachment.flip();

//获得读取的字节数

System.out.println("Server -> " + "收到客户端的数据长度为:" + resultSize);

//获取读取的数据

String resultData = new String(attachment.array()).trim();

System.out.println("Server -> " + "收到客户端的数据信息为:" + resultData);

String response = "服务器响应, 收到了客户端发来的数据: " + resultData;

write(asc, response);

}

@Override

public void failed(Throwable exc, ByteBuffer attachment) {

exc.printStackTrace();

}

});

}

private void write(AsynchronousSocketChannel asc, String response) {

try {

ByteBuffer buf = ByteBuffer.allocate(1024);

buf.put(response.getBytes());

buf.flip();

asc.write(buf).get();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

@Override

public void failed(Throwable exc, Server attachment) {

exc.printStackTrace();

}

 

}

 

 

Client端代码

 

public class Clientimplements Runnable{

 

private AsynchronousSocketChannel asc ;

public Client() throws Exception {

asc = AsynchronousSocketChannel.open();

}

public void connect(){

asc.connect(new InetSocketAddress("127.0.0.1", 8765));

}

public void write(String request){

try {

asc.write(ByteBuffer.wrap(request.getBytes())).get();

read();

} catch (Exception e) {

e.printStackTrace();

}

}

 

private void read() {

ByteBuffer buf = ByteBuffer.allocate(1024);

try {

asc.read(buf).get();

buf.flip();

byte[] respByte = new byte[buf.remaining()];

buf.get(respByte);

System.out.println(new String(respByte,"utf-8").trim());

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

@Override

public void run() {

while(true){

}

}

public static void main(String[] args) throws Exception {

Client c1 = new Client();

c1.connect();

Client c2 = new Client();

c2.connect();

Client c3 = new Client();

c3.connect();

new Thread(c1, "c1").start();

new Thread(c2, "c2").start();

new Thread(c3, "c3").start();

Thread.sleep(1000);

c1.write("c1 aaa");

c2.write("c2 bbbb");

c3.write("c3 ccccc");

}

}

 

 

 

各种I/O的对比

 

 

适用场景

 

1)BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

 

2)NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

 

3)AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

 

另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

 

 

推荐NIO好文地址:https://mp.weixin.qq.com/s/jeuISFEh49aUheFsX_aHMg

https://mp.weixin.qq.com/s/jeuISFEh49aUheFsX_aHMg

http://mp.weixin.qq.com/s/5SKgdkC0kaHN495psLd3Tg

https://mp.weixin.qq.com/s/jeuISFEh49aUheFsX_aHMg

  • 18
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值