分布式网络通信框架Netty_深入Linux网络IO模型_学习总结

                                    分布式网络通信框架Netty

                                                 深入Linux网络IO模型

                                                                                          学习总结

                                                                                                                                                                   田超凡 2019年10月20日

                                                                                                                                                                        转载请注明原作者

1 同步和异步处理

在IO处理模式中,同步指的是程序从上往下执行;异步是指重新开启一个新分支,相互不会影响;我们的Http协议请求默认情况下同步形式调用,如果调用过程非常耗时的情况下,客户端等待时间就非常长,这这种形式我们可以理解为阻塞式处理模式。

 

2 Linux网络IO模型

Linux常用的五种IO模型按照实现方式不同可以分为两类:同步IO模型异步IO模型

 

2.1 阻塞I/O(BIO

分布式网络通信框架Netty_深入Linux网络IO模型_学习总结_第1张图片

 

专业话术:

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

 

白话文:

比如我们现在每特教育第六期要开班啦,由于口碑非常好、就业薪资非常高 这个时候有很多程序猿来排队报名学习;这个时候小军前面还有100个学员正在排队报名,小军必须要等待前面100个学员报名完成之后,轮训到小军才可以报名;小军为了保证当前的排队位置存在,也不能做其他的事情,这个过程我们可以称作为阻塞式;

 

阻塞IO的特点:高并发模式下执行阻塞,程序无法正常继续执行。

 

2.2 非阻塞I/O (NIO

专业术语

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

 

白话文:

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

 

非阻塞IO特点:非常消耗CPU的资源,但是程序不会阻塞。类似线程安全中的乐观锁(非阻塞IO模型)与悲观锁(阻塞IO模型)

 

2.3 I/O复用(select 和poll) (IOM

专业术语:

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

 

白话文

单个线程可以同时处理多个不同的io操作应用场景非常广泛,如redis原理,Mysql连接原理,都是采用非阻塞IO模型+多路IO复用机制实现IO操作

 

2.4 信号驱动I/O (SIGIO

专业术语:

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

 

白话文:

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

 

2.5异步I/O (AIO

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

 

3 NIO与BIO的核心区别

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

NIO与BIO区别

BIO

Nio

面向流(Stream oriented)

面向缓冲区(Buffer oriented)

阻塞式(Blocking IO)

非阻塞式(Non blocking IO)

 

选择器(多路IO复用)(Selectors)

 

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

NIO(非阻塞IO模型):不管是否有获取到数据,都必须立马获取到结果,如果没有获取数据的情况下,就会不断的重试获取数据,类似于cas、悲观和乐观锁。

BIO是面向文件流传输的,而NIO是面向缓冲区传输的,NIO最大的亮点就是具备选择器,实现多路IO复用机制。

 

4 NIO核心组件

4.1 选择器(Selector)

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

 

4.2 通道(Channel)

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

 

4.3 缓冲区(Buffer)

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

 

NIO设计思想伪代码

public class SocketNioTcpServer {
    private static List 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(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();
        }
    }
}

 

 

 

 

JDK1.4+ 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 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();
    }
}

 

 

在Linux系统中NIO的选择器采用的是Epoll,而Windows系统中选择器采用的是Selector

转载请注明原作者

 

你可能感兴趣的:(Java架构)