bio与nio的几种实现方式

Posted 八阿哥克星

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bio与nio的几种实现方式相关的知识,希望对你有一定的参考价值。

在socket 通讯层面,BIO就是阻塞io,也就是说,在socket等待连接事件或者读写事件的发生的过程中,当前线程会一直处于阻塞状态,不能做其他事情;

1、bio

public static void main(String[] args) throws IOException 
 //bio阻塞io,主线程只能处理一个连接,当前连接没有处理完,是不能接收新链接的,这对于并发来讲,是非常不友好的
    ServerSocket serverSocket = new ServerSocket(9000);
    while(true) 
        //此处会阻塞,等待客户端连接
        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接");
        byte[] bytes = new byte[128];
        //此处会阻塞,等待客户端发送数据
        int len = socket.getInputStream().read(bytes);
        System.out.println("客户端发送数据:" + new String(bytes, 0, len));
    
​

2、bio-plus

对于上种情形,可以简单地做出改进,也就是利用多线程把建立连接和处理读写事件分开,这样,二者之间就不会互相影响

public static void main(String[] args) throws IOException 
​
    //加强版bio,利用异步线程去做收发数据,主线程只负责建立连接,这样,即时已连接客户端不发送数据,主线程也不会一直阻塞,可以继续循环接收新的连接;
    // 但是,这样在并发高的情况下,对内存是巨大的消耗;于是,nio就应运而生;
    ServerSocket serverSocket = new ServerSocket(9000);
    while(true) 
        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接");
        new Thread(() -> 
            byte[] bytes = new byte[128];
            int len = 0;
            try 
                len = socket.getInputStream().read(bytes);
             catch (IOException e) 
                e.printStackTrace();
            
            System.out.println("客户端发送数据:" + new String(bytes, 0, len));
        ).start();
    
​

bio-plus版虽然将建立连接和处理读写事件分开了,但是在高并发的情况下仍然“不堪一击”,并且,这种方式虽然看似是解决了bio的阻塞问题,但是属于治标不治本,它的解决方式只不过是利用空间(开辟新线程)去换取时间而已,实质上还是阻塞IO;

3.Nio-Select

于是,nio非阻塞式IO出现了,如上所示,将通道对象serverSocketChannel设置为非阻塞即可,并且引入了selector选择器(可以认为就是socketList),将建立好的连接对象放到socketList中,每次循环就会将所有的socket连接遍历一遍,处理读写事件。这样,单个线程也可以处理多个连接和事件。

public class Nioselect 
    private static List<SocketChannel> socketList=new ArrayList();
    public static void main(String[] args) throws IOException 
        //nio演示,接收客户端连接和接收数据都不会阻塞;
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //设置serverSocketChannel为非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服务启动");
        while(true) 
            SocketChannel socketChannel = serverSocketChannel.accept();
            if(socketChannel!=null)
                System.out.println("客户端已连接");
                //设置socketChannel为非阻塞
                socketChannel.configureBlocking(false);
                //将客户端连接放到集合中
                socketList.add(socketChannel);
            
            //遍历所有的socket连接,获取数据;但是这样会有空轮训的问题,就是没有发送数据的连接也会被遍历,不合理
            while(socketList.size()>0)
                for(int i=0;i<socketList.size();i++)
                    ByteBuffer allocate = ByteBuffer.allocate(128);
                    int read = socketList.get(i).read(allocate);
                    if(read>0)
                        System.out.println("客户端发送消息:"+new String(allocate.array()));
                    else
                        socketList.remove(i);
                    
                
            
        
​
​
    

但是,问题仍然存在。比如此时socketList维护了100个连接,只有一个连接向服务端发送了数据,但是,按照代码却需要将着100个连接全部遍历一遍,这显然是不合理的,并且,socketList中可以存放的连接数量也是有限的,无法解决c10k问题。(所谓c10k问题,指的是:服务器如何支持10k个并发连接)

4.Nio-Epoll

public class NioEpoll 
    private static List<SocketChannel> socketList=new ArrayList();
    public static void main(String[] args) throws IOException 
        //nio演示,接收客户端连接和接收数据都不会阻塞;
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //设置serverSocketChannel为非阻塞
        serverSocketChannel.configureBlocking(false);
        //获取selector选择器
        Selector selector = Selector.open();
        //将serverSocketChannel注册到选择器,并且声明需要选择器监听的是accept事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动");
        while(true) 
            //选择器开始执行监听,无监听事件发生会一直阻塞,有监听事件发生会跳出阻塞,继续往下执行
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext())
                SelectionKey selectionKey = iterator.next();
                //判断发生的是什么事件
                //是接收连接事件,就获取新的客户连接,及事件的注册
                if(selectionKey.isAcceptable())
                    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = channel.accept();
                    System.out.println("客户端连接成功");
                    socketChannel.configureBlocking(false);
                    //监听的是数据读事件
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    //如果是发生数据读事件,则接收数据,并打印
                else if(selectionKey.isReadable())
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer allocate = ByteBuffer.allocate(128);
                    int read = channel.read(allocate);
                    if(read>0)
                        System.out.println("客户端发送数据:"+new String(allocate.array(),0,read,"GBK"));
                    else
                        System.out.println("客户端已断开连接");
                        channel.close();
                    
                
                //将处理过的事件从列表中移除,防止重复处理
                iterator.remove();
​
            
        
    

这个版本的nio看起来就又进步了一节,这里用epoll选择器代替了上面的select选择器,epoll选择器内部可以分成两部分,一部分是注册到其上的对象列表,另一个部分是有事件发生的列表,于是,可以想到的就是,相比于select遍历所有,epoll只需要遍历有事件发生的对象即可。

其实,在select和epoll之间还有一个poll选择器,只不过poll与select相比,仅仅是存放的连接对象数量变多了,其他的并没有什么区别。

以上是关于bio与nio的几种实现方式的主要内容,如果未能解决你的问题,请参考以下文章

bio与nio的几种实现方式

Tomcat Connector的BIO与NIO模式的比较及区别

JAVA 远程 调用的几种实现方式简析 详细�0�3

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式