BIO与NIO的区别

BIO blocking input/output 阻塞式输入输出 一般值java.io下面的类,比如我们经常读写文件就用这个包里面的类,

NIO non-blocking input/output 非阻塞式输入输出, 一般值java.nio下面的类,netty其实就是对nio的封装。相对来说netty书写非阻塞式的网络请求代码更简洁。

BIO编程模式,一般一个网络请求,服务端就会分配一个线程来处理,然后服务端就会阻塞在那里,一直等待客户端将数据发送完毕,服务端收到请求后,再处理完,最后将服务端处理好的数据返回给客户端,最后再释放链接,BIO这种情况下,服务端效率相对来说会比较低。因为等待的过程服务端会阻塞在那里啥也不干。BIO两个核心类Socket,ServerSocket

NIO编程模式,多个网络请求,首先会通过一个或多个Selector来接待,selector会轮询这些请求是否有数据准备好,而且读写数据使用缓存ByteBuffer,不一定说一个链接请求,得先读写完成再去处理另外一个请求,而是可以先缓存起来,Selector轮询那个链接准备好了,再让服务器端新建一个线程来处理。对于每个链接,线程不会阻塞,更充分的使用了服务端的资源,所以通信效率更高。NIO三大核心部分:Channel(通道,SocketChannel,ServerSocketChannel),Buffer(缓冲区),Selector(选择器)

下面通过代码来感受一下,两者的区别:

1.BIO模式下:

客户端连接服务端,发送数据及接收服务端响应,网络通信主要通过Socket和ServerSocket类来完成

package com.figo.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;


/**
 * 类说明:客户端
 */
public class BioClient {

    public static void main(String[] args) throws InterruptedException,
            IOException {
        //通过构造函数创建Socket,并且连接指定地址和端口的服务端
        Socket socket =  new Socket("127.0.0.1",8088);
        System.out.println("请输入请求消息:");
        //启动读取服务端输出数据的线程
        new ReadMsg(socket).start();
        PrintWriter pw = null;
        //允许客户端在控制台输入数据,然后送往服务器
        while(true){
            pw = new PrintWriter(socket.getOutputStream());
            pw.println(new Scanner(System.in).next());
            pw.flush();
        }
    }

    //读取服务端输出数据的线程
    private static class ReadMsg extends Thread {
        Socket socket;

        public ReadMsg(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            //负责socket读写的输入流
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()))){
                String line = null;
                //通过输入流读取服务端传输的数据
                //如果已经读到输入流尾部,返回null,退出循环
                //如果得到非空值,就将结果进行业务处理
                while((line=br.readLine())!=null){
                    System.out.printf("%s\n",line);
                }
            } catch (SocketException e) {
                System.out.printf("%s\n", "服务器断开了你的连接");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                clear();
            }
        }

        //必要的资源清理工作
        private void clear() {
            if (socket != null)
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

服务端接收客户端请求,处理完成返回客户端

package com.figo.test;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * 类说明:bio的服务端主程序
 */
public class BioServer {
    //服务器端必须
    private static ServerSocket server;
    //线程池,处理每个客户端的请求
    private static ExecutorService executorService
            = Executors.newFixedThreadPool(5);

    private static void start() throws IOException{

        try{
            //通过构造函数创建ServerSocket
            //如果端口合法且空闲,服务端就监听成功
            server = new ServerSocket(8088);
            System.out.println("服务器已启动,端口号:" + DEFAULT_PORT);
            while(true){
                Socket socket= server.accept();
                System.out.println("有新的客户端连接----" );
                //当有新的客户端接入时,打包成一个任务,投入线程池
                executorService.execute(new BioServerTask(socket));
            }
        }finally{
            if(server!=null){
                server.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        start();
    }

}



package com.figo.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;


/**
 * 新建一个线程处理客户端请求:
 */
public class BioServerTask implements Runnable{
    private Socket socket;
    public BioServerHandler(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try(//负责socket读写的输出、输入流
            BufferedReader in = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(),
                    true)){
            String message;
            String result;
            //通过输入流读取客户端传输的数据
            //如果已经读到输入流尾部,返回null,退出循环
            //如果得到非空值,就将结果进行业务处理
            while((message = in.readLine())!=null){
                System.out.println("Server accept message:"+message);
                result = "这里是否服务端,接收到消息"+message+",业务逻辑已经处理完成!";
                //将业务结果通过输出流返回给客户端
                out.println(result);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }

}

2.NIO模式下

客户端连接服务端使用SocketChannel对应BIO的Socket,服务端使用ServerSocketChannel对应BIO的ServerSocket

    //客户端
    @Test
    public void TestClient(){
        SocketChannel client=null;
        ByteBuffer buffer=null;
        try {
            //获取socket连接
            client = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
            //切换非阻塞状态
            client.configureBlocking(false);
            //指定缓冲区
            buffer = ByteBuffer.allocate(1024);
            //从控制台读入数据,发送数据到服务端
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()){
                buffer.put(scanner.next().getBytes());
                //重置limit限制读取limit长度,起始位置position改成0,标记改成-1
                /**
                  *  public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
                  */         
                buffer.flip();
                client.write(buffer);
                buffer.clear();
            }

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

//服务端
    @Test
    public void TestServer() throws IOException {
        ServerSocketChannel server = ServerSocketChannel.open();
        //1.切换非阻塞模式
        server.configureBlocking(false);
        //2.绑定
        server.bind(new InetSocketAddress("127.0.0.1",8888));
        //3.获取选择器
        Selector selector = Selector.open();
        //4.将服务注册到选择器上,第二个参数式选择键selectionkey表示,有四个常量
        // (1)selectionkey.OP_READ 读操作    (2)selectionkey.OP_WRITE 写操作     (3)selectionkey.OP_CONNECT 连接操作     (4)selectionkey.OP_ACCEPT 接收操作
        server.register(selector, SelectionKey.OP_ACCEPT);  //将读事件注册到选择器上
        //5.轮询式的获取选择器上已经准备就绪的事件
        while (selector.select()>0){
            //6.获取当前选择器中所有注册的“选择键”
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //7.判断什么事件准备就绪
                if (key.isAcceptable()){
                    //8.如果是接收就绪,采取动作
                    SocketChannel accept = server.accept();
                    //9.切换非阻塞状态
                    accept.configureBlocking(false);
                    //10.将读事件注册到selector上
                    accept.register(selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    //11.获取当前选择器上“读状态”的通道
                    SocketChannel channel =(SocketChannel) key.channel();
                    //12.读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len=0;
                    while ((len=channel.read(buffer))>0){
                        buffer.flip();
                        String s = new String(buffer.array(), 0, len);
                        System.out.println("接收到客户端的数据:"+s);
                        buffer.clear();
                        //数据返回给客户端
                        //将消息编码为字节数组
                        byte[] bytes = "服务端处理完成".getBytes();
                        //根据数组容量创建ByteBuffer
                        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
                        //将字节数组复制到缓冲区
                        writeBuffer.put(bytes);
                        //flip操作
                        writeBuffer.flip();
                        //发送缓冲区的字节数组
                        channel.write(writeBuffer);
                    }
                }
                //执行完任务之后就要取消选择键
                iterator.remove();
            }
        }
    }

对比下来,1.发现NIO比BIO多了Selector,使用了多路复用技术,一个selector线程处理多个客户端连接,省去BIO每次都需要创建一个线程的开销。2.另外BIO是面向流Stream的,流是是阻塞式进行的,那么读写需要阻塞线程, NIO是面向缓存Buffer的,读写缓存无需阻塞,那么不会阻塞线程。

阻塞和非阻塞怎么理解?比如买奶茶这件事,阻塞就好像你点好了单,然后在排队等奶茶制作完成,非阻塞是你点好了单,然后去逛旁边的服装店了,奶茶做好了,奶茶店老板电话通知你去取。不用傻傻的在排队等了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值