最近读代码的节奏很慢,。。在HttpPrcoessor部分已经停滞了挺久的时间了。。
可能它设计到的东西还是挺多的吧。。。
今天写的是InternalNioInputBuffer类型的分析,它算是Tomcat对IO封装部分的一个十分重要的环节了,可以将其理解为用于沟通底层socket与上层servletInputStream的中间层。。。而且InternalNioInputBuffer中还实现了HTTP协议的requestLine与header部分的解析。。。
嗯,这里先来看看简单的继承结构吧:
这个够简单的了吧,这里先来看看最上层的接口InputBuffer的定义吧:
public interface InputBuffer {
/** Return from the input stream.
IMPORTANT: the current model assumes that the protocol will ‘own‘ the
buffer and return a pointer to it in ByteChunk ( i.e. the param will
have chunk.getBytes()==null before call, and the result after the call ).
*/
//从与这个request关联的socket读取数据保存到chunk里面
public int doRead(ByteChunk chunk, Request request)
throws IOException;
}
好啦,接下来就是AbstractInputBuffer的定义了,这里先来看看的一些重要的属性的申明吧:
protected Request request; //与当前buffer关联的tomcat的request
protected MimeHeaders headers; //用于保存所有的处理出来的header数据
protected boolean parsingHeader; //是否正在处理header
protected boolean swallowInput; //是否要接收当前关联的连接的数据
protected byte[] buf; //用于存数据的字节数组
protected int lastValid; //表示最后一个可用数据的下标
protected int pos; //当前读取到的下标
protected int end; //用于指向header在buffer的尾部的下标
protected InputBuffer inputStreamInputBuffer; //这里内部有一个InputBuffer,用于代理当前对象的一些方法,主要是暴露给外部的读取数据的api
protected InputFilter[] filterLibrary; //所有的filter
protected InputFilter[] activeFilters; //活动的filter,当外部代码调用buffer来读取数据的时候,如果有filter,那么将会用filter来处理一下
protected int lastActiveFilter; //最后一个活动的filter的下标嗯,这里属性的定义还不算太多吧,不过都挺重要的,具体的每个属性是什么意思,后面的注释应该也说的比较清楚了。。。而且可以看到,具体的数据的保存其实也是保存到一个字节数组里面的。。。
另外这里可以看到定义了一个InputBuffer类型的属性inputStreamInputBuffer,这里大概可以理解为一个外观模式吧,通过这个对象,具体的向代理当前对象的一些方法,用于暴露出doRead方法,来看看在AbstractInputBuffer中定义的doRead方法吧:
//读取数据的方法
public int doRead(ByteChunk chunk, Request req)
throws IOException {
if (lastActiveFilter == -1) //如果都没有活动的filter,anemia可以直接读取
return inputStreamInputBuffer.doRead(chunk, req);
else //否则需要filter介入
return activeFilters[lastActiveFilter].doRead(chunk,req);
}嗯,这里也可以知道filter是干嘛用的了,这里如果有filter的话,那么会在filter上面调用doRead方法,这样可以在具体的读取的数据的时候做一些额外的工作,例如context-length的filter,就用于限制读取的数据的数量。。。
当然最终还是调用inputStreamInputBuffer来读取数据的。。具体的会在InternalNioInputBuffer中交代.
好了,接下来再来看看InternalNioInputBuffer类型,还是先来看看它的属性定义吧:
private NioChannel socket; //关联的底层的niochannel
private NioSelectorPool pool; //提供selector的pool,它用于读取http请求的body数据,因为必须要阻塞的读取,所以为了节约CPU,就只有用selector来协作了
private final int headerBufferSize; //允许的最大的hader大小
private int socketReadBufferSize; //下面的channel的设置的读取的buffer的size
首先是底层关联的nioChannel,然后就还有一些配置。。这里再来看看它的构造函数吧:
//构造函数,第一个参数是关联的request对象,第二个参数是最大的header数据限制
public InternalNioInputBuffer(Request request, int headerBufferSize) {
this.request = request;
headers = request.getMimeHeaders(); //用于保存header
this.headerBufferSize = headerBufferSize;
inputStreamInputBuffer = new SocketInputBuffer(); //真正用到的buffer,其实只不过是为了实现将当前底层字节数组里面的数据暴露出去,doRead方法,将数据写到某个chunk
filterLibrary = new InputFilter[0]; //input的filter吧
activeFilters = new InputFilter[0]; //活动的filter,例如content-length什么的限制
lastActiveFilter = -1; //刚开始直接设置为-1,表示低下的字节数组没有数据
//一些标志位的初始化
parsingHeader = true; //表示还要解析header
parsingRequestLine = true;
parsingRequestLinePhase = 0;
parsingRequestLineEol = false;
parsingRequestLineStart = 0;
parsingRequestLineQPos = -1;
headerParsePos = HeaderParsePosition.HEADER_START;
headerData.recycle();
swallowInput = true; //默认是ture的,表示还要接受当前连接的数据
其实这里主要是一些标志位的初始化,另外这里还有一个比较重要的这里为inputStreamInputBuffer属性初始化了对象,是SocketInputBuffer类型的,它是一个当前类型的内部类。。。来看看它的定义吧:
//这里实现了一个简单的InputBuffer,主要是为了代理当前对象的读取数据的方法,将方法暴露给外面的对象
protected class SocketInputBuffer
implements InputBuffer {
/**
* Read bytes into the specified chunk.
*/
@Override
public int doRead(ByteChunk chunk, Request req )
throws IOException {
if (pos >= lastValid) { //如果低下的字节数组已经没有数据可以读取了,这里主要是调用fill方法,从socket里面读取数据,并将其保存到当前对象的字节数组
if (!fill(true,true)) //这里是阻塞的读取//read body, must be blocking, as the thread is inside the app
return -1;
}
int length = lastValid - pos; //表示还有多少数据可以读取
chunk.setBytes(buf, pos, length); //相当有从底层的字节数据中读数据,这里会重用下面的byte数组
pos = lastValid; //更新pos的下标
return (length);
}
}嗯,这里应该就能够明白为啥前面说是为了代理当前对象的方法,用于暴露给外部doRead方法了吧,因为它的具体实现还是通过InternalNioInputBuffer的方法来实现的。。。这里说白了就是将当前对象的字节数组buf设置到chunk里面去,也算是数据的重用吧,然后再设置下标就好了。。。
具体的读取数据的方法则是通过fill方法来实现的。。。来看看吧:
//读取数据到缓冲,在nio里,第一个参数一般是ture,第二个一般是false,但是在读取body的时候则必须是阻塞的
protected boolean fill(boolean timeout, boolean block)
throws IOException, EOFException {
boolean read = false;
if (parsingHeader) { //如果是正在处理header的极端,那么这里可能要判断是否超出最大值
if (lastValid > headerBufferSize) {
throw new IllegalArgumentException
(sm.getString("iib.requestheadertoolarge.error"));
}
// Do a simple read with a short timeout
read = readSocket(timeout,block)>0;
} else { //body的数据吧
lastValid = pos = end;
// Do a simple read with a short timeout
read = readSocket(timeout, block)>0; //从socket读取数据
}
return read;
}嗯,这里读取的方法有阻塞的方式,也有非阻塞的方法吧,由于现在tomcat都是用nio的方法,所以在处理http请求的requestline与header的时候都是非阻塞的,但是在处理请求的body的时候则必须是阻塞的了,因为读取body部分的代码一般是在servlet部分控制的也就是由上层代码控制,这个就已经超出了tomcat控制的范围、。、、、
再来看看readSocket方法吧:
//从底层的socket读取数据
private int readSocket(boolean timeout, boolean block) throws IOException {
int nRead = 0;
socket.getBufHandler().getReadBuffer().clear(); //调用socket关联的nio的buf的clear方法,待会可以将socket里读取的数据写进去了
if ( block ) { // 如果是阻塞的话将会后去selector来关联当前的socket,为节约cpu吧,在读取body的时候就会是阻塞的读取
Selector selector = null;
try {
selector = pool.get(); //从selector的pool里面获取一个selector对象
} catch ( IOException x ) {
// Ignore
}
try {
NioEndpoint.KeyAttachment att =
(NioEndpoint.KeyAttachment) socket.getAttachment(false);
if (att == null) {
throw new IOException("Key must be cancelled.");
}
nRead = pool.read(socket.getBufHandler().getReadBuffer(),
socket, selector,
socket.getIOChannel().socket().getSoTimeout());
} catch ( EOFException eof ) {
nRead = -1;
} finally {
if ( selector != null ) pool.put(selector);
}
} else { //这里是非阻塞的读取数据,有多少就读多少就好了
nRead = socket.read(socket.getBufHandler().getReadBuffer());
}
if (nRead > 0) { //如果有数据读取出来了
socket.getBufHandler().getReadBuffer().flip(); //调用flip,待会可以从buf里读取数据了
socket.getBufHandler().getReadBuffer().limit(nRead); //设置limit参数
expand(nRead + pos); //这里会检查低下的字节数据是否够大,如果不够大的话,会扩大
socket.getBufHandler().getReadBuffer().get(buf, pos, nRead); //将socket读取的数据转移到当前对象使用的buf,也就是创建的字节数组
lastValid = pos + nRead; //更新最后一个有效数据的下标 更新lastValid下标
return nRead; //返回读取的数据量
} else if (nRead == -1) { //如果是-1的话,那么表示当前出错了
//return false;
throw new EOFException(sm.getString("iib.eof.error"));
} else {
return 0;
}
}其实说白了就是通过底层的channel来读取数据,然后再将读取到的数据从channel的buf里面拷贝到当前对象的字节数组buf里面就好了。。。这里多了一层拷贝,不知道是不是应该优化一下呀。。。
好了,到这里整个InternalNioInputBuffer类型如何通过底层channel读取数据应该很清楚了吧,另外它是如何暴露给外面doRead方法应该也算清楚了。。。。
当然这并不是InternalNioInputBuffer类型的全部,因为其实它还会具体的负责http协议的处理,有如下方法:
(1)parseRequestLine,用于解析http的requestLine,
(2)parseHeaders,用于解析header部分。。。
嗯,最后再来总结一下InternalNioInputBuffer与httpprocessor对象的关系吧。。。
//构造函数,这里主要是创建buffer对象
// 第一个参数是最大的httpheader数量可以有多少,第二个是用到的endpoint,第三个与第四个参数主要是对数据传输filter的设置
public Http11NioProcessor(int maxHttpHeaderSize, NioEndpoint endpoint,
int maxTrailerSize, int maxExtensionSize) {
super(endpoint);
inputBuffer = new InternalNioInputBuffer(request, maxHttpHeaderSize); //创建inputbuffer
request.setInputBuffer(inputBuffer); //在request设置inputbuffer
outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize); // 创建outputbuffer
response.setOutputBuffer(outputBuffer); //在response设置outputbuffer
initializeFilters(maxTrailerSize, maxExtensionSize); //初始化input与output的filter
}在httpprocessor对象创建的时候就会相应的创建InternalNioInputBuffer对象,并且设置相应的属性,
然后在每次process方法的时候,会首先执行如下代码:
getInputBuffer().init(socketWrapper, endpoint); //初始化buffer,这里主要是为了将inputbuf底层用的socket与当前要处理的socket关联起来
为InternalNioInputBuffer对象初始化,例如设置底层的channel,用到的selectorPool等:
//在httpprocessor里面用到的inputbuffer的初始化方法,这里书要是为了保存关联的socket以及一些数据大小限制参数的设置
protected void init(SocketWrapper<NioChannel> socketWrapper,
AbstractEndpoint<NioChannel> endpoint) throws IOException {
socket = socketWrapper.getSocket(); //首先获取底层的channel
if (socket == null) {
// Socket has been closed in another thread
throw new IOException(sm.getString("iib.socketClosed"));
}
socketReadBufferSize =
socket.getBufHandler().getReadBuffer().capacity(); //获取buffer的容量大小
int bufLength = headerBufferSize + socketReadBufferSize; //构建要创建的长度
if (buf == null || buf.length < bufLength) {
buf = new byte[bufLength]; //构建字节数组
}
pool = ((NioEndpoint)endpoint).getSelectorPool(); //selector的pool
}然后就还有调用parseRequestLine与parseHeaders方法来处理http请求。。
最后他还剩下的工作,就是暴露给外部doRead方法,用于上层对象获取底层channel接收到的数据。。。
Tomcat源码阅读之底层IO封装(1)InternalNioInputBuffer的分析,布布扣,bubuko.com
Tomcat源码阅读之底层IO封装(1)InternalNioInputBuffer的分析
原文:http://blog.csdn.net/fjslovejhl/article/details/22403917