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没法展示)
对应解析出来的request长这样
附录:不同body类型在byteBuffer中的体现
form-data
这里传了txt格式文本export.txt,里面的值为123456,都被解析出来了
x-www-form-urlencoded
raw
传输的是json类型的