Tomcat容器流程:处理请求,响应数据流程

Tomcat容器流程:处理请求,响应数据流程,第1张

Tomcat容器流程:处理请求,响应数据流程

目录

处理请求流程:

响应数据过程:


处理请求流程:

Tomcat通过Endpoint组件接收socket连接,接收到一个socket连接后会执行如下步骤

1.第一次从socket中获取数据到InputBuffer中,BIO对应是InternallInputBuffer,父类是AbstractInputBuffer

2.然后基于InputBuffer进行解析数据

3.先解析请求行,把请求方法,请求uri,请求协议等封装到org.apache.copyte.Request对象中

4.org.apache.coyote.Request中的属性都是MessageBytes类型,直接可以理解为字节类型,因为从socket中获取到的数据是字节,在解析过程中不用直接把字节转成字符串,并且MessageBytes虽然表示字节,但是它并不会真正的存储字节,还是使用ByteChunk基于InputBuffer中的字节数组来进行标记,标记字节数组中的那个一个范围表示请求方法,那个一个范围表示请求uri等等。

5.然后解析头,和解析请求行类似

6.解析完请求头后,就基于请求头来初始化一些参数,比如connection是keepalive是close,比如是否有content-length,并且对于的长度是多少等等,还包括当前请求在处理请求时应该使用那个InputFilter。

7.然后将请求交给容器

8.容器再将请求交给具体的servlet进行处理

9.servlet在处理请求的过程中会利用response进行响应,然后返回给客户端,一个普通的响应流程会把数据先写入一个缓冲区,当使用flush,或者close方法时缓冲区中的内容发送给socket,下面章节是讲解tomcat响应请求流程

10.servlet处理完请求后,先会检查是否需要把响应数据发送给socket

11.接着看当前请求的请求体是否处理结束,是否还有剩余数据,如果有剩余的数据需要把这些数据处理掉,以便能够获取到下一个请求的数据

12.然后回到第一步开始处理下一个请求

响应数据过程:

当我们在Servlet中调用如下方法

OutputStream outputSteram = resp.getOutputStream();
outputSteram.write("test".getBytes());

resp对应的类型为ResponseFacade,得到的outputSteram的类型为CoyoteOutputStream.

所以响应数据是通过CoyoteOutputStream这个类处理的。

当调用outputStream的write方法时,实际调用的就是CoyoteOutputStream类的write(byte[] b)方法。

@Override
public void write(byte[] b) throws IOException{
    write(b,0,b.length);
}
@Override
public void write(byte[] b,int off ,int len)throws IOException{
ob.write(b,off,len);
}

在CoyoteoutputStream类中有一个属性是ob,类型为org.apache.catalina.connector.OutputBuffer,该属性是在构造CoyoteOutputStream对象时初始化,先注意OutputBuffer所在的包。

我们在调用write方法时,实际调用OutputBuffer的write方法,而write方法实际调用的是类中的writeBytes(byte b[],int off, int len)

private void writeBytes(byte b[],int off ,int len)throw IOException{
 
     if(closed){
       return;
}
bb.append(b,off,len);
bytesWritten +=len;
if(doFlush){
    //每次write都缓冲中的数据发送出去 
    bb.flushBuffer();
}
}

在OutputBuffer中有一个属性叫做bb,类型是ByteChunk,在Tomcat响应流程中,可以把ByteChunk类当做一个缓冲区的实现,该类中有一个字节数组,名字叫做buff,默认是8192.

当我们在write字节数据时,就是把数据添加到ByteChunk对应的缓冲区buff中,当把数据添加到缓冲区后,如果有其他执行outputSteam的flush()方法,则doFlush为true,那么则会调用bb.flushBuffer();

这里就要考虑一个问题,我们把数据都写到缓冲区buff中,那么buff中的数据时何时传递给socket中的呢?

在ByteChunk中有一个属性out,类型是ByteOutputChannel,它表示缓冲区中的数据流该向那个渠道,为了方便理解,可以先理解为渠道就是socket,表示把缓冲区中的数据发送给socket,当实际情况并不是,暂且这么理解。

ByteOutputChannel类中有一个方法realWriteBytes(byte buf[],int off ,int len),当调用out.realWriteBytes(src,off,len)方法时,就会把src数据发送到对应的驱动。

在当前这个ByteChunk中,它的out对应的仍然是org.apache.catalina.connector.OutputBuffer,在这类中存在该方法:

public void realWriteBytes(byte buf[],int off,int cnt)  throws IOException{
        if(closed){
       return;
}
if(coyoteResponse == null){
   return;
}
if(cnt > 0){
   outputChunk.setBytes(but,off,cnt);
   try{
    coyoteResponse.doWrite(ouputChunk);   
} catch(IOException e){
   throw new  ClientAbortException(e);
}
}
}

该方法中通过一个outputChunk来标记数据,表示标记的这些数据时发送给socket的,而真正的发送逻辑交给coyoteResponse.dowrite(outputChunk)来进行处理,coyoteResponse的类型为org.apache.coyote.Response.

具体怎么将缓冲区中所标记的数据怎么发送出去的,我们等会再看,我们先来看到底何时会触发这个发送动作。

ByteChunk中有一个方法append,表示向缓冲区buff中添加数据,其中有一个逻辑,当缓冲区满了之后就会调用out.realWriteBytes(src,off,len),表示把缓冲区中的数据发送出去,缓存区的大小有一个限制,可以修改,默认为8192。

还有一种情况就算缓冲区没有满,但是在write之前调用用过flush方法,那么本次write的数据会先放入缓冲区,然后再把缓冲区中的数据发送出去。

当我们调用outputSteram的flush方法时:

1.先判断是否发送过响应头,没有发送则先发送响应头

2.在调用ByteChunk的flushBuffer方法,把缓冲区中剩余的数据发送出去

3.因为上文中我们所理解的将缓冲区的数据发送出去,是直接发送给socket,但实际情况是把数据发送给另外一个缓冲区,这个缓冲区也是用ByteChunk类实现的,名字是socketBuffer,所以我们在使用flush方法时需要把socketBuffer中的数据真正发送给socket。

接下来我们看看coyoteResponse.dowrite(outputChunk);的具体实现细节。

该方法实际调用的是outputBuffer.doWrite(chunk,this);这里的outputBuffer的类型是InternalOutPutbUFFER,执行doWrite方法时,调用的是父类AbstractOutputBuffer的doWrite方法。

该doWrite方法中,首先会判断响应头是否已经发送,如果没有发送,则会构造响应头,并发响应头发送给socketbuffer,发送完响应头,会调用响应的output的activeFilters,对于不用的响应体需要使用不同的发送逻辑。比如ChunkedOutputFilter用来发送分块响应体的,IdentityoutputFilter是用来发送Content-length响应体的,VoidoutputFilter不会真正的把数据发送出去。

构造响应头时,会识别响应体应该通过什么OutputFilter来发送,如果响应中存在content-length那么则使用IdentityOutputFilter来发送响应体,否则使用ChunkedOutputFilter,当然还有一些异常情况下使用VoidOutputFilter,表示不会发送响应体。

那现在的问题是,响应体的Content-length是在什么时候确定的?

答案是:当请求在servlet中执行完成后,会调用response.finishResponse()方法,该方法会调用outputBuffer.close(),该outputBuffer就是org.apache.catalina.connector.outputBuffer,该方法会判断响应体是否已发送,如果在调用这个close时响应头还没有发送,则表示响应体的数据在之前一直没有发送过,一直存在了第一层缓冲区中,并且一直没有塞满该缓冲区,因为该缓冲区如果被塞满了,则会发送响应头,所以当执行到close方法时,响应头还没发送过,那么缓冲区中的数据就是响应体全部的数据,即,缓冲区数据的长度就是content-length.

反之,在调用close方法之前,就已经发送过数据了,那么响应头中就没有content-length,就是用ChunkedOutputFilter来发送数据.

并且在执行close方法时,会先将响应头的数据发送到socketbuffer,然后将第一层缓冲区的数据通过对应的outputfilter发送给socketbuffer,然后调用outputfilter的end方法,identityoutputfilter的end方法实现很简单,而chunkedoutputfilter的end方法则相对做的事情更多一点,因为chunkedoutputfilter的dowrite一次只会发送已坏数据,所以end要负责循环调用diwrite方法,把全部的数据库发送完,

最后将socketbuffer中的数据发送给socket.

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5682603.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存