BIO,NIO,AIO实践学习笔记(便于理解理论)

BIO

BIO: 同步并阻塞 ,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
阻塞I/O 在调用一个io函数的时候,如果没有获取到数据的情况下,那么就会一直等待;等待的过程中会导致整个应用程序一直是一个阻塞的过程,无法去做其他的实现。
在这里插入图片描述

package com.monkey.bio;

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

public class SocketServer {
    public static void main(String[] args) throws IOException{
        //cmd命令: telnet localhost 9000
        //help
        //send xiaoxi
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true){
            System.out.println("等待连接。。。");
            //阻塞方法
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了。。。");
            
            //优化--多线程
            //缺点:不能支持大量连接(C10m问题导致服务器宕机)
            //改进:线程池,但是如果都在handler中等待读数据时阻塞,那么这个服务器还是不能处理新的客户端连接
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(clientSocket);
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }).start();
//            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));
        }
        System.out.println("end");
        //clientSocket.getOutputStream().write("HelloCLient".getBytes());
        //clientSocket.getOutputStream().flush();
    }
}

/*
Redis,Zookeeper,Netty,游戏服务器等其实底层就是IO通信程序
*/

NIO

NIO: 同步非阻塞 ,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/0请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

非阻塞I/O 不管是否有获取到数据,都会立马获取结果,如果没有获取数据的话、那么就不间断的循环重试,但是我们整个应用程序不会实现阻塞。

原始的NIO:

package com.monkey.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,InterruptedException{
        //创建NIO ServerSocketChannel, 与BIO的serverSocket类似
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        //设置ServerSocketChanne为非阻塞
        serverSocket.configureBlocking(false);
        System.out.println("服务启动成功");

        while(true){
            //非阻塞模式accept方法不会阻塞,否则会阻塞
            //NIO的非阻塞是由操作系统内部实现的,底层调用了Linux内核的accept函数
            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(6);
                //非阻塞模式read方法不会阻塞,否则会阻塞
                int len = sc.read(byteBuffer);
                //如果有数据。把数据打印出来
                if(len > 0){
                    System.out.println("接收到消息:"+new String(byteBuffer.array()));
                }else if(len == -1){ //如果客户端断开,把socket从集合中去掉
                    iterator.remove();
                    System.out.println("客户端连接断开");
                }
            }
        }
    }
}
//缺点:1、一个连接的时间不能太长,否则会导致后续连接阻塞
//2、连接数量过多,时间复杂度会过高
//3、连接没有消息时也会加入遍历,无效遍历,浪费资源
//优化:多路复用器

改进:使用多路复用器
在这里插入图片描述
如上图:

package com.monkey.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;
import java.util.Set;

public class NioSelectorServer {
    public static void main(String[] args) throws IOException,InterruptedException{
        //创建NIO ServerSocketChannel, 与BIO的serverSocket类似
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        //设置ServerSocketChanne为非阻塞
        serverSocketChannel.configureBlocking(false);

        //打开Selector处理Channel,即创建epoll
        Selector selector = Selector.open();
        //把ServerSocketChanneL注册到seLector上,并且seLector对客厂7端accept连接操作感兴趣
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");

        while (true){
            //阻塞等待需要处理的事件发生(即有消息来时才跳出阻塞)
            selector.select();

            //获取seLector中注册的全部事件的SelectionKey实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            //遍历SeLectionKey对事件进行处理
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //如果是0P_ ACCEPT事件, 则进行连接获取和事件注册
                if(key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    //这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                }else if(key.isReadable()){ //如果是0P_ READ 事件,则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(6);
                    int len = socketChannel.read(byteBuffer);
                    //如果有数据。把数据打印出来
                    if(len > 0){
                        System.out.println("接收到消息:"+new String(byteBuffer.array()));
                    }else if(len == -1){ //如果客户端断开,关闭socket
                        System.out.println("客户端连接断开");
                        socketChannel.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次seLect重复处理
                iterator.remove();
            }
        }
    }
}

其底层实现–epoll

I/O多路复用模型能处理多个connection的优点就使其能支持更多的并发连接请求。
在这里插入图片描述
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,32位系统1024,64位系统2048
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构(链表结构)而不是select的fd_set结构,所以连接数上没有限制,其他的都差不多。

epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?

  • 对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
  • 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果)。
  • 对于第三个缺点,epoll没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/filemax察看,一般来说这个数目和系统内存关系很大。

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

AIO

AIO: 异步非阻塞 ,服务器实现模式为一个有效请求一个线程,客户端的/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。AIO方式使用于连接数目多且连接比较长(重操作〉的架构,比如相册服务器,充分调用Os参与并发操作,编程比较复杂,JDK7开始支持。
【参考】https://www.bilibili.com/video/BV1Am4y1f7Ec?p=3&spm_id_from=333.880.my_history.page.click

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值