NIO主要有三大核心部分:Buffer(缓冲区),Channel(通道), Selector(选择器),本篇主要介绍Http11InputBuffer缓冲区。

nio常见方法

  • ByteBuffer包含几个基本的属性:
    • position:当前的下标位置,表示进行下一个读写操作时的起始位置
    • limit:结束标记下标,表示进行下一个读写操作时的(最大)结束位置
    • capacity:该ByteBuffer容量
    • mark: 自定义的标记位置,默认为-1
    • 四者有以下关系:mark <= position <= limit <= capacity
  • ByteBuffer的allocate()方法表示分配字节数,一个int型占4字节,1个char型占1字节,此时的limit和capacity等于分配长度
  • put()方法写入字节,此时position移位字节长度
  • get()方法读取字节,此时position下标+1
  • rewind()方法,position下标变为0
  • flip()方法,limit变为position,position下标变为0
  • compact()方法,将position至limit之间的数据移到最左端,position=limit-position,limit=capacity
  • mark()方法,将mark置为当前position
  • clear()方法,position置为0,mark置为-1,limit置为capacity,相当于初始化
  • reset()方法,将position置为当前mark,不能为-1
  • SocketChannel的read()方法,读入数据到指定的ByteBuffer中,已当前position为起始位填充,返回读了多少字节nRead,此时position=position+nRead,这里的position最大为limit
  • 可以通过new String(byteBuffer.array(),"UTF-8")快速查看byteBuffer里的数据

Http11Processor的创建

任务线程池默认命名规则为http-nio-8080-exec-xxx,放入的是由SocketProcessorBase封装的线程,切入点从它的run()方法开始说起。

SocketProcessorBase->run():
    // SocketProcessorBase是一个抽象类,它的子类是NioEndpoint的SocketProcessor
    // 调用子类的doRun()方法
    doRun();
NioEndpoint->SocketProcessor->doRun():
    state = getHandler().process(socketWrapper, event);
AbstractProtocol->ConnectionHandler->process():
    // 省略一段获得processor的过程,processor是处理类
    // 优先从缓存获取,如果缓存没有的话,会根据协议创建对应的处理类
    ......
    // 协议是Http11NioProtocol,对应的处理类为Http11Processor
    // Http11Processor有两个重要的成员变量,inputBuffer和outputBuffer,分别对应Http11InputBuffer和Http11OutputBuffer
    state = processor.process(wrapper, status);
AbstractProcessorLight->process():
    state = service(socketWrapper);复制代码

Http11InputBuffer

Http11InputBuffer内置ByteBuffer,它是用来读取socket的关键

Http11Processor->service():
    // 如果byteBuffer为空或者它的capacity小于bufLength的话,分配bufLength长度的字节数
    // bufLength默认情况下为AbstractHttp11Protocol.maxHttpHeaderSize+SocketProperties.appReadBufSize,是可以设置的
    inputBuffer.init(socketWrapper);
    ......
    // 解析请求行
    if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
        protocol.getKeepAliveTimeout())) {
            if (inputBuffer.getParsingRequestLinePhase() == -1) {
                return SocketState.UPGRADING;
            } else if (handleIncompleteRequestLineRead()) {
                break;
            }
        }
    ......
    // 省略的部分是解析请求头,过程与解析请求行基本类似,它在headers属性中体现
    // 校验请求头中的部分属性,继续对request赋值
    prepareRequest();
    ......
    getAdapter().service(request, response);
    ......复制代码

Http11InputBuffer内置request,它与Http11Processor内置的request是一个对象,解析的过程中会对request赋值,这里的request就是最原始的Request类

Http11InputBuffer->parseRequestLine():
    if (!parsingRequestLine) {
        return true;
    }
    if (parsingRequestLinePhase < 2) {
        // 如果当前下标等于limit的话,需要补数据,因为数据可能已达到一次读取的最大上限
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill(false)) {                // --1
                parsingRequestLinePhase = 1;
                return false;
            }
        }
    }
    // 从0开始读到第一个空格,设置method属性(此处为POST)
    if (parsingRequestLinePhase == 2) {
    ......
    // 是空格或制表符就继续读,否则停止
    if (parsingRequestLinePhase == 3) {
    ......
    // 读到空格或制表符,如果有问号,设置queryString属性(此处为name=123&password=123456)
    // 否则只设置requestURI属性(此处为/hello/test2)
    if (parsingRequestLinePhase == 4) {
    ......
    // 是空格或制表符就继续读,否则停止
    if (parsingRequestLinePhase == 5) {
    ......
    // 读到下一个换行符,设置protocol属性(此处为HTTP/1.1)
    if (parsingRequestLinePhase == 6) {
    ......
    // 这样下次进来的时候,就不会重复读取了
    parsingRequestLine = false;
    parsingRequestLinePhase = 0;
    parsingRequestLineEol = false;
    parsingRequestLineStart = 0;
    return true;

nio使用channel.read,如果读不到数据返回0
Http11InputBuffer->fill():                // --1
    byteBuffer.mark();
    // 读取数据到ByteBuffer中,block为false,所以这里使用channel.read
    int nRead = wrapper.read(block, byteBuffer);
    // limit设置为position,同时position设置为mark的位置
    byteBuffer.limit(byteBuffer.position()).reset();
    if (nRead > 0) {
        return true;
    } else if (nRead == -1) {
        // -1是异常情况,可以不用管
        throw new EOFException(sm.getString("iib.eof.error"));
    } else {
        return false;
    }复制代码

在解析的每一步过程中,都会有判断数据不足,用fill方法尝试读取数据的操作,如果读不到数据的话会直接返回,结束此次处理。当Poller再次检测到该通道的可读事件后,nio会再次从channel里读数据,并接着上一次结束的位置继续处理。以此类推,最终完成channel的读取。

非阻塞式io使用channel.read(ByteBuffer byteBuffer),阻塞式io一般使用inputstream.read(byte b[]),如果读不到数据会一直阻塞,这是阻塞式io与非阻塞式io最根本的区别

一般来说,byteBuffer长这样(\r、\n、\t没法展示)SpringBoot Tomcat(3) nio的应用(中)_SpringBoot Tomcat

对应解析出来的request长这样SpringBoot Tomcat(3) nio的应用(中)_SpringBoot Tomcat_02

附录:不同body类型在byteBuffer中的体现

form-data

SpringBoot Tomcat(3) nio的应用(中)_SpringBoot Tomcat_03这里传了txt格式文本export.txt,里面的值为123456,都被解析出来了

x-www-form-urlencoded

SpringBoot Tomcat(3) nio的应用(中)_SpringBoot Tomcat_04

raw

SpringBoot Tomcat(3) nio的应用(中)_SpringBoot Tomcat_05传输的是json类型的