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下标+1rewind()方法,position下标变为0flip()方法,limit变为position,position下标变为0compact()方法,将position至limit之间的数据移到最左端,position=limit-position,limit=capacitymark()方法,将mark置为当前positionclear()方法,position置为0,mark置为-1,limit置为capacity,相当于初始化reset()方法,将position置为当前mark,不能为-1SocketChannel的read()方法,读入数据到指定的ByteBuffer中,以当前position为起始位填充,返回读了多少字节nRead,此时position=position+nRead,这里的position最大为limit。该方法为非阻塞方式。- 可以通过
new String(byteBuffer.array(),"UTF-8")快速查看byteBuffer里的数据 - 可以通过
new String(byteBuffer.array(),0,byteBuffer.limit())快速查看byteBuffer里的数据 CharBuffer是字符缓冲,它与字节缓冲的方法大同小异
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内置org.apache.coyote.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类型的