Bio演变的NIO的过程

Bio是一个阻塞式的io,不能够支持并发请求访问;可以多线程优化代码

这种方式也存在缺点:如果每个请求过来都使用一个线程,这时候非常浪费CPU的资源。

所以在网络编程服务器中,是否使用单线程提高响应的效率问题,所以nio出现;

public class ServerTcpSocket {
   
static byte[] bytes = new byte[1024];

   
public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
       
try {
           
// 1.创建一个ServerSocket连接
           
final ServerSocket serverSocket = new ServerSocket();
           
// 2.绑定端口号
           
serverSocket.bind(new InetSocketAddress(8080));
           
// 3.当前线程放弃cpu资源等待获取数据
           
System.out.println("等待获取数据...");
           
while (true) {
               
final Socket socket = serverSocket.accept();
                executorService.execute(
new Runnable() {
                   
public void run() {
                       
try {
                            System.
out.println("获取到数据...");
                           
// 4.读取数据
                            
int read = socket.getInputStream().read(bytes);
                            String result =
new String(bytes);
                            System.
out.println(result);
                        }
catch (Exception e) {

                        }
                    }
                });

            }
        }
catch (Exception e) {

        }
    }
}

 

 

 

 

NIO非阻塞式代码

public class ServerNioTcpSocket {
   
static ByteBuffer byteBuffer = ByteBuffer.allocate(512);

   
public static void main(String[] args) {
       
try {
           
// 1.创建一个ServerSocketChannel连接
           
final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
           
// 2.绑定端口号
           
serverSocketChannel.bind(new InetSocketAddress(8080));
           
// 设置为非阻塞式
           
serverSocketChannel.configureBlocking(false);
           
// 非阻塞式
           
SocketChannel socketChannel = serverSocketChannel.accept();
           
if (socketChannel != null) {
               
int j = socketChannel.read(byteBuffer);
               
if (j > 0) {
                   
byte[] bytes = Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
                    System.
out.println("获取到数据" + new String(bytes));
                }
            }
            System.
out.println("程序执行完毕..");

        }
catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

 

 

 

public class ClientTcpSocket {
   
public static void main(String[] args) {
       
try {
            Socket socket =
new Socket();
            SocketAddress address =
new InetSocketAddress(InetAddress.getLocalHost(), 8080);
            socket.connect(address);
            socket.getOutputStream().write(
"mayikt".getBytes());
        }
catch (Exception e) {

        }
    }
}

 

 

五种IO模型

Linux下的五种I/O模型

1)阻塞I/O(blocking I/O)

2)非阻塞I/O (nonblocking I/O)

3) I/O复用(select 和poll) (I/O multiplexing)

4)信号驱动I/O (signal driven I/O (SIGIO))

5)异步I/O (asynchronous I/O (the POSIX aio_functions))

 

前面四种都是同步io、第五种是异步IO;

阻塞I/O

NIO多路复用底层实现原理

专业话术:

当我们在调用一个io函数的时候,如果没有获取到数据的情况下,那么就会一直等待;等待的过程中会导致整个应用程序一直是一个阻塞的过程,无法去做其他的实现。

 

 

非阻塞I/O

专业数据:

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

白话文:

小军安排黄牛帮我们代替排队,每次间隔一段时间咨询黄牛 是否轮到自己呢。那么这个时候小军可以实现做其他的事情

 

这种非阻塞式的io还存在那些缺点?也是非常消耗cpu的资源,但是程序不会阻塞。

Cas、乐观锁与悲观锁

 

IO复用(select,poll,epoll)

 

IO复用机制:IO实际指的就是网络的IO、多路也就是多个不同的tcp连接;复用也就是指使用同一个线程合并处理多个不同的IO操作,这样的话可以减少CPU资源。

 

白话文:单个线程可以同时处理多个不同的io操作,应用场景非常广泛:redis原理。Mysql连接原理

 

信号驱动I/O

发出一个请求实现观察监听,当有数据的时候直接走我们异步回调;

 

白话文:小军在排队的时候 只需要去领取一个排队的号码,等到叫到了小军的时候才开始处理业务,这时候小军实际上还是可以去做其他的事情。 银行架构

异步I/O(Aio)

异步io也就是发出请求数据之后,剩下的事情完全实现异步完成

 

Nio与Bio的核心区别

什么是NIO

Java的nio是在Jdk1.4版本之后推出了一套新的io方案,这种io方案对原有io做了一次性能上的升级

NIO翻译成英文 no  blocking io 简称为 nio 非阻塞io,不是new io。

比传统的io支持了面向缓冲区、基于通道实现的io的方案。

NIO与BIO区别

 

IO

Nio

面向流(Stream oriented)

面向缓冲区(Buffer oriented)

阻塞式(Blocking IO)

非阻塞式(Non blocking IO)

 

选择器(Selectors)

 

 

传统的bio(同步阻塞 )是面向与流传输的,而NIO(同步非阻塞io)是面向与缓冲区非阻塞式的io,其中最大的亮点就是多路io复用机制。

阻塞式io:当我们没有获取到数据的时候,整个应用程序会实现阻塞等待,不能实现做其他的事情。

非阻塞式io:不管是否有获取到数据,都必须立马获取到结果,如果没有获取数据的情况下,就会不断的重试获取数据

类似于之前学习:cas、悲观和乐观锁

NIO的核心组件

通道(Channel)

通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。

              

缓冲区(Buffer)

Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。

选择器(Selector)

Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;

 

NIO设计思想伪代码

public class SocketNioTcpServer {

    private static List<SocketChannel> listSocketChannel = new ArrayList<>();

    private static ByteBuffer byteBuffer = ByteBuffer.allocate(512);

  

    public static void main(String[] args) {

        try {

            // 1.创建一个ServerSocketChannel

            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

            // 2. 绑定地址

            ServerSocketChannel bind = serverSocketChannel.bind(new InetSocketAddress(8080));

            serverSocketChannel.configureBlocking(false);

            while (true) {

                SocketChannel socketChannel = serverSocketChannel.accept();

                if (socketChannel != null) {

                    socketChannel.configureBlocking(false);

                    listSocketChannel.add(socketChannel);

                }

                for (SocketChannel scl : listSocketChannel) {

                    try {

                        int read = scl.read(byteBuffer);

                        if (read > 0) {

                            byteBuffer.flip();

                            Charset charset = Charset.forName("UTF-8");

                            String receiveText = charset.newDecoder().decode

                                    (byteBuffer.asReadOnlyBuffer()).toString();

                            System.out.println("receiveText:" + receiveText);

                        }

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                }

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

 

 

 

 

 

使用jdk原生api实现nio

public class NIOServer {

  

    /**

     * 创建一个选择器

     */ 

    private Selector selector;

  

    public void initServer(int port) throws IOException {

        // 获得一个ServerSocketChannel通道

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 设置通道为非阻塞

        serverSocketChannel.configureBlocking(false);

        // 将该通道对应的ServerSocket绑定到port端口     

        serverSocketChannel.bind(new InetSocketAddress(port));

        // 获得一个通道管理器

        this.selector = Selector.open();

        // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,

        // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    }

  

    public void listen() throws IOException {

        System.out.println("服务端启动成功!");

        // 轮询访问selector

        while (true) {

            // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞

            int select = selector.select();

            if (select == 0) {

                continue;

            }

            // 获得selector中选中的项的迭代器,选中的项为注册的事件

            Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();

            while (ite.hasNext()) {

                SelectionKey key = (SelectionKey) ite.next();

                // 删除已选的key,以防重复处理

                ite.remove();

  

                if (key.isAcceptable()) {// 客户端请求连接事件

                    ServerSocketChannel server = (ServerSocketChannel) key.channel();

                    // 获得和客户端连接的通道

                    SocketChannel channel = server.accept();

                    // 设置成非阻塞

                    channel.configureBlocking(false);

  

                    // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。

                    channel.register(this.selector, SelectionKey.OP_READ);

  

                } else if (key.isReadable()) {// 获得了可读的事件

                    read(key);

                }

  

            }

  

        }

    }

  

    public void read(SelectionKey key) throws IOException {

        // 服务器可读取消息:得到事件发生的Socket通道

        SocketChannel channel = (SocketChannel) key.channel();

        // 创建读取的缓冲区

        ByteBuffer buffer = ByteBuffer.allocate(512);

        channel.read(buffer);

        byte[] data = buffer.array();

        String msg = new String(data).trim();

        System.out.println("服务端收到信息:" + msg);

        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));

        channel.write(outBuffer);// 将消息回送给客户端

    }

  

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

        NIOServer server = new NIOServer();

        server.initServer(8000);

        server.listen();

    }

}

 

 

 

Redis是如何单线程且又能够非常好支持并发

Redis官方没有windows版本redis,只有linux版本的reids

Redis的底层是采用nio 多路io复用机制实现对多个不同的连接(tcp)实现io的复用;能够非常好的支持高并发,同时能够先天性支持线程安全的问题。

备注:使用一个线程维护多个不同的io操作 原理使用nio的选择器,将多个不同的Channel统一交给我们的selector(选择器管理)

但是nio的实现在不同的操作系统上存在差别:在我们windows操作系统上使用select实现轮训机制、在linux操作系统使用epoll

备注:windows操作系统是没有epoll

在windows操作系统中使用select实现轮训机制时间复杂度是为 o(n),而且这种情况也会存在空轮训的情况,效率非常低、其次默认对我们的轮训有一定限制,所以这样的话很难支持上万tcp连接。

所以在这时候linux操作就出现epoll实现事件驱动回调形式通知,不会存在空轮训的情况,只是对活跃的socket实现主动回调,这样的性能有很大的提升 所以时间复杂度为是o(1)

注意:windows操作系统没有epoll、只有linux操作系统有。

所以为什么Nginx、redis能够支持非常高的并发 最终都是靠的linux版本的 io多路复用机制epoll

 

 

 

 

 

 

 

 

 

 

 

相关文章: