2. Netty初认识--NIO入门---服务端处理概述

  |   0 评论   |   0 浏览

BIO (Blocking)在处理的时候,每一个客户端请求,新启动一个线程进行操作,当并发量大的时候扛不住。

伪异步IO采用线程池和任务队列的方式有所改进,避免每个请求都创建一个新的线程;但是此方法底层通信依然采用同步阻塞模型,无法从根本上解决问题,

BIO是同步的,服务器或者客户端有一方响应慢,就会造成对方也在等待。
NIO可以提供异步操作。

NIO弥补了原来同步阻塞I/O的不足,提供了高速的,面向快的I/O. 了解一下NIO类库的相关概念:

1. 缓冲区Buffer
缓冲区Buffer是一个对象,包含一些要写入或者要读出的数据. 在面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中。
在NIO库中,所有数据都是用缓冲区处理的. 缓冲区实质上是一个数组.通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。事实上每一种Java基本类型(除了Boolean类型) 都对应一种缓冲区:
ByteBuffer: 字节缓冲区
CharBuffer: 字符缓冲区
ShortBuffer: 短整形缓冲区
IntBuffer: 整形缓冲区
LongBuffer: 长整形缓冲区
FloatBuffer: 浮点型缓冲区
DoubleBuffer: 双精度浮点型缓冲区

2. 通道Channel
Channel是个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(InputStream 或者 OutputStream),而且通道可以用于读、写或同时用于读写。

因为Channel是全双工的,所以它可以比流更好的映射底层操作系统的API。Channel可以分为两大类,用于网络读写的SelectableChannel和用于文件操作的FileChannel.

3. 多路复用器Selector
Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel集合,进行后续的I/O操作。一个多路复用Selector可以同时轮询多个Channel.

image.png

下面以服务端创建进行说明:(以下看做伪代码,不可以运行)

package club.wujingjian.com.wujingjian.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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 ServerPoint {

    public static void main(String[] args) throws IOException {
        //1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,
        ServerSocketChannel acceptorSvr = ServerSocketChannel.open();

        //2. 绑定监听端口,设置连接为非阻塞模式,
        int port = 8989;
        acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"),port));
        acceptorSvr.configureBlocking(false);

        //3. 创建Reactor线程,创建多路复用器并启动线程
        Selector selector = Selector.open();
        new Thread(new ReactorTask()).start();

        //4. 将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件
        SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ACCEPT, ioHandler);

        //5. 多路复用器在多线程run方法的无线循环体内轮训准备就绪的Key
        int num = selector.select();
        Set selectedKeys = selector.selectedKeys();
        Iterator it = selectedKeys.iterator();
        while (it.hasNext()) {
            SelectionKey key1 = (SelectionKey) it.next();
            // ....   处理 IO 事件.....
        }

        //6. 多路复用监听到有新的客户端接入,处理新的接入请求,完成TCP 三次握手,建立物理链路
        SocketChannel channel = acceptorSvr.accept();

        //7. 设置客户端链路为非阻塞模式,
        channel.configureBlocking(false);
        channel.socket().setReuseAddress(true);

        //8. 将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作
        SelectionKey key2 = channel.register(selector, SelectionKey.OP_READ,ioHandler);

        //9. 异步读取客户端请求消息到缓冲区
        int readNumber = channel.read(receivedBuffer);

        //10. 对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装为Task,投递到业务线程池中,进行业务逻辑编排
        Object message = null;
        while (buffer.hasRemain()) {
            byteBuffer.mark();
            Object message = decode(byteBuffer);
            if (message == null) {
                byteBuffer.reset();
                break;
            }
            messageList.add(message);
        }
        if(!byteBuffer.hasRemain()) {
            byteBuffer.clear();
        } else {
            byteBuffer.compact();
        }
        if (messageList != null && !messgeList.isEmpty()) {
            for (Object messageE : messageList) {
                handlerTask(messageE);
            }
        }


        //11. 将POJO对象encode成ByteBuffer,调用SocketChannel 的异步write接口,将消息异步发送给客户端,
        socketChannel.write(buffer);
        //注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓冲区,
    }
}


标题:2. Netty初认识--NIO入门---服务端处理概述
作者:码农路上
地址:http://wujingjian.club/articles/2020/02/26/1582714725734.html