最近读代码的节奏很慢,。。在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