Tomcat源码学习之--请求数据传输

3 篇文章 0 订阅

本文介绍基于tomcat7中默认的BIO方式
以前说过tomcat通过socket传输 接口请求之路

发送数据

大部分的HTTP请求一般都是长连接,可以从HTTP请求头中看到

一般发送HTTP请求之前会建立一个socket连接。如果tomcat判断需要对这个socket请求进行关闭,会在HTTP请求的响应头中写入Connection:close,当HTTP得到这样的响应就会关闭对应的连接。如果tomcat判断这个连接可以继续使用,保持长连接,响应头就不设置对应的内容,则会继续使用Connection:keep-alive。
注意,如果关闭的话是http发起关闭,而不是tomcat进行关闭

长连接表示请求完成后socket连接不关闭,还可以继续通过这个对应的socket连接继续发送请求。
长连接请求和返回

发送http请求前需要建立一个socket连接,长连接表示请求完成后socket连接不关闭,还可以通过这个socket连接继续发送请求。如果tomcat如果这个socket请求需要关闭,那么http响应头中Connection:close。这样接收完响应之后就会关闭socke连接,如果保持长连接,http响应头就不设置。

接收数据

  1. Tomcat通过Processor(所有协议处理器的通用接口)处理请求。
  2. Processor内部会设置Endpoint,并通过Endpoint的内部类Acceptor接收socket连接。
  3. 客户端发送数据,在服务端的操作系统中有个RecvBuf(缓冲区),操作系统会将数据先放在recvBufferbyte中,如果recvbuf中填满了,就无法发送数据了。每个socket对应一个缓冲区。当客户端写socket的发送数据是,也会放在缓冲区sendbuf中。

参考如下源码内容:

  • AbstractHttp11Processor类中的processSocketWapper方法
  • JIOEndpoint类中有个Acceptor方法,会通过BIO的方式接受socket
  • Socket=serverSocketFactory.accept
  • setSocketOption设置socket参数的方法中有个soTimeout参数,这个参数是指当socke从recvbuf中read数据时,如果recvbuf中数据为空,则read阻塞,具体阻塞时间就是这个参数指定的时间。
  1. 第一次从socket中获取到数据写入InputBuffer中,然后进行解析对应的请求行(请求头),请求方法,请求协议等放入到Request中,并且设置一些参数。
    处理socket
  • BIO的方式,每接收到一个socket就将其交给一个线程,在tomcat的BIO里是每个请求都有一个对应的线程进行处理,当socket关闭后,这个线程也会被释放。
  • NIO则是一个线程处理多个请求。

参考如下源码内容:

  • JIoEndpoint类中的prcocessSocket方法的getExecutor.execute
  • 在BIO的方式中最大请求数maxConnection和最大线程数MaxThread实际是一致的
  • 在调用线程处理时根据run方法的process调用中的的内容,找到AbstarctProtocol中的process方法,方法会发现首先要调用createProcessor创建一个处理,根据不同的实现类里面设置了不同的属性,这个属性也可以通过标签来设置。maxKeepAliveRequests属性,表示这个长连接上能够处理的最大活动数(http请求)默认100
  • 创建处理器完成后,调用对应process方法,解析http请求中的数据。
  • 如果maxKeepAliveRequests==1,那么处理完后连接就会被关闭,意味着对应的设置keepalive=false
  • 对应的判断,如,当前活跃的线程数占线程池最大线程数的比例大于 75%,那么则关闭KeepAlive,不再支持长连接,不支持长连接并不表示不支持连接处理,意味着75%之后的ssocket连接将会被当成短链接处理。但是当超过**100%**的socket连接数时,将不再处理后续的socke连接。
  • getInputBuffer().parseRequest和getInputBuffer().parseHeaders()读取请求体和请求头
  1. 调用adapter进行服务处理,调用fill等一系列方法。

fill方法是将操作系统中的revfbuf读取到tomcat的缓冲区buf中(默认8kb)

  1. 处理过程中会将对应的内容放入响应response中,返回给客户端。同样的也是先放在缓冲区,最后将缓冲区的数据返回。
  2. 最后判断数据是否响应完成,把剩余的数据清除完,获取下个请求。
public SocketState process(SocketWrapper<S> socketWrapper)
    throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    // 设置请求状态为解析状态
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);   
    // 设置IO
    setSocketWrapper(socketWrapper);
    //设置输入、输出缓冲区
    //将socket的InputStream与InternalInputBuffer进行绑定(缓冲区内容)
    getInputBuffer().init(socketWrapper, endpoint);
    // 将socket的OutputStream与InternalOutputBuffer进行绑定
    getOutputBuffer().init(socketWrapper, endpoint);
      // 长连接等一系列标志
    keepAlive = true;
    // NioEndpoint返回true, BIO返回false
    if (endpoint.getUsePolling()) {
        keptAlive = false;
    } else {
        keptAlive = socketWrapper.isKeptAlive();
    }
    // 如果当前活跃的线程数占线程池最大线程数的比例大于75%(默认值),那么则关闭KeepAlive,不再支持长连接
    if (disableKeepAlive()) {
        socketWrapper.setKeepAliveLeft(0);
    }
    // keepAlive的值会从请求中读取,默认为true
    while () {
        // keepAlive如果为true,接下来需要从socket中不停的获取http请求
        // 解析请求头
        try {
            // 第一次从socket中读取数据,并设置socket的读取数据的超时时间
            // 对于BIO,一个socket连接建立好后,不一定马上就被Tomcat处理了,其中需要线程池的调度,所以这段等待的时间要算在socket读取数据的时间内;而对于NIO而言,没有阻塞
            //第一次连接的时候用,超时时间=真实的时间-socket建立的时间
            setRequestLineReadTimeout();
            // 解析请求行
            if (!getInputBuffer().parseRequestLine(keptAlive)) {               
            }
            if (endpoint.isPaused()) {
               //如果Endpoint被暂停了,则返回503
            } else {
                keptAlive = true;
                // 每次处理一个请求就重新获取一下请求头和cookies的最大限制
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());//默认100
                request.getCookies().setLimit(getMaxCookieCount());//默认200                            }
        } 
        //如果最大的保持连接请求数量==1,表示只允许一次请求
        if (maxKeepAliveRequests == 1) {
            keepAlive = false;//长连接参数直接设为false
        } else if (maxKeepAliveRequests > 0 &&
                socketWrapper.decrementKeepAlive() <= 0) {
            // 如果已经达到了keepAlive的最大限制,也设置为false,则不会继续从socket中获取Http请求了
            keepAlive = false;
        }
        //在适配器开始处理请求
        if (!getErrorState().isError()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); // 设置请求状态为服务状态,表示正在处理请求
                adapter.service(request, response); // 处理请求
            } 
        }
        // 完成请求处理
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);  // 设置请求的状态为处理请求结束
        if (!isAsync() && !comet) {
            // 当前http请求已经处理完了,做一些收尾工作
            endRequest();
        }
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT); // 请求状态为输出结束
       if (!isAsync() && !comet || getErrorState().isError()) {
            if (getErrorState().isIoAllowed()) {
                // 准备处理下一个请求
                getInputBuffer().nextRequest();
                getOutputBuffer().nextRequest();
            }
        }      
        rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
        // 如果处理完当前这个Http请求之后,发现socket里没有下一个请求了,那么就退出当前循环
        // 如果是keepalive,就不会关闭socket, 如果是close就会关闭socket
        // 对于keepalive的情况,因为是一个线程处理一个socket,当退出这个while后,当前线程就会介绍,
        // 当时对于socket来说,它仍然要继续介绍连接,所以又会新开一个线程继续来处理这个socket
        if (breakKeepAliveLoop(socketWrapper)) {
            break;
        }
    }
    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值