• Tomcat请求处理源码分析(四)


    一、长连接

      在Http请求头中,Connection: keep-alive 代表长连接。在tomcat io线程读写时,是否保持长连接的方法如下:

    // doRun() method logic in SocketProcessor
    if (handshake == 0) {
        SocketState state = SocketState.OPEN;
        // Process the request from this socket
        if (event == null) {
            state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
        } else {
            state = getHandler().process(socketWrapper, event);
        }
        if (state == SocketState.CLOSED) {
            poller.cancelledKey(key, socketWrapper);
        }
    } else if (handshake == -1 ) {
        poller.cancelledKey(key, socketWrapper);
    }

    handshake 为 -1 表明SSL握手有问题,调用 poller.cancelledKey() 方法来关闭原始 socket 连接。

    如果握手正常,则调用ConnectionHandler的 process() 方法会间接的调用Http11Processor 的 service() 方法返回SocketState,根据SocketState的值决定是否关闭socket连接,如果不为CLOSED则保持长连接。

    核心代码如下:

        public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException {
            
            ···
            
            // Flags
            keepAlive = true;
            openSocket = false;
            readComplete = true;
            boolean keptAlive = false;
            SendfileState sendfileState = SendfileState.DONE;
    
            while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                    sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
    
                ···
                //maxKeepAliveRequests在Http11Processor初始化的时候被赋值100
                if (maxKeepAliveRequests == 1) {
                    keepAlive = false;
                } else if (maxKeepAliveRequests > 0 &&
                        //keepAliveLeft的默认值也是100,每次有新的连接会-1
                        socketWrapper.decrementKeepAlive() <= 0) {
                    keepAlive = false;
                }
                ···
                //把keepAlive赋值给openSocket
                sendfileState = processSendfile(socketWrapper);
            }
    
            rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
    
            if (getErrorState().isError() || (endpoint.isPaused() && !isAsync())) {
                return SocketState.CLOSED;
            } else if (isAsync()) {
                return SocketState.LONG;
            } else if (isUpgrade()) {
                return SocketState.UPGRADING;
            } else {
                if (sendfileState == SendfileState.PENDING) {
                    return SocketState.SENDFILE;
                } else {
                    //true则保持长连接
                    if (openSocket) {
                        if (readComplete) {
                            return SocketState.OPEN;
                        } else {
                            return SocketState.LONG;
                        }
                    } else {
                        //false关闭连接
                        return SocketState.CLOSED;
                    }
                }
            }
        }
        
        private SendfileState processSendfile(SocketWrapperBase<?> socketWrapper) {
            openSocket = keepAlive;
            //Other logic in processSendfile method
            return result;
        }

    由源码可知,默认的长连接数最大为100,即 server 端每个长连接可以支持 100 个请求,超过就会关闭连接。

    除了以上在 tomcat io 线程中决定是否使用长连接之外,poller 线程也可以决定是否使用长连接。在 poller 的循环 run() 方法里会调用 timeout() 方法来决定是否关闭连接,核心逻辑如下:

    if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ || (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
      boolean isTimedOut = false;
      boolean readTimeout = false;
      boolean writeTimeout = false;
      // Check for read timeout
      if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
          long delta = now - socketWrapper.getLastRead();
          long timeout = socketWrapper.getReadTimeout();
          isTimedOut = timeout > 0 && delta > timeout;
          readTimeout = true;
      }
      // Check for write timeout
      if (!isTimedOut && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
          long delta = now - socketWrapper.getLastWrite();
          long timeout = socketWrapper.getWriteTimeout();
          isTimedOut = timeout > 0 && delta > timeout;
          writeTimeout = true;
      }
      if (isTimedOut) {
          key.interestOps(0);
          // Avoid duplicate timeout calls
              socketWrapper.interestOps(0);
              socketWrapper.setError(new SocketTimeoutException());
              if (readTimeout && socketWrapper.readOperation != null) {
                  if (!socketWrapper.readOperation.process()) {
                      cancelledKey(key, socketWrapper);
                  }
              } else if (writeTimeout && socketWrapper.writeOperation != null) {
                  if (!socketWrapper.writeOperation.process()) {
                      cancelledKey(key, socketWrapper);
                  }
              } else if (!processSocket(socketWrapper, SocketEvent.ERROR, true)) {
                  cancelledKey(key, socketWrapper);
              }
          }
      }

    该方法会判断是否有读写超时,读写超时时间由 NioSocketWrapper 实例的 getReadTimeout() 和 getWriteTimeout() 决定,默认都为 1 分钟。
    NioSocketWrapper 实例会有 getLastRead() 和 getLastWrite() 方法记录最近一次读写时间,根据上面超时时间判断是否超时(1分钟内没有读写操作)。
    根据上述如果读写超时,一般情况会走 processSocket(socketWrapper,SocketEvent.ERROR, true) 调用,传递 SocketEvent.ERROR 作为 socket 事件。而对于 error 事件处理也是关闭 socket 。即使上面调用不成功也会调用 cancelledKey() 方法来关闭 socket ,从而不保持长连接。

    二、总结

     

     

    参考链接:https://blog.csdn.net/weixin_46073333/article/details/110359292

  • 相关阅读:
    Spring boot test
    SQL怎么输出前n个记录? n是中间计算得到的,不支持变量传递
    crontab 命令
    Python 素数判断;以及默尼森数
    精通Web Analytics 2.0 (1) 引言
    Python 编程小备忘
    Python 编辑器 tips
    Hadoop 权威指南学习1 (主要框架)
    ml的线性回归应用(python语言)
    python 学习5--matplotlib画图实践
  • 原文地址:https://www.cnblogs.com/sglx/p/15471378.html
Copyright © 2020-2023  润新知