zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    升级python
    python内置函数整理
    import 搜索路径
    webpy 解决中文出现UnicodeDecodeError: 'ascii' codec can't decode byte 问题
    未预期的符号 `$'{ '' 附近有语法错误
    python引入模块时import与from ... import的区别
    "Native table 'performance_schema'.'session_variables' has the wrong structure") [SQL: "SHOW VARIABLES LIKE 'sql_mode'"]
    git命令
    Redis简介
    MongoDB基本操作
  • 原文地址:https://www.cnblogs.com/sglx/p/15471378.html
Copyright © 2011-2022 走看看