BIO和NIO

本文探讨了BIO与NIO在服务器开发中的区别,BIO以阻塞模式处理每个连接,而NIO采用非阻塞和事件驱动,提升了并发连接处理能力。重点讲解了NIO的Selector实现和select/poll/epoll的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BIO代码

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

public class SocketSever {
    public static void main(String[] args) throws IOException {
        //
        ServerSocket serverSocket = new ServerSocket(9000);
        while(true){
            System.out.println("等待连接");
            // 阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了...");
            handler(clientSocket);
        }
    }


    private static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("准备read...");
        // 接受客户端的数据,阻塞方法
        int read = clientSocket.getInputStream().read(bytes);
        System.out.println("read 完毕。。。");
        if(read !=-1){
            System.out.println("接受到客户端请求"+new String(bytes,0,read));
        }
    }
}

NIO 简单版本

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class NioServer {
    //保存客户端连接
    static List<SocketChannel> channelList = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        // 创建NIO serverSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置SeverSocketChannel 非阻塞
        serverSocket.configureBlocking(false);
        System.out.println("服务启动成功");

        while (true){
            SocketChannel socketChannel = serverSocket.accept();
            if(socketChannel!=null){
                System.out.println("连接成功");
                // 设置SocketChannel 为非阻塞
                socketChannel.configureBlocking(false);
                // 保存客户端连接在list 中
                channelList.add(socketChannel);
            }
            // 遍历连接进行数据读取
            Iterator<SocketChannel> iterator = channelList.iterator();
            while (iterator.hasNext()){
                SocketChannel sc = iterator.next();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                // 非阻塞模式read方法不会阻塞,否则会阻塞
                int len = sc.read(byteBuffer);
                // 如果有数据
                if(len>0){
                    System.out.println("收到消息:"+new String(byteBuffer.array()));
                }else if(len ==-1){
                    iterator.remove();
                    System.out.println("客户端断开连接");
                }

            }
        }

    }

}

NIO 简单版本会有一个问题: 它会把所有连接都遍历,不管连接是否有数据读写进来。
会浪费大量的资源

NIO selector 版本代码

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 NioSelectorServer {
    public static void main(String[] args) throws IOException {
        // 创建 NIO SeverSocketChannelS
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置 serverSocket 为非阻塞
        serverSocket.configureBlocking(false);
        // 打开selector处理channel ,即创建epoll
        Selector selector = Selector.open();
        // 把ServerSocketChannel 注册到selector 上,并且selector 感知客户端的accept
        SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");
        while (true) {
            // 阻塞等待要处理的事件发生
            selector.select();
            // 获取selector 中注册的全部事件的 selectionKey 实列
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历Selection Key 对事件进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是 OP_ACCEPT事件 则进行连接获取事件和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel sever = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = sever.accept();
                    socketChannel.configureBlocking(false);
                    // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                } else if (key.isReadable()) {
                    // 如果是OP_READ 事件,进行读取
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // 如果有数据,把数据打印出来
                    if (len > 0) {
                        System.out.println("接受到消息:" +key+ new String(byteBuffer.array()));
                    } else if (len == -1) {
                        System.out.println(key+"客户端断开连接,关闭socket");
                        socketChannel.close();
                    }
                }
                // 从事件集合里删除本次处理的key ,防止下次select 重复处理
                iterator.remove();
            }
        }
    }
}

这里的代码都是创建一个服务端等待客户端连接并接受内容
不同的在于 BIO 是阻塞的,当客户端连接后服务端就会阻塞直到客户端输入或者关闭。
如果是100个客户端要连接就需要启动100个线程去处理。

NIO 可以一个线程处理100个客户端,并且都是非阻塞模式
nio是通过调用系统的epoll 函数要用监听的方式来处理IO
这就要求服务端和客户端都注册到selector
注册后有数据变动就会出发事件,并且把事件放到事件列表

在这里插入图片描述

select poll epoll 的区别
JDK1.4开始有NIO,那时候用的是select,JDK把chanel列表哪出来遍历
,在找出那些有数据的进行处理。如果列表里有1W个channel ,其中只有100 有发送数据,那效率很低

JDK1.5后用epoll ,采用事件回调方式,channel列表还是有1W个,但是不再轮询channl,而是selector ,效率变高了

而poll是linux系统的另一个函数和select 类似,只是他没有数量上限

selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表
IO效率每次遍历都进行线性遍历,时间复杂度O(N)每次遍历都进行线性遍历,时间复杂度O(N)事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1)
最大连接有上限无上限无上限
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值