浅谈BIO与NIO

BIO
同步阻塞式IO,即当当前IO操作未完成时,不能进行其他操作。当服务器监听到有客户端请求连接时,就会创建一个新的线程去处理客户端的需求。
在这里插入图片描述
代码如下:

private static void start(int port) throws IOException {
        Socket socket;
        sever = new ServerSocket(port);//监听端口
        System.out.println("服务端已启动");
        while (true) {//通过无限循环监听端口
            socket = sever.accept();//监听是否有服务端连接,该方法阻塞式
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            new ServerThread(socket);
        }
    }

缺点:
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。
可以使用线程池来改进。
在这里插入图片描述
private static void start(int port) throws IOException {
Socket socket;
sever = new ServerSocket(port);//监听端口
service = Executors.newFixedThreadPool(50);
System.out.println(“服务端已启动”);
while (true) {//通过无限循环监听端口
socket = sever.accept();//监听是否有服务端连接
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
service.execute(new ServerThread(socket));
}
}
缺点:
如果使用CachedThreadPool线程池),其实除了能自动帮我们管理线程(复用),看起来也就像是1:1的客户端:线程数模型,而使用FixedThreadPool我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步I/O模型。
但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对Socket的输入流就行读取时,会一直阻塞,直到发生:
有数据可读
可用数据以及读取完毕
发生空指针或I/O异常
所以在读取数据较慢时(比如数据量大、网络传输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。
而后面即将介绍的NIO,就能解决这个难题。
NIO:
BIO是监听到一个请求连接就创建一个线程,如果这个连接什么都不做, 会造成不必要的线程开销,在java1.4后,就有了NIO,它是同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理,这样就大大减少了不必要的线程开销。
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件。这就需要提到多路复用器
多路复用器 Selector
selector一般称作选择器,也叫做多路复用器作用是检查一个或者多个NIO Channel(通道)的状态是否处于可读、可写。可以实现单线程管理多个channels,也可以管理多个网络请求
Selector是Java NIO 编程的基础。
Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
它可以用来监听来自多个客户端的IO请求,主要有以下几步:
(1)当服务器监听有客户端的连接请求时,就会为其建立通道(Chnnel),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通通道。这个通道是非阻塞式的,即可读也可写。
(2)若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
即在一个线程中可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应方法处理。
NIO服务端代码如下:

 //NIO server初始化固定流程:5步
        selector = Selector.open();					//1.selector open
        server = ServerSocketChannel.open();		//2.ServerSocketChannel open
        server.bind(new InetSocketAddress(port));	//3.serverChannel绑定端口
        server.configureBlocking(false);			//4.设置NIO为非阻塞模式
        server.register(selector, SelectionKey.OP_ACCEPT);//5.将channel注册在选择器上
    //NIO server处理数据固定流程:5步
    SocketChannel client;
    SelectionKey key;
    Iterator<SelectionKey> iKeys;

    while(true){
        selector.select();							//1.用select()方法阻塞,一直到有可用连接加入
        iKeys = selector.selectedKeys().iterator();	//2.到了这步,说明有可用连接到底,取出所有可用连接
        while(iKeys.hasNext()){
            key = iKeys.next();						//3.遍历
            if(key.isAcceptable()){					//4.对每个连接感兴趣的事做不同的处理
                //对于客户端连接,注册到服务端
                client = server.accept();			//获取客户端首次连接
                client.configureBlocking(false);
                //不用注册写,只有当写入量大,或写需要争用时,才考虑注册写事件
                client.register(selector, SelectionKey.OP_READ);
                System.out.println("+++++客户端:"+client.getRemoteAddress()+",建立连接+++++");
                client.write(charset.encode("请输入自定义用户名:"));
            }
            if(key.isReadable()){
                client = (SocketChannel) key.channel();//通过key取得客户端channel
                StringBuilder msg = new StringBuilder();
                buffer.clear();		//多次使用的缓存,用前要先清空
                try{
                    while(client.read(buffer) > 0){
                        buffer.flip();	//将写模式转换为读模式
                        msg.append(charset.decode(buffer));
                    }
                }catch(IOException e){
                    //如果client.read(buffer)抛出异常,说明此客户端主动断开连接,需做下面处理
                    client.close();			//关闭channel
                    key.cancel();			//将channel对应的key置为不可用
                    onlineUsers.values().remove(client);	//将问题连接从map中删除
                    System.out.println("-----用户'"+key.attachment().toString()+"'退出连接,当前用户列表:"+onlineUsers.keySet().toString()+"-----");
                    continue;				//跳出循环
                }
                if(msg.length() > 0) this.processMsg(msg.toString(),client,key);	//处理消息体
            }
            iKeys.remove();					//5.处理完一次事件后,要显示的移除
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值