连接器在Tomcat中是怎么设计的-成都快上网建站

连接器在Tomcat中是怎么设计的

这篇文章将为大家详细讲解有关连接器在Tomcat中是怎么设计的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

创新互联建站网站建设公司是一家服务多年做网站建设策划设计制作的公司,为广大用户提供了成都网站建设、网站建设,成都网站设计,广告投放平台,成都做网站选创新互联建站,贴合企业需求,高性价比,满足客户不同层次的需求一站式服务欢迎致电。

成都创新互联是专业的民勤网站建设公司,民勤接单;提供成都做网站、网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行民勤网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!

专业领域包括成都做网站、网站建设、外贸营销网站建设成都商城网站开发、微信营销、系统平台开发, 与其他网站设计及系统开发公司不同,创新互联的整合解决方案结合了帮做网络品牌建设经验和互联网整合营销的理念,并将策略和执行紧密结合,为客户提供全网互联网整合方案。

创新互联专注于企业营销型网站建设、网站重做改版、坪山网站定制设计、自适应品牌网站建设、HTML5成都商城网站开发、集团公司官网建设、成都外贸网站制作、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为坪山等各大城市提供网站开发制作服务。

创新互联网站建设提供从项目策划、软件开发,软件安全维护、网站优化(SEO)、网站分析、效果评估等整套的建站服务,主营业务为网站设计、成都网站设计,重庆App定制开发以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。创新互联深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

创新互联-专业网站定制、快速模板网站建设、高性价比楚雄州网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式楚雄州网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖楚雄州地区。费用合理售后完善,十余年实体公司更值得信赖。

从连接器(Connector)源码说起

既然是来解析连接器(Connector),那么我们直接从源码入手,后面所有源码我会剔除不重要部分,所以会忽略大部分源码细节,只关注流程。源码如下(高能预警,大量代码):

public class Connector extends LifecycleMBeanBase {  public Connector() {  this("org.apache.coyote.http11.Http11NioProtocol");  }  public Connector(String protocol) {  boolean aprConnector = AprLifecycleListener.isAprAvailable() &&  AprLifecycleListener.getUseAprConnector();  if ("HTTP/1.1".equals(protocol) || protocol == null) {  if (aprConnector) {  protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";  } else {  protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";  }  } else if ("AJP/1.3".equals(protocol)) {  if (aprConnector) {  protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";  } else {  protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";  }  } else {  protocolHandlerClassName = protocol;  }  // Instantiate protocol handler  ProtocolHandler p = null;  try {  Class clazz = Class.forName(protocolHandlerClassName);  p = (ProtocolHandler) clazz.getConstructor().newInstance();  } catch (Exception e) {  log.error(sm.getString(  "coyoteConnector.protocolHandlerInstantiationFailed"), e);  } finally {  this.protocolHandler = p;  }  // Default for Connector depends on this system property  setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));  }

我们来看看Connector的构造方法,其实只做了一件事情,就是根据协议设置对应的ProtocolHandler,根据名称我们知道,这是协议处理类,所以连接器内部的一个重要子模块就是ProtocolHandler。

关于生命周期

我们看到Connector继承了LifecycleMBeanBase,我们来看看Connector的最终继承关系:

连接器在Tomcat中是怎么设计的

我们看到最终实现的是Lifecycle接口,我们看看这个接口是何方神圣。我把其接口的注释拿下来解释下

/**  * Common interface for component life cycle methods. Catalina components  * may implement this interface (as well as the appropriate interface(s) for  * the functionality they support) in order to provide a consistent mechanism  * to start and stop the component.  * start()  * -----------------------------  * | |  * | init() |  * NEW -»-- INITIALIZING |  * | | | | ------------------«-----------------------  * | | |auto | | |  * | | \|/ start() \|/ \|/ auto auto stop() |  * | | INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»--- |  * | | | | |  * | |destroy()| | |  * | --»-----«-- ------------------------«-------------------------------- ^  * | | | |  * | | \|/ auto auto start() |  * | | STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----  * | \|/ ^ | ^  * | | stop() | | |  * | | -------------------------- | |  * | | | | |  * | | | destroy() destroy() | |  * | | FAILED ----»------ DESTROYING ---«----------------- |  * | | ^ | |  * | | destroy() | |auto |  * | --------»----------------- \|/ |  * | DESTROYED |  * | |  * | stop() |  * ----»-----------------------------»------------------------------  *  * Any state can transition to FAILED.  *  * Calling start() while a component is in states STARTING_PREP, STARTING or  * STARTED has no effect.  *  * Calling start() while a component is in state NEW will cause init() to be  * called immediately after the start() method is entered.  *  * Calling stop() while a component is in states STOPPING_PREP, STOPPING or  * STOPPED has no effect.  *  * Calling stop() while a component is in state NEW transitions the component  * to STOPPED. This is typically encountered when a component fails to start and  * does not start all its sub-components. When the component is stopped, it will  * try to stop all sub-components - even those it didn't start.  *  * Attempting any other transition will throw {@link LifecycleException}.  *  * 
  * The {@link LifecycleEvent}s fired during state changes are defined in the  * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the  * attempted transition is not valid.

这段注释翻译就是,这个接口是提供给组件声明周期管理的,并且提供了声明周期流转图。这里我们只需要知道正常流程即可:

New--->Init()---->Start()---->Stop()--->Destory()

从生命周期探索连接器

根据上面的生命周期说明,我们可以知道连接器(Connector)就是按照如此的声明周期管理的,所以我们找到了线索,所以连接器肯定会先初始化然后再启动。我们查看其initInternal()方法可以知道连接器初始化做了什么事情,源码如下:

@Override  protected void initInternal() throws LifecycleException {  super.initInternal();  if (protocolHandler == null) {  throw new LifecycleException(  sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));  }  // Initialize adapter  adapter = new CoyoteAdapter(this);  protocolHandler.setAdapter(adapter);  if (service != null) {  protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());  }  // Make sure parseBodyMethodsSet has a default  if (null == parseBodyMethodsSet) {  setParseBodyMethods(getParseBodyMethods());  }  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",  getProtocolHandlerClassName()));  }  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",  getProtocolHandlerClassName()));  }  if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&  protocolHandler instanceof AbstractHttp11JsseProtocol) {  AbstractHttp11JsseProtocol jsseProtocolHandler =  (AbstractHttp11JsseProtocol) protocolHandler;  if (jsseProtocolHandler.isSSLEnabled() &&  jsseProtocolHandler.getSslImplementationName() == null) {  // OpenSSL is compatible with the JSSE configuration, so use it if APR is available  jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());  }  }  try {  protocolHandler.init();  } catch (Exception e) {  throw new LifecycleException(  sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);  }  } }

根据上面源码,我们发现主要是处理protocolHandler并初始化它,同时我们注意到了protocolHandler  设置了一个适配器,我们看看这个适配器是做啥的,跟踪源码如下:

/**  * The adapter, used to call the connector.  *  * @param adapter The adapter to associate  */  public void setAdapter(Adapter adapter);

这个注释已经说的很直白了,这个适配器就是用来调用连接器的。我们再继续看看protocolHandler的初始化方法

 /**  * Endpoint that provides low-level network I/O - must be matched to the  * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO  * Endpoint etc.).  */ private final AbstractEndpoint endpoint; public void init() throws Exception {  if (getLog().isInfoEnabled()) {  getLog().info(sm.getString("abstractProtocolHandler.init", getName()));  logPortOffset();  }  if (oname == null) {  // Component not pre-registered so register it  oname = createObjectName();  if (oname != null) {  Registry.getRegistry(null, null).registerComponent(this, oname, null);  }  }  if (this.domain != null) {  rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());  Registry.getRegistry(null, null).registerComponent(  getHandler().getGlobal(), rgOname, null);  }  String endpointName = getName();  endpoint.setName(endpointName.substring(1, endpointName.length()-1));  endpoint.setDomain(domain);  endpoint.init();  }

这里出现了一个新的对象,endpoint,根据注释我们可以知道endpoint是用来处理网络IO的,而且必须匹配到指定的子类(比如Nio,就是NioEndPoint处理)。endpoint.init()实际上就是做一些网络的配置,然后就是初始化完毕了。根据我们上面的周期管理,我们知道init()后就是start(),所以我们查看Connector的start()源码:

protected void startInternal() throws LifecycleException { // Validate settings before starting if (getPortWithOffset() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); } setState(LifecycleState.STARTING); try { protocolHandler.start(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); } }

其实就是主要调用 protocolHandler.start()方法,继续跟踪,为了方便表述,我会把接下来的代码统一放在一起说明,代码如下:

//1.类:AbstractProtocol implements ProtocolHandler,  MBeanRegistration  public void start() throws Exception {  // 省略部分代码  endpoint.start();  } //2. 类:AbstractEndPoint  public final void start() throws Exception {  // 省略部分代码  startInternal();  }  /**3.类:NioEndPoint extends AbstractJsseEndpoint  * Start the NIO endpoint, creating acceptor, poller threads.  */  @Override  public void startInternal() throws Exception {  //省略部分代码    // Start poller thread  poller = new Poller();  Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");  pollerThread.setPriority(threadPriority);  pollerThread.setDaemon(true);  pollerThread.start();  startAcceptorThread();  }  }

到这里,其实整个启动代码就完成了,我们看到最后是在NioEndPoint创建了一个Poller,并且启动它,这里需要补充说明下,这里只是以NioEndPoint为示列,其实Tomcat  主要提供了三种实现,分别是AprEndPoint,NioEndPoint,Nio2EndPoint,这里表示了tomcat支持的I/O模型:

  • APR:采用 Apache 可移植运行库实现,它根据不同操作系统,分别用c重写了大部分IO和系统线程操作模块,据说性能要比其他模式要好(未实测)。

  • NIO:非阻塞 I/O

  • NIO.2:异步 I/O

上述代码主要是开启两个线程,一个是Poller,一个是开启Acceptor,既然是线程,核心的代码肯定是run方法,我们来查看源码,代码如下:

//4.类:Acceptor implements Runnable  public void run() {  //省略了部分代码  U socket = null;  socket = endpoint.serverSocketAccept();  // Configure the socket  if (endpoint.isRunning() && !endpoint.isPaused()) {  // setSocketOptions() will hand the socket off to  // an appropriate processor if successful  //核心逻辑  if (!endpoint.setSocketOptions(socket)) {  endpoint.closeSocket(socket);  }  } else {  endpoint.destroySocket(socket);  }    state = AcceptorState.ENDED; } //5.类:NioEndpoint protected boolean setSocketOptions(SocketChannel socket) {  // Process the connection  //省略部分代码  try {  // Disable blocking, polling will be used  socket.configureBlocking(false);  Socket sock = socket.socket();  socketProperties.setProperties(sock);  NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);  channel.setSocketWrapper(socketWrapper);  socketWrapper.setReadTimeout(getConnectionTimeout());  socketWrapper.setWriteTimeout(getConnectionTimeout());  socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());  socketWrapper.setSecure(isSSLEnabled());  //核心逻辑  poller.register(channel, socketWrapper);  return true;    }

这里可以发现Acceptor主要就是接受socket,然后把它注册到poller中,我们继续看看是如何注册的。

/**6.类NioEndpoint  * Registers a newly created socket with the poller.  *  * @param socket The newly created socket  * @param socketWrapper The socket wrapper  */  public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) {  socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.  PollerEvent r = null;  if (eventCache != null) {  r = eventCache.pop();  }  if (r == null) {  r = new PollerEvent(socket, OP_REGISTER);  } else {  r.reset(socket, OP_REGISTER);  }  addEvent(r);  } /** 7.类:PollerEvent implements Runnable  public void run() {  //省略部分代码  socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper());  }

这里发现最终就是采用NIO模型把其注册到通道中。(这里涉及NIO网络编程知识,不了解的同学可以传送这里)。那么注册完毕后,我们看看Poller做了什么事情。

*/   /**8.类:NioEndPoint内部类 Poller implements Runnable  **/   @Override  public void run() {  // Loop until destroy() is called  while (true) {  //省略部分代码  Iterator iterator =  keyCount > 0 ? selector.selectedKeys().iterator() : null;  // Walk through the collection of ready keys and dispatch  // any active event.  while (iterator != null && iterator.hasNext()) {  SelectionKey sk = iterator.next();  NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();  // Attachment may be null if another thread has called  // cancelledKey()  if (socketWrapper == null) {  iterator.remove();  } else {  iterator.remove();  //sock处理  processKey(sk, socketWrapper);  }  }  //省略部分代码  }

这个就是通过selector把之前注册的事件取出来,从而完成了调用。

//9.类: NioEndPoint内部类 Poller implements Runnable  protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {  //省略大部分代码  processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)   }   //10.类:AbstractEndPoint  public boolean processSocket(SocketWrapperBase socketWrapper,  SocketEvent event, boolean dispatch) {  //省略部分代码  Executor executor = getExecutor();  if (dispatch && executor != null) {  executor.execute(sc);  } else {  sc.run();  }    return true;  }  //11.类:SocketProcessorBase implements Runnable  public final void run() {  synchronized (socketWrapper) {  // It is possible that processing may be triggered for read and  // write at the same time. The sync above makes sure that processing  // does not occur in parallel. The test below ensures that if the  // first event to be processed results in the socket being closed,  // the subsequent events are not processed.  if (socketWrapper.isClosed()) {  return;  }  doRun();  }  }   //类:12.NioEndPoint extends AbstractJsseEndpoint  protected void doRun() {  //省略部分代码  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);  }  }  }

Poller调用的run方法或者用Executor线程池去执行run(),最终调用都是各个子EndPoint中的doRun()方法,最终会取一个Handler去处理socketWrapper。继续看源码:

//类:13.AbstractProtocol内部类ConnectionHandler implements AbstractEndpoint.Handler  public SocketState process(SocketWrapperBase wrapper, SocketEvent status) {  //省略部分代码    state = processor.process(wrapper, status);    return SocketState.CLOSED;  }   //类:14.AbstractProcessorLight implements Processor  public SocketState process(SocketWrapperBase socketWrapper, SocketEvent status)  throws IOException {  //省略部分代码    state = service(socketWrapper);    return state;  }

这部分源码表明最终调用的process是通过一个Processor接口的实现类来完成的,这里最终也是会调用到各个子类中,那么这里的处理器其实就是处理应用协议,我们可以查看AbstractProcessorLight的实现类,分别有AjpProcessor、Http11Processor、StreamProcessor,分别代表tomcat支持三种应用层协议,分别是:

  • AJP协议

  • HTTP.1协议

  • HTTP2.0协议

这里我们以常用的HTTP1.1为例,继续看源码:

//类:15. Http11Processor extends AbstractProcessor public SocketState service(SocketWrapperBase socketWrapper)  throws IOException {  //省略大部分代码  getAdapter().service(request, response);  //省略大部分代码   }  //类:16 CoyoteAdapter implements Adapter public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)  throws Exception {  Request request = (Request) req.getNote(ADAPTER_NOTES);  Response response = (Response) res.getNote(ADAPTER_NOTES);  postParseSuccess = postParseRequest(req, request, res, response);  if (postParseSuccess) {  //check valves if we support async  request.setAsyncSupported(  connector.getService().getContainer().getPipeline().isAsyncSupported());  // Calling the container  connector.getService().getContainer().getPipeline().getFirst().invoke(  request, response);  }    }

这里我们发现协议处理器最终会调用适配器(CoyoteAdapter),而适配器最终的工作是转换Request和Response对象为HttpServletRequest和HttpServletResponse,从而可以去调用容器,到这里整个连接器的流程和作用我们就已经分析完了。

小结

那么我们来回忆下整个流程,我画了一张时序图来说明:

连接器在Tomcat中是怎么设计的

这张图包含了两个流程,一个是组件的初始化,一个是调用的流程。连接器(Connector)主要初始化了两个组件,ProtcoHandler和EndPoint,但是我们从代码结构发现,他们两个是父子关系,也就是说ProtcoHandler包含了EndPoint。后面的流程就是各个子组件的调用链关系,总结来说就是Acceptor负责接收请求,然后注册到Poller,Poller负责处理请求,然后调用processor处理器来处理,最后把请求转成符合Servlet规范的request和response去调用容器(Container)。点击免费“领取Java架构资料”

我们流程梳理清楚了,接下来我们来结构化的梳理下:

回到连接器(Connector)是源码,我们发现,上述说的模块只有ProtocolHandler和Adapter两个属于连接器中,也就是说,连接器只包含了这两大子模块,那么后续的EndPoint、Acceptor、Poller、Processor都是ProtocolHandler的子模块。  而Acceptor和Poller两个模块的核心功能都是在EndPoint  中完成的,所以是其子模块,而Processor比较独立,所以它和EndPoint是一个级别的子模块。

我们用图来说明下上述的关系:

连接器在Tomcat中是怎么设计的

根据上图我们可以知道,连接器主要负责处理连接请求,然后通过适配器调用容器。那么具体流程细化可以如下:

  • Acceptor监听网络请求,获取请求。

  • Poller获取到监听的请求提交线程池进行处理。

  • Processor根据具体的应用协议(HTTP/AJP)来生成Tomcat Request对象。

  • Adapter把Request对象转换成Servlet标准的Request对象,调用容器。

关于连接器在Tomcat中是怎么设计的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


网站题目:连接器在Tomcat中是怎么设计的
网站URL:http://kswjz.com/article/igchcp.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流