BIONIOAIO 代码实战

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BIONIOAIO 代码实战相关的知识,希望对你有一定的参考价值。

文章目录

IO

IO流是Java中比较难理解的一个知识点,但是IO流在实际的开发场景中经常会使用到,比如Dubbo底层就是NIO进行通讯。本文将介绍Java发展过程中出现的三种IO:BIO、NIO以及AIO,重点介绍NIO。

BIO

BIO概念

BIO即同步阻塞IO,实现模型为一个连接就需要一个线程去处理。这种方式简单来说就是当有客户端来请求服务器时,服务器就会开启一个线程去处理这个请求,即使这个请求不干任何事情,这个线程都一直处于阻塞状态。

BIO模型有很多缺点,最大的缺点就是资源的浪费。想象一下如果QQ使用BIO模型,当有一个人上线时就需要一个线程,即使这个人不聊天,这个线程也一直被占用,那再多的服务器资源都不管用。

BIO代码实现

我们通过socket模拟BIO的实现逻辑

首先建立Server,建立一个ServerSocket对象,绑定端口,然后等待连接,如果连接成功就新建一个线程去处理连接。然后在Client使用Socket进行连接服务器。

Server代码:

package com.yyl.iotest.bio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Bioserver 
    private static Socket socket = null;
    public static void main(String[] args) 
        // 启动socket
        try 
            //绑定端口
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8880));
            // 监听socket
            while (true) 
                // 等待连接  阻塞
                System.out.println("等待连接");
                socket = serverSocket.accept();
                System.out.println("连接成功");
                //连接成功后新开一个线程去处理这个连接
                new Thread(new Runnable() 
                    @Override
                    public void run() 
                        byte[] bytes=new byte[1024];
                        try 
                            System.out.println("等待读取数据");
                            //等待读取数据    阻塞
                            int length=socket.getInputStream().read(bytes);
                            System.out.println(new String(bytes,0,length));
                            System.out.println("数据读取成功");
                         catch (IOException e) 
                            e.printStackTrace();
                        
                    
                ).start();
            

         catch (IOException e) 
            e.printStackTrace();
        
    


Client代码:

package com.yyl.iotest.bio;

import java.io.IOException;
import java.net.Socket;

public class BIOClient 
    public static void main(String[] args) 
        Socket socket= null;
        try 
            socket = new Socket("127.0.0.1",8880);
            socket.getOutputStream().write("BIO的一条数据".getBytes());
            socket.close();
         catch (IOException e) 
            e.printStackTrace();
        
    


运行结果:

NIO

NIO概念

BIO是阻塞的,如果没有多线程,BIO就需要一直占用CPU,而NIO则是非阻塞IO,NIO在获取连接或者请求时,即使没有取得连接和数据,也不会阻塞程序。

NIO的服务器实现模式为一个线程可以处理多个请求(连接)。

NIO有几个知识点需要掌握,Channel(通道),Buffer(缓冲区), Selector(多路复用选择器)。

Channel既可以用来进行读操作,又可以用来进行写操作。NIO中常用的Channel有FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。

Buffer缓冲区用来发送和接受数据。

Selector 一般称为选择器或者多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。在javaNIO中使用Selector往往是将Channel注册到Selector中。


下面我通过代码的方式模拟javaNIO的运行流程。

NIO代码实现

首先贴上NIO的实践代码:

NIO服务端详细的执行过程是这样的:

1、创建一个ServerSocketChannel和Selector,然后将ServerSocketChannel注册到Selector上

2、Selector通过select方法去轮询监听channel事件,如果有客户端要连接时,监听到连接事件。

3、通过channel方法将socketchannel绑定到ServerSocketChannel上,绑定通过SelectorKey实现。

4、socketchannel注册到Selector上,关心读事件。

5、Selector通过select方法去轮询监听channel事件,当监听到有读事件时,ServerSocketChannel通过绑定的SelectorKey定位到具体的channel,读取里面的数据。

Server代码:

package com.yyl.iotest.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;

public class NIOServer 
    public static void main(String[] args) throws IOException 
        //创建一个socket通道,并且设置为非阻塞的方式
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8881));
        //创建一个selector选择器,把channel注册到selector选择器上
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) 
            System.out.println("等待事件发生");
            selector.select();
            System.out.println("有事件发生了");
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            // 交给selector去处理
            while (iterator.hasNext()) 
                SelectionKey key = iterator.next();
                iterator.remove();
                handle(key);
            
        
    

    private static void handle(SelectionKey key) throws IOException 
        if (key.isAcceptable()) 
            System.out.println("连接事件发生");
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 创建客户端一侧的channel,并注册到selector上
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(key.selector(), SelectionKey.OP_READ);
         else if (key.isReadable()) 
            System.out.println("数据可读事件发生");
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if (len != -1) 
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            
            // 给客户端发送信息
            ByteBuffer wrap = ByteBuffer.wrap("hello world".getBytes());
            socketChannel.write(wrap);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            socketChannel.close();
        
    


Client代码:

package com.yyl.iotest.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;

public class NIOClient 
    public static void main(String[] args) throws IOException 
        //配置基本的连接参数
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        Selector selector = Selector.open();
        channel.connect(new InetSocketAddress("127.0.0.1", 8881));
        channel.register(selector, SelectionKey.OP_CONNECT);        
        
        // 轮询访问selector
        while (true) 
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) 
                SelectionKey key = iterator.next();
                iterator.remove();
                //连接事件发生
                if (key.isConnectable()) 
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    //如果正在连接,则完成连接
                    if (socketChannel.isConnectionPending()) 
                        socketChannel.finishConnect();
                    
                    socketChannel.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.wrap("客户端发送的数据".getBytes());
                    socketChannel.write(buffer);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                 else if (key.isReadable()) 
                    //读取服务端发送过来的消息
                    read(key);
                
            
        
    

    private static void read(SelectionKey key) throws IOException 
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(512);
        int len = socketChannel.read(buffer);
        if (len != -1) 
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        
    


效果大概是这样的:首先服务端等待事件发生,当客户端启动时,服务器端先接受到连接的请求,接着接受到数据读取的请求,读完数据后继续等待。

客户端发送数据后,获取到了来自服务端的回复。


NIO通过一个Selector,负责监听各种IO事件的发生,然后交给后端的线程去处理。NIO相比与BIO而言,非阻塞体现在轮询处理上。BIO后端线程需要阻塞等待客户端写数据,如果客户端不写数据就一直处于阻塞状态。而NIO通过Selector进行轮询已注册的客户端,当有事件发生时才会交给后端去处理,后端线程不需要等待。

AIO

AIO概念

AIO是在JDK1.7中推出的新的IO方式–异步非阻塞IO,也被称为NIO2.0,AIO在进行读写操作时,直接调用API的read和write方法即可,这两种均是异步的方法,且完成后会主动调用回调函数。简单来讲,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。

Java提供了四个异步通道:AsynchronousSocketChannel、AsynchronousServerSocketChannel、AsynchronousFileChannel、AsynchronousDatagramChannel。

AIO代码实现

服务器端代码:AIO的创建方式和NIO类似,先创建通道,再绑定,再监听。只不过AIO中使用了异步的通道。

package com.yyl.iotest.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.TimeUnit;

public class AIOServer 
    public static void main(String[] args) 
        try 
            //创建异步通道
            AsynchronousServerSocketChannel serverSocketChannel=AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8882));
            System.out.println("等待连接中");
            //在AIO中,accept有两个参数,
            // 第一个参数是一个泛型,可以用来控制想传递的对象
            // 第二个参数CompletionHandler,用来处理监听成功和失败的逻辑
            //  如此设置监听的原因是因为这里的监听是一个类似于递归的操作,每次监听成功后要开启下一个监听
            serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() 
                //请求成功处理逻辑
                @Override
                public void completed(AsynchronousSocketChannel result, Object attachment) 
                    System.out.println("连接成功,处理数据中");
                    //开启新的监听
                    serverSocketChannel.accept(null,this);
                    handledata(result);
                
                @Override
                public void failed(Throwable exc, Object attachment) 
                    System.out.println("失败");
                
            );
            try 
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
             catch (InterruptedException e) 
                e.printStackTrace();
            
         catch (IOException e) 
            e.printStackTrace();
        
    

    private static void handledata(AsynchronousSocketChannel result) 
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        //通道的read方法也带有三个参数
        //1.目的地:处理客户端传递数据的中转缓存,可以不使用
        //2.处理客户端传递数据的对象
        //3.处理逻辑,也有成功和不成功的两个写法
        result.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() 
            @Override
            public void completed(Integer result, ByteBuffer attachment) 
                if (result>0)
                    attachment.flip();
                    byte[] array = attachment.array();
                    System.out.println(new String(array));
                
            
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) 
                System.out.println("失败");
            
        );
    


客户端代码基本上没有太多差别,主要还是实现数据的发送功能

package com.yyl.iotest.aio;

import java.io.IOException;
import java.net.InetSock

以上是关于BIONIOAIO 代码实战的主要内容,如果未能解决你的问题,请参考以下文章

Java中的BIONIOAIO-3

Java 网络IO编程总结(BIONIOAIO均含完整实例代码)

Java 网络IO编程总结(BIONIOAIO均含完整实例代码)

Java 网络IO编程总结(BIONIOAIO均含完整实例代码)

(转)Java 网络IO编程总结(BIONIOAIO均含完整实例代码)

JAVA BIONIOAIO详解(附代码实现)以及Netty的简介