首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Tomcat NIO(15)-长连接

Tomcat NIO(15)-长连接

作者头像
TA码字
发布2020-12-14 11:53:37
1.5K0
发布2020-12-14 11:53:37
举报
文章被收录于专栏:TA码字TA码字TA码字

上一篇文章里我们主要介绍了 tomcat nio 中 block poller 线程的阻塞与唤醒,当 tomcat io 线程读取请求实体数据不可读或者写入响应数据不可写的时候,就会注册事件到 block poller 线程中,并阻塞当前线程。block poller 线程负责注册并监测可读可写到原始 socket ,当可读可写的时候唤醒阻塞的线程,让其继续处理读写事件。在这里我们主要介绍 tomcat 中的长连接。

tomcat 数据的读写都在 io 线程中,根据以前文章 io 线程序列图如下:

对于 SocketProcessor 类,决定是否保持长连接的核心代码如下:

// 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 表明握手有问题,调用 poller.cancelledKey() 方法来关闭原始 socket 不使用长连接。

握手正常,取决 ConnectionHandler.process() 返回的 SocketState 。如果状态为 CLOSED 则直接关闭原始 socket ,否则将不关闭保持长连接。

根据以前文章,ConnectionHandler 实例的 process() 方法会间接的调用Http11Processor 的 service() 方法来决定返回的 SocketState ,其核心逻辑如下:

由上可知 SocketState 由 openSocket 全局变量决定,为 false 的时候关闭连接,为 true 的时候不关闭连接。

openSocket 全局变量又会在 Http11Processor 的 service() 方法中通过调用 processSendfile() 方法改变,逻辑如下:

由上述逻辑可知,openSokcet 又由全局变量 keepAlive 决定。keepAlive 的初始值为 true 。但是会有如下 items 改变其值:

  • Http11Processor 中 service() 方法逻辑: int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests(); if (maxKeepAliveRequests == 1) { keepAlive = false; } else if (maxKeepAliveRequests > 0 && socketWrapper.decrementKeepAlive() <= 0) { keepAlive = false; }
  • Http11Processor 中 prepareRequest() 方法逻辑: if (protocolMB.equals(Constants.HTTP_10)) { http11 = false; keepAlive = false; protocolMB.setString(Constants.HTTP_10); } MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION); if (connectionValueMB != null) { ByteChunk connectionValueBC = connectionValueMB.getByteChunk(); if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) { keepAlive = false; } else if (findBytes(connectionValueBC, Constants.KEEPALIVE_BYTES) != -1) { keepAlive = true; } }
  • 对于 http1.1 协议会根据 Connection 请求头的值决定,如果为 close 则表示关闭 socket 不用长连接,如果为 keep-alive 则表示使用长连接,这也符合 http1.1 对长连接定义的标准。

除了以上在 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 ,从而不保持长连接。

根据以上分析对于 tomcat 长连接的总结如下:

  • tomcat 默认就是开启长连接的。
  • 对于 http1.0 协议不使用长连接。
  • 如果请求头中 Connection 的值为 keep-alive 则使用长连接,为 close 则关闭 socket 不使用长连接。
  • tomcat 每个长连接默认支持 100 个请求,如果超过则关闭 socket 停止当前长连接,不过在后续新的连接里还是继续支持长连接。
  • 对于每个长连接 tomcat 会在以前文章介绍的 poller 线程中检查是否有读写超时,默认读写超时时间均为 1 分钟,如果 1 分钟之内没有读写操作,那么关闭 socket 停止当前长连接。
  • 对于上面没有读写操作关闭长连接的情况不仅仅适用于 http 协议,还适用于其它基于 tcp 的协议,因为这个是基于原始 socket 的检测,例如 websocket 协议。 只是对于 websocket 协议来说服务器设置的默认读写超时时间为-1,即不会超时,所以实现了该协议的长连接。当然对于 websocket 协议来说本身也有 ping/pong 定义来实现 keeplive 。

目前先写到这里,下一篇文章里我们继续介绍 tomcat 中的文件上传。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TA码字 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档