- 核心架构模块,例如Server,Service,engine,host和context及wrapper等
- 网络接口模块connector
- log模块
- session管理模块
- jasper模块
- naming模块
- JMX模块
- 权限控制模块
- ……
![03105900_1jhP.jpg](http://static.oschina.net/uploads/img/201507/03105900_1jhP.jpg)
- ProtocolHandler是接收socket请求,并将其解析成HTTP请求对象,可以配置成nio模式或者传统io模式
- Adapter是处理HTTP请求对象,它就是从StandEngine的valve一直调用到StandWrapper的valve
引用
http://www.mydomain.com/app/index.html
详细情况如图所示。 ![03105900_tgTu.jpg](http://static.oschina.net/uploads/img/201507/03105900_tgTu.jpg)
- Wrapper封装了具体的访问资源,例如 index.html
- Context 封装了各个wrapper资源的集合,例如 app
- Host 封装了各个context资源的集合,例如 www.mydomain.com
引用
org.apache.catalina.valves.RequestDumperValve
valve机制是tomcat非常重要的处理逻辑的机制,会在相关文档里专门描述。 如果这个valve配置在server.xml的节点下,则其只打印出访问这个app(my)的request与response消息。 Xml代码
![收藏代码](http://static.oschina.net/uploads/img/201507/03105901_GMR7.png)
- <Host name="localhost" appBase="webapps"
- unpackWARs="true" autoDeploy="true"
- xmlValidation="false" xmlNamespaceAware="false">
- <Context path="/my" docBase=" /usr/local/tomcat/backup/my" >
- <Valve className="org.apache.catalina.valves.RequestDumperValve"/>
- </Context>
- <Context path="/my2" docBase=" /usr/local/tomcat/backup/my" >
- </Context>
- </Host>
Xml代码
![收藏代码](http://static.oschina.net/uploads/img/201507/03105901_GMR7.png)
- <Host name="localhost" appBase="webapps"
- unpackWARs="true" autoDeploy="true"
- xmlValidation="false" xmlNamespaceAware="false">
- <Valve className="org.apache.catalina.valves.RequestDumperValve"/>
- <Context path="/my" docBase=" /usr/local/tomcat/backup/my" >
- </Context>
- <Context path="/my2" docBase=" /usr/local/tomcat/backup/my" >
- </Context>
- </Host>
Xml代码
![收藏代码](http://static.oschina.net/uploads/img/201507/03105901_GMR7.png)
- <Server port="8005" shutdown="SHUTDOWN">
- <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
- <Listener className="org.apache.catalina.core.JasperListener" />
- <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
- <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
- <GlobalNamingResources>
- <Resource name="UserDatabase" auth="Container"
- type="org.apache.catalina.UserDatabase"
- description="User database that can be updated and saved"
- factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
- pathname="conf/tomcat-users.xml" />
- </GlobalNamingResources>
- <Service name="Catalina">
- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
- maxThreads="150" minSpareThreads="4"/>
- <Connector port="80" protocol="HTTP/1.1"
- connectionTimeout="20000"
- redirectPort="7443" />
- <Connector port="7009" protocol="AJP/1.3" redirectPort="7443" />
- <Engine name="Catalina" defaultHost="localhost">
- <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
- resourceName="UserDatabase"/>
- <Host name="localhost" appBase="webapps"
- unpackWARs="true" autoDeploy="true"
- xmlValidation="false" xmlNamespaceAware="false">
- <Context path="/my" docBase="/usr/local/tomcat/backup/my" >
- </Context>
- </Host>
- </Engine>
- </Service>
- </Server>
-
Tomcat提供了engine,host,context及wrapper四种容器。在总体结构中已经阐述了他们之间的包含关系。这四种容器继承了一个容器基类,因此可以定制化。当然,tomcat也提供了标准实现。
- Engine:org.apache.catalina.core.StandardEngine
- Host: org.apache.catalina.core.StandardHost
- Context:org.apache.catalina.core.StandardContext
- Wrapper:org.apache.catalina.core.StandardWrapper
基类ContainerBase ContainerBase是个abstract基类。其类路径为:
Java代码- org.apache.catalina.core.ContainerBase
Java代码- org.apache.catalina.core.StandardEngine
从图中可以看出,engine有四大组件:
- Cluster: 实现tomcat集群,例如session共享等功能,通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的。其实现方式是基于pipeline+valve模式的,有时间会专门整理一个pipeline+valve模式应用系列;
- Realm:实现用户权限管理模块,例如用户登录,访问控制等,通过通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的;
- Pipeline:这里简单介绍下,之后会有专门文档说明。每个容器对象都有一个pipeline,它不是通过server.xml配置产生的,是必须有的。它就是容器对象实现逻辑操作的骨架,在pipeline上配置不同的valve,当需要调用此容器实现逻辑时,就会按照顺序将此pipeline上的所有valve调用一遍,这里可以参考责任链模式;
- Valve:实现具体业务逻辑单元。可以定制化valve(实现特定接口),然后配置在server.xml里。对其包含的所有host里的应用有效。定制化的valve是可选的,但是每个容器有一个缺省的valve,例如engine的StandardEngineValve,是在StandardEngine里自带的,它主要实现了对其子host对象的StandardHostValve的调用,以此类推。
Xml代码- <Engine name="Catalina" defaultHost="localhost">
- <Valve className="MyValve0"/>
- <Valve className="MyValve1"/>
- <Valve className="MyValve2"/>
- ……
- <Host name="localhost" appBase="webapps">
- </Host>
- </Engine>
Java代码- org.apache.catalina.core.StandardHost
- War部署
- 文件夹部署
- 配置部署等
有时间单独再说吧。照例贴个核心模块概念图。
Context Context是host的子容器,它是wrapper容器的集合。其标准实现类为:
Java代码- org.apache.catalina.core.StandardContext
Pipeline,valve,realm与上面容器一样,只是作用域不一样,不多说了。
- Manager: 它主要是应用的session管理模块。其主要功能是session的创建,session的维护,session的持久化(persistence),以及跨context的session的管理等。Manager模块可以定制化,tomcat也给出了一个标准实现;
Java代码- org.apache.catalina.session.StandardManager
- Resources: 它是每个web app对应的部署结构的封装,比如,有的app是tomcat的webapps目录下的某个子目录或是在context节点配置的其他目录,或者是war文件部署的结构等。它对于每个web app是必须的。
- Loader:它是对每个web app的自有的classloader的封装。具体内容涉及到tomcat的classloader体系,会在一篇文档中单独说明。Tomcat正是有一套完整的classloader体系,才能保证每个web app或是独立运营,或是共享某些对象等等。它对于每个web app是必须的。
- Mapper:它封装了请求资源URI与每个相对应的处理wrapper容器的映射关系。
Xml代码- <servlet>
- <servlet-name>httpserver</servlet-name>
- <servlet-class>com.gearever.servlet.TestServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>httpserver</servlet-name>
- <url-pattern>/*.do</url-pattern>
- </servlet-mapping>
Java代码- request.getRequestDispatcher(url).forward(request, response)
Mapper对象在tomcat中存在于两个地方(注意,不是说只有两个mapper对象存在),其一,是每个context容器对象中,它只记录了此context内部的访问资源与相对应的wrapper子容器的映射;其二,是connector模块中,这是tomcat全局的变量,它记录了一个完整的映射对应关系,即根据访问的完整URL如何定位到哪个host下的哪个context的哪个wrapper容器。 这样,通过上面说的forward方式访问资源会用到第一种mapper,除此之外,其他的任何方式,都是通过第二种方式的mapper定位到wrapper来处理的。也就是说,forward是服务器内部的重定向,不需要经过网络接口,因此只需要通过内存中的处理就能完成。这也就是常说的forward与sendRedirect方式重定向区别的根本所在。 看一下request.getRequestDispatcher(url) 方法的源码。
Java代码- public RequestDispatcher getRequestDispatcher(String path) {
- // Validate the path argument
- if (path == null)
- return (null);
- if (!path.startsWith("/"))
- throw new IllegalArgumentException
- (sm.getString
- ("applicationContext.requestDispatcher.iae", path));
- // Get query string
- String queryString = null;
- int pos = path.indexOf('?');
- if (pos >= 0) {
- queryString = path.substring(pos + 1);
- path = path.substring(0, pos);
- }
- path = normalize(path);
- if (path == null)
- return (null);
- pos = path.length();
- // Use the thread local URI and mapping data
- DispatchData dd = dispatchData.get();
- if (dd == null) {
- dd = new DispatchData();
- dispatchData.set(dd);
- }
- MessageBytes uriMB = dd.uriMB;
- uriMB.recycle();
- // Use the thread local mapping data
- MappingData mappingData = dd.mappingData;
- // Map the URI
- CharChunk uriCC = uriMB.getCharChunk();
- try {
- uriCC.append(context.getPath(), 0, context.getPath().length());
- /*
- * Ignore any trailing path params (separated by ';') for mapping
- * purposes
- */
- int semicolon = path.indexOf(';');
- if (pos >= 0 && semicolon > pos) {
- semicolon = -1;
- }
- uriCC.append(path, 0, semicolon > 0 ? semicolon : pos);
- <span style="color: #ff0000;"> context.getMapper().map(uriMB, mappingData); </span>
- if (mappingData.wrapper == null) {
- return (null);
- }
- /*
- * Append any trailing path params (separated by ';') that were
- * ignored for mapping purposes, so that they're reflected in the
- * RequestDispatcher's requestURI
- */
- if (semicolon > 0) {
- uriCC.append(path, semicolon, pos - semicolon);
- }
- } catch (Exception e) {
- // Should never happen
- log(sm.getString("applicationContext.mapping.error"), e);
- return (null);
- }
- <span style="color: #ff0000;">Wrapper wrapper = (Wrapper) mappingData.wrapper; </span>
- String wrapperPath = mappingData.wrapperPath.toString();
- String pathInfo = mappingData.pathInfo.toString();
- mappingData.recycle();
- // Construct a RequestDispatcher to process this request
- return new ApplicationDispatcher
- (<span style="color: #ff0000;">wrapper</span>, uriCC.toString(), wrapperPath, pathInfo,
- queryString, null);
- }
Java代码- org.apache.catalina.core.StandardWrapper
Pipeline,valve与上面容器一样,只是作用域不一样,不多说了。 主要说说servlet对象与servlet stack对象。这两个对象在wrapper容器中只存在其中之一,也就是说只有其中一个不为空。当以servlet对象存在时,说明此servlet是支持多线程并发访问的,也就是说不存在线程同步的过程,此wrapper容器中只包含一个servlet对象(这是我们常用的模式);当以servlet stack对象存在时,说明servlet是不支持多线程并发访问的,每个servlet对象任一时刻只有一个线程可以调用,这样servlet stack实现的就是个简易的线程池,此wrapper容器中只包含一组servlet对象,它的基本原型是worker thread模式实现的。 那么,怎么来决定是以servlet对象方式存储还是servlet stack方式存储呢?其实,只要在开发servlet类时,实现一个SingleThreadModel接口即可。 如果需要线程同步的servlet类,例如:
Java代码- public class LoginServlet extends HttpServlet implements javax.servlet.SingleThreadModel{ …… }
- 处理静态资源的一个wrapper:例如html,jpg等静态资源的wrapper,它包含了一个tomcat的实现处理静态资源的缺省servlet:
Java代码- org.apache.catalina.servlets.DefaultServlet
- 处理jsp的一个wrapper:例如访问的所有jsp文件,它包含了一个tomcat的实现处理jsp的缺省servlet:
Java代码- org.apache.jasper.servlet.JspServlet
- 处理servlet的若干wrapper:它包含了自定义的servlet对象,就是在web.xml中配置的servlet。
Xml代码- <servlet>
- <servlet-name>default</servlet-name>
- <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
- <init-param>
- <param-name>debug</param-name>
- <param-value>0</param-value>
- </init-param>
- <init-param>
- <param-name>listings</param-name>
- <param-value>false</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet>
- <servlet-name>jsp</servlet-name>
- <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
- <init-param>
- <param-name>fork</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>xpoweredBy</param-name>
- <param-value>false</param-value>
- </init-param>
- <load-on-startup>3</load-on-startup>
- </servlet>
这些valve就是在tomcat的server.xml中配置,只要满足一定条件,继承ValveBase基类
引用org.apache.catalina.valves.ValveBase就可以在不同的容器中配置,然后在消息流中被逐一调用。每个容器的valve的作用域不一样,在总体结构中已有说明。这里红色标记的是配置的自定义的valve,这样可以扩展成多个其他应用,例如cluster应用等。 Tomcat实现 Tomcat提供了Pipeline的标准实现:引用org.apache.catalina.core.StandardPipeline四大容器类StandardEngine,StandardHost,StandardContext及StandardWrapper都有各自缺省的标准valve实现。它们分别是- Engine:org.apache.catalina.core.StandardEngineValve
- Host: org.apache.catalina.core.StandardHostValve
- Context:org.apache.catalina.core.StandardContextValve
- Wrapper:org.apache.catalina.core.StandardWrapperValve
Java代码- public class StandardHost extends ContainerBase implements Host {
- protected Pipeline pipeline = new StandardPipeline(this);
- public StandardHost() {
- super();
- pipeline.setBasic(new StandardHostValve());
- }
- }
Xml代码- <Engine name="Catalina" defaultHost="localhost">
- <Valve className="MyValve0"/>
- <Valve className="MyValve1"/>
- <Valve className="MyValve2"/>
- ……
- <Host name="localhost" appBase="webapps">
- </Host>
- </Engine>
Java代码- public class StandardPipeline implements Pipeline, Contained, Lifecycle{
- protected Valve first = null;
- public void addValve(Valve valve) {
- // Validate that we can add this Valve
- if (valve instanceof Contained)
- ((Contained) valve).setContainer(this.container);
- // Start the new component if necessary
- if (started) {
- if (valve instanceof Lifecycle) {
- try {
- ((Lifecycle) valve).start();
- } catch (LifecycleException e) {
- log.error("StandardPipeline.addValve: start: ", e);
- }
- }
- // Register the newly added valve
- registerValve(valve);
- }
- // 将配置的valve添加到链表中,并且每个容器的标准valve在链表的尾端
- if (first == null) {
- first = valve;
- valve.setNext(basic);
- } else {
- Valve current = first;
- while (current != null) {
- if (current.getNext() == basic) {
- current.setNext(valve);
- valve.setNext(basic);
- break;
- }
- current = current.getNext();
- }
- }
- }
- }
图中显示的是各个容器默认的valve之间的实际调用情况。从StandardEngineValve开始,一直到StandardWrapperValve,完成整个消息处理过程。注意每一个上层的valve都是在调用下一层的valve返回后再返回的,这样每个上层valve不仅具有request对象,同时还能拿到response对象,想象一下,这样是不是可以批量的做很多东西?现在通过源码来加深下理解。侯捷说过,源码面前,了无秘密。通过这些代码,可以看到在tomcat中我们经常碰到的一些现象或配置是怎么实现的。 StandardEngineValve 看一下StandardEngineValve的调用逻辑;
Java代码- public final void invoke(Request request, Response response)
- throws IOException, ServletException {
- // 定位host
- Host host = request.getHost();
- if (host == null) {
- ......
- return;
- }
- // 调用host的第一个valve
- host.getPipeline().getFirst().invoke(request, response);
- }
Java代码- public final void invoke(Request request, Response response)
- throws IOException, ServletException {
- // 定位context
- Context context = request.getContext();
- if (context == null) {
- ......
- return;
- }
- ......
- // 调用context的第一个valve
- context.getPipeline().getFirst().invoke(request, response);
- // 更新session
- if (Globals.STRICT_SERVLET_COMPLIANCE) {
- request.getSession(false);
- }
- // Error page processing
- response.setSuspended(false);
- //如果有抛异常或某个HTTP错误,导向响应的配置页面
- Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR);
- if (t != null) {
- throwable(request, response, t);
- } else {
- status(request, response);
- }
- // Restore the context classloader
- Thread.currentThread().setContextClassLoader
- (StandardHostValve.class.getClassLoader());
- }
Xml代码- <error-page>
- <error-code>404</error-code>
- <location>/error.jsp</location>
- </error-page>
Java代码- public final void invoke(Request request, Response response)
- throws IOException, ServletException {
- ......
- // 定位wrapper
- Wrapper wrapper = request.getWrapper();
- if (wrapper == null) {
- notFound(response);
- return;
- } else if (wrapper.isUnavailable()) {
- ......
- }
- // Normal request processing
- //web.xml中配置web-app/listener/listener-class
- Object instances[] = context.getApplicationEventListeners();
- ServletRequestEvent event = null;
- //响应request初始化事件,具体的响应listener是可配置的
- ......
- //调用wrapper的第一个valve
- wrapper.getPipeline().getFirst().invoke(request, response);
- //响应request撤销事件,具体的响应listener是可配置的
- ......
- }
Java代码- public final void invoke(Request request, Response response)
- throws IOException, ServletException {
- ......
- requestCount++;
- //定位wrapper
- StandardWrapper wrapper = (StandardWrapper) getContainer();
- Servlet servlet = null;
- Context context = (Context) wrapper.getParent();
- ......
- // Allocate a servlet instance to process this request
- try {
- if (!unavailable) {
- //加载servlet
- servlet = wrapper.allocate();
- }
- } catch (UnavailableException e) {
- ......
- }
- ......
- // 根据配置建立一个filter-servlet的处理链表,servlet在链表的尾端
- ApplicationFilterFactory factory =
- ApplicationFilterFactory.getInstance();
- ApplicationFilterChain filterChain =
- factory.createFilterChain(request, wrapper, servlet);
- // Reset comet flag value after creating the filter chain
- request.setComet(false);
- // Call the filter chain for this request
- // NOTE: This also calls the servlet's service() method
- try {
- String jspFile = wrapper.getJspFile();
- if (jspFile != null)
- request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
- else
- request.removeAttribute(Globals.JSP_FILE_ATTR);
- if ((servlet != null) && (filterChain != null)) {
- // Swallow output if needed
- if (context.getSwallowOutput()) {
- try {
- SystemLogHandler.startCapture();
- if (comet) {
- filterChain.doFilterEvent(request.getEvent());
- request.setComet(true);
- } else {
- //调用filter-servlet链表
- filterChain.doFilter(request.getRequest(),
- response.getResponse());
- }
- } finally {
- String log = SystemLogHandler.stopCapture();
- if (log != null && log.length() > 0) {
- context.getLogger().info(log);
- }
- }
- } else {
- if (comet) {
- request.setComet(true);
- filterChain.doFilterEvent(request.getEvent());
- } else {
- //调用filter-servlet链表
- filterChain.doFilter
- (request.getRequest(), response.getResponse());
- }
- }
- }
- request.removeAttribute(Globals.JSP_FILE_ATTR);
- } catch (ClientAbortException e) {
- request.removeAttribute(Globals.JSP_FILE_ATTR);
- throwable = e;
- exception(request, response, e);
- }
- ......
- }
引用org.apache.catalina.valves.RequestDumperValveRequestDumperValve是打印出request及response信息的valve。其实现方法为:Java代码- public void invoke(Request request, Response response)
- throws IOException, ServletException {
- Log log = container.getLogger();
- // Log pre-service information
- log.info("REQUEST URI =" + request.getRequestURI());
- ......
- log.info(" queryString=" + request.getQueryString());
- ......
- log.info("-------------------------------------------------------");
- // 调用下一个valve
- getNext().invoke(request, response);
- // Log post-service information
- log.info("-------------------------------------------------------");
- ......
- log.info(" contentType=" + response.getContentType());
- Cookie rcookies[] = response.getCookies();
- for (int i = 0; i < rcookies.length; i++) {
- log.info(" cookie=" + rcookies[i].getName() + "=" +
- rcookies[i].getValue() + "; domain=" +
- rcookies[i].getDomain() + "; path=" + rcookies[i].getPath());
- }
- String rhnames[] = response.getHeaderNames();
- for (int i = 0; i < rhnames.length; i++) {
- String rhvalues[] = response.getHeaderValues(rhnames[i]);
- for (int j = 0; j < rhvalues.length; j++)
- log.info(" header=" + rhnames[i] + "=" + rhvalues[j]);
- }
- log.info(" message=" + response.getMessage());
- log.info("========================================================");
- }
Xml代码- <Host name="localhost" appBase="webapps"
- unpackWARs="true" autoDeploy="true"
- xmlValidation="false" xmlNamespaceAware="false">
- <Valve className="org.apache.catalina.valves.RequestDumperValve"/>
- <Context path="/my" docBase=" /usr/local/tomcat/backup/my" >
- </Context>
- <Context path="/my2" docBase=" /usr/local/tomcat/backup/my" >
- </Context>
- </Host>
引用org.apache.catalina.session.StandardManagerSession对象也可以定制化实现,其主要实现标准servlet的session接口:引用javax.servlet.http.HttpSessionTomcat也提供了标准的session实现:引用org.apache.catalina.session.StandardSession本文主要就是结合消息流程介绍这两个类的实现,及session机制。 Session方面牵涉的东西还是蛮多的,例如HA,session复制是其中重要部分等,不过本篇主要从功能方面介绍session管理,有时间再说说扩展。 Session管理主要涉及到这几个方面:- 创建session
- 注销session
- 持久化及启动加载session
- browser发送Http request;
- tomcat内核Http11Processor会从HTTP request中解析出“jsessionid”(具体的解析过程为先从request的URL中解析,这是为了有的浏览器把cookie功能禁止后,将URL重写考虑的,如果解析不出来,再从cookie中解析相应的jsessionid),解析完后封装成一个request对象(当然还有其他的http header);
- servlet中获取session,其过程是根据刚才解析得到的jsessionid(如果有的话),从session池(session maps)中获取相应的session对象;这个地方有个逻辑,就是如果jsessionid为空的话(或者没有其对应的session对象,或者有session对象,但此对象已经过期超时),可以选择创建一个session,或者不创建;
- 如果创建新session,则将session放入session池中,同时将与其相对应的jsessionid写入cookie通过Http response header的方式发送给browser,然后重复第一步。
Java代码- protected Session doGetSession(boolean create) {
- ……
- // 先获取所在context的manager对象
- Manager manager = null;
- if (context != null)
- manager = context.getManager();
- if (manager == null)
- return (null); // Sessions are not supported
- //这个requestedSessionId就是从Http request中解析出来的
- if (requestedSessionId != null) {
- try {
- //manager管理的session池中找相应的session对象
- session = manager.findSession(requestedSessionId);
- } catch (IOException e) {
- session = null;
- }
- //判断session是否为空及是否过期超时
- if ((session != null) && !session.isValid())
- session = null;
- if (session != null) {
- //session对象有效,记录此次访问时间
- session.access();
- return (session);
- }
- }
- // 如果参数是false,则不创建新session对象了,直接退出了
- if (!create)
- return (null);
- if ((context != null) && (response != null) &&
- context.getCookies() &&
- response.getResponse().isCommitted()) {
- throw new IllegalStateException
- (sm.getString("coyoteRequest.sessionCreateCommitted"));
- }
- // 开始创建新session对象
- if (connector.getEmptySessionPath()
- && isRequestedSessionIdFromCookie()) {
- session = manager.createSession(getRequestedSessionId());
- } else {
- session = manager.createSession(null);
- }
- // 将新session的jsessionid写入cookie,传给browser
- if ((session != null) && (getContext() != null)
- && getContext().getCookies()) {
- Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
- session.getIdInternal());
- configureSessionCookie(cookie);
- response.addCookieInternal(cookie);
- }
- //记录session最新访问时间
- if (session != null) {
- session.access();
- return (session);
- } else {
- return (null);
- }
- }
引用request.getSession(); 或者request.getSession(true);不创建session引用request.getSession(false);接下来,看一下StandardManager的createSession方法,了解一下session的创建过程;Java代码- public Session createSession(String sessionId) {
- 是个session数量控制逻辑,超过上限则抛异常退出
- if ((maxActiveSessions >= 0) &&
- (sessions.size() >= maxActiveSessions)) {
- rejectedSessions++;
- throw new IllegalStateException
- (sm.getString("standardManager.createSession.ise"));
- }
- return (super.createSession(sessionId));
- }
Java代码- public Session createSession(String sessionId) {
- // 创建一个新的StandardSession对象
- Session session = createEmptySession();
- // Initialize the properties of the new session and return it
- session.setNew(true);
- session.setValid(true);
- session.setCreationTime(System.currentTimeMillis());
- session.setMaxInactiveInterval(this.maxInactiveInterval);
- if (sessionId == null) {
- //设置jsessionid
- sessionId = generateSessionId();
- }
- session.setId(sessionId);
- sessionCounter++;
- return (session);
- }
Java代码- protected synchronized String generateSessionId() {
- byte random[] = new byte[16];
- String jvmRoute = getJvmRoute();
- String result = null;
- // Render the result as a String of hexadecimal digits
- StringBuffer buffer = new StringBuffer();
- do {
- int resultLenBytes = 0;
- if (result != null) {
- buffer = new StringBuffer();
- duplicates++;
- }
- while (resultLenBytes < this.sessionIdLength) {
- getRandomBytes(random);
- random = getDigest().digest(random);
- for (int j = 0;
- j < random.length && resultLenBytes < this.sessionIdLength;
- j++) {
- byte b1 = (byte) ((random[j] & 0xf0) >> 4);
- byte b2 = (byte) (random[j] & 0x0f);
- if (b1 < 10)
- buffer.append((char) ('0' + b1));
- else
- buffer.append((char) ('A' + (b1 - 10)));
- if (b2 < 10)
- buffer.append((char) ('0' + b2));
- else
- buffer.append((char) ('A' + (b2 - 10)));
- resultLenBytes++;
- }
- }
- if (jvmRoute != null) {
- buffer.append('.').append(jvmRoute);
- }
- result = buffer.toString();
- //注意这个do…while结构
- } while (sessions.containsKey(result));
- return (result);
- }
如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。 好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。 注销session
- 主动注销
- 超时注销
引用session.invalidate();看一下tomcat提供的标准session实现(StandardSession)Java代码- public void invalidate() {
- if (!isValidInternal())
- throw new IllegalStateException
- (sm.getString("standardSession.invalidate.ise"));
- // 明显的注销方法
- expire();
- }
Java代码- public boolean isValid() {
- ……
- //这就是判断距离上次访问是否超时的过程
- if (maxInactiveInterval >= 0) {
- long timeNow = System.currentTimeMillis();
- int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
- if (timeIdle >= maxInactiveInterval) {
- expire(true);
- }
- }
- return (this.isValid);
- }
Java代码- public void expire(boolean notify) {
- synchronized (this) {
- ......
- //设立标志位
- setValid(false);
- //计算一些统计值,例如此manager下所有session平均存活时间等
- long timeNow = System.currentTimeMillis();
- int timeAlive = (int) ((timeNow - creationTime)/1000);
- synchronized (manager) {
- if (timeAlive > manager.getSessionMaxAliveTime()) {
- manager.setSessionMaxAliveTime(timeAlive);
- }
- int numExpired = manager.getExpiredSessions();
- numExpired++;
- manager.setExpiredSessions(numExpired);
- int average = manager.getSessionAverageAliveTime();
- average = ((average * (numExpired-1)) + timeAlive)/numExpired;
- manager.setSessionAverageAliveTime(average);
- }
- // 将此session从manager对象的session池中删除
- manager.remove(this);
- ......
- }
- }
Xml代码- <session-config>
- <session-timeout>30</session-timeout>
- </session-config>
总结 由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。JNDI(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API。命名服务将名称和对象联系起来,使得我们可以用名称访问对象。目录服务是一种命名服务,在这种服务里,对象不但有名称,还有属性。 ---百度百科 通俗点说,JNDI封装了一个简单name到实体对象的mapping,通过字符串可以方便的得到想要的对象资源。通常这种对象资源有很多种,例如数据库JDBC,JMS,EJB等。平时用的最多的就是数据库了。在tomcat中,这些资源都是以java:comp/env开头的字符串来绑定的。以数据库连接为例,我们在app中的调用场景是;
Java代码- //获得对数据源的引用:
- Context ctx = new InitalContext();
- DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myDB");
- //获得数据库连接对象:
- Connection conn = ds.getConnection();
- //返回数据库连接到连接池:
- conn.close();
Xml代码- <Server port="8005">
- <GlobalNamingResources>
- <Resource
- name="jdbc/mysql"
- type="javax.sql.DataSource"
- username="root"
- password="root"
- driverClassName="com.mysql.jdbc.Driver"
- maxIdle="200"
- maxWait="5000"
- url="……"
- maxActive="100"/>
- </GlobalNamingResources>
- ……
- </Server>
Xml代码- <Server port="8005">
- <Service>
- <Engine>
- <Host>
- <Context>
- < ResourceLink globalname=" jdbc/mysql " name="myDB" type="…"/>
- </Context>
- </Host>
- </Engine>
- </Service>
- ……
- </Server>
Xml代码- <Context>
- < ResourceLink globalname=" jdbc/mysql " name="myDB" type="…"/>
- </Context>
Xml代码- <web-app>
- <resource-ref>
- <description/>
- <res-auth/>
- <res-ref-name>myDB</res-ref-name>
- <res-sharing-scope/>
- <res-type/>
- </resource-ref>
- </web-app>
Java代码- //获得对数据源的引用:
- Context ctx = new InitalContext();
- DataSource ds = (DataSource) ctx.lookup("java:comp/env/myDB");
- //获得数据库连接对象:
- Connection conn = ds.getConnection();
- //返回数据库连接到连接池:
- conn.close();
Tomcat这种资源不限于数据库连接,还有很多例如EJB,Web Service等,在配置中它们分别对应不同的节点。例如上面的数据库连接,在server.xml中对应<Resource>,在web.xml中对应的是<resource-ref>,EJB连接在server.xml中对应<Ejb>,在web.xml中对应的是<ejb-ref>等,因为有些资源在现在的开发中应用的不是很多,就不一一例举了,总结一下它们所有的对应关系;
第二种方案 没有上述方案那么麻烦,主要是为了需要引用一个自己独有的资源对象的app而言。 <tomcat>/conf/server.xml
Xml代码- <Server port="8005">
- <Service>
- <Engine>
- <Host>
- <Context>
- <Resource
- name="jdbc/mysql"
- type="javax.sql.DataSource"
- username="root"
- password="root"
- driverClassName="com.mysql.jdbc.Driver"
- maxIdle="200"
- maxWait="5000"
- url="……"
- maxActive="100"/>
- </Context>
- </Host>
- </Engine>
- </Service>
- ……
- </Server>
Xml代码- <Context>
- <Resource
- name="jdbc/mysql"
- type="javax.sql.DataSource"
- username="root"
- password="root"
- driverClassName="com.mysql.jdbc.Driver"
- maxIdle="200"
- maxWait="5000"
- url="……"
- maxActive="100"/>
- </Context>
Java代码- //获得对数据源的引用:
- Context ctx = new InitalContext();
- DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mysql");
- //获得数据库连接对象:
- Connection conn = ds.getConnection();
- //返回数据库连接到连接池:
- conn.close();
- Tomcat源码
- JNDI源码(javax.naming.*),参考OpenJDK项目
JNDI体系分为三个部分;
- 在tomcat架构分析 (容器类)中介绍了StandardContext类,它是每个app的一个逻辑封装。当tomcat初始化时,将根据配置文件,对StandardContext中的NamingResources对象进行赋值,同时,将实例化一个NamingContextListener对象作为这个context作用域内的事件监听器,它会响应一些例如系统启动,系统关闭等事件,作出相应的操作;
- 初始化完成后,tomcat启动,完成启动逻辑,抛出一个系统启动event,由那个NamingContextListener捕获,进行处理,将初始化时的NamingResources对象中的数据,绑定到相应的JNDI对象树(namingContext)上,即java:comp/env分支,然后将这个根namingContext与这个app的classloader进行绑定,这样每个app只有在自己的JNDI对象树上调用,互不影响;
- 每个app中的类都由自己app的classloader加载,如果需要用到JNDI绑定对象,也是从自己classloader对应的JNDI对象树上获取资源对象
Xml代码- <Server port="8005">
- <Service>
- <Engine>
- <Host>
- <Context>
- <Resource
- name="jdbc/mysql"
- type="javax.sql.DataSource"
- username="root"
- password="root"
- driverClassName="com.mysql.jdbc.Driver"
- maxIdle="200"
- maxWait="5000"
- url="……"
- maxActive="100"/>
- </Context>
- </Host>
- </Engine>
- </Service>
- ……
- </Server>
Java代码- org.apache.catalina.deploy.ContextResource
Java代码- public void addResource(ContextResource resource) {
- //确保每一个资源对象的name都是唯一的
- //不仅是Resource对象之间,包括Service等所有的资源对象
- if (entries.containsKey(resource.getName())) {
- return;
- } else {
- entries.put(resource.getName(), resource.getType());
- }
- //建立一个name和资源对象的mapping
- synchronized (resources) {
- resource.setNamingResources(this);
- resources.put(resource.getName(), resource);
- }
- support.firePropertyChange("resource", null, resource);
- }
Java代码- public void lifecycleEvent(LifecycleEvent event) {
- container = event.getLifecycle();
- if (container instanceof Context) {
- //这个namingResources对象就是StandardContext的namingResources对象
- namingResources = ((Context) container).getNamingResources();
- logger = log;
- } else if (container instanceof Server) {
- namingResources = ((Server) container).getGlobalNamingResources();
- } else {
- return;
- }
- //响应start event
- if (event.getType() == Lifecycle.START_EVENT) {
- if (initialized)
- return;
- Hashtable contextEnv = new Hashtable();
- try {
- //生成这个StandardContext域的JNDI对象树根NamingContext对象
- namingContext = new NamingContext(contextEnv, getName());
- } catch (NamingException e) {
- // Never happens
- }
- ContextAccessController.setSecurityToken(getName(), container);
- //将此StandardContext对象与JNDI对象树根NamingContext对象绑定
- ContextBindings.bindContext(container, namingContext, container);
- if( log.isDebugEnabled() ) {
- log.debug("Bound " + container );
- }
- // Setting the context in read/write mode
- ContextAccessController.setWritable(getName(), container);
- try {
- //将初始化时的资源对象绑定JNDI对象树
- createNamingContext();
- } catch (NamingException e) {
- logger.error
- (sm.getString("naming.namingContextCreationFailed", e));
- }
- // 针对Context下配置Resource对象而言
- if (container instanceof Context) {
- // Setting the context in read only mode
- ContextAccessController.setReadOnly(getName());
- try {
- //通过此StandardContext对象获取到JNDI对象树根NamingContext对象
- //同时将此app的classloader与此JNDI对象树根NamingContext对象绑定
- ContextBindings.bindClassLoader
- (container, container,
- ((Container) container).getLoader().getClassLoader());
- } catch (NamingException e) {
- logger.error(sm.getString("naming.bindFailed", e));
- }
- }
- // 针对global资源而言,这里不用关注
- if (container instanceof Server) {
- namingResources.addPropertyChangeListener(this);
- org.apache.naming.factory.ResourceLinkFactory.setGlobalContext
- (namingContext);
- try {
- ContextBindings.bindClassLoader
- (container, container,
- this.getClass().getClassLoader());
- } catch (NamingException e) {
- logger.error(sm.getString("naming.bindFailed", e));
- }
- if (container instanceof StandardServer) {
- ((StandardServer) container).setGlobalNamingContext
- (namingContext);
- }
- }
- initialized = true;
- }
- //响应stop event
- else if (event.getType() == Lifecycle.STOP_EVENT) {
- ......
- }
- }
Java代码- public static void bindContext(Object name, Context context,
- Object token) {
- if (ContextAccessController.checkSecurityToken(name, token))
- //先是将StandardContext对象与JNDI对象树根NamingContext对象绑定
- //注意,这里第一个参数name是StandardContext对象
- contextNameBindings.put(name, context);
- }
Java代码- public static void bindClassLoader(Object name, Object token,
- ClassLoader classLoader)
- throws NamingException {
- if (ContextAccessController.checkSecurityToken(name, token)) {
- //根据上面的StandardContext对象获取刚才绑定的NamingContext对象
- Context context = (Context) contextNameBindings.get(name);
- if (context == null)
- throw new NamingException
- (sm.getString("contextBindings.unknownContext", name));
- //将classloader与NamingContext对象绑定
- clBindings.put(classLoader, context);
- clNameBindings.put(classLoader, name);
- }
- }
Java代码- private void createNamingContext()
- throws NamingException {
- // Creating the comp subcontext
- if (container instanceof Server) {
- compCtx = namingContext;
- envCtx = namingContext;
- } else {
- //对于StandardContext而言,在JNDI对象树的根namingContext对象上
- //建立comp树枝,以及在comp树枝上建立env树枝namingContext对象
- compCtx = namingContext.createSubcontext("comp");
- envCtx = compCtx.createSubcontext("env");
- }
- ......
- // 从初始化的NamingResources对象中获取Resource对象加载到JNDI对象树上
- ContextResource[] resources = namingResources.findResources();
- for (i = 0; i < resources.length; i++) {
- addResource(resources[i]);
- }
- ......
- }
Java代码- public void addResource(ContextResource resource) {
- // Create a reference to the resource.
- Reference ref = new ResourceRef
- (resource.getType(), resource.getDescription(),
- resource.getScope(), resource.getAuth());
- // 遍历Resource对象的各个属性,这些属性存在一个HashMap中
- Iterator params = resource.listProperties();
- while (params.hasNext()) {
- String paramName = (String) params.next();
- String paramValue = (String) resource.getProperty(paramName);
- //封装成StringRefAddr,这些都是JNDI的标准API
- StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
- ref.add(refAddr);
- }
- try {
- if (logger.isDebugEnabled()) {
- logger.debug(" Adding resource ref "
- + resource.getName() + " " + ref);
- }
- //在上面创建的comp/env树枝节点上,根据Resource配置的name继续创建新的节点
- //例如配置的name=”jdbc/mysql”,则在comp/env树枝节点下再创建一个jdbc树枝节点
- createSubcontexts(envCtx, resource.getName());
- //绑定叶子节点,它不是namingContext对象,而是最后的Resource对象
- envCtx.bind(resource.getName(), ref);
- } catch (NamingException e) {
- logger.error(sm.getString("naming.bindFailed", e));
- }
- //这就是上面说的对于配置type="javax.sql.DataSource"时的特殊逻辑
- //将数据库连接池类型的资源对象注册到tomcat全局的JMX中,方便管理及调试
- if ("javax.sql.DataSource".equals(ref.getClassName())) {
- try {
- ObjectName on = createObjectName(resource);
- Object actualResource = envCtx.lookup(resource.getName());
- Registry.getRegistry(null, null).registerComponent(actualResource, on, null);
- objectNames.put(resource.getName(), on);
- } catch (Exception e) {
- logger.warn(sm.getString("naming.jmxRegistrationFailed", e));
- }
- }
- }
到目前为止,完成了JNDI对象树的绑定,可以看到,每个app对应的StandardContext对应一个JNDI对象树,并且每个app的各个classloader与此JNDI对象树分别绑定,那么各个app之间的JNDI可以不互相干扰,各自配置及调用。 需要注意的是,NamingContext对象就是JNDI对象树上的树枝节点,类似文件系统中的目录,各个Resource对象则是JNDI对象树上的叶子节点,类似文件系统的具体文件,通过NamingContext对象将整个JNDI对象树组织起来,每个Resource对象才是真正存储数据的地方。 本篇就描述tomcat内部是如何构造JNDI对象树的,如何通过JNDI获取对象,涉及到JNDI API内部运作了,将在另一篇中继续。connector组件是service容器中的一部分。它主要是接收,解析http请求,然后调用本service下的相关servlet。由于tomcat从架构上采用的是一个分层结构,因此根据解析过的http请求,定位到相应的servlet也是一个相对比较复杂的过程。
整个connector实现了从接收socket到调用servlet的全部过程。先来看一下connector的功能逻辑;
- 接收socket
- 从socket获取数据包,并解析成HttpServletRequest对象
- 从engine容器开始走调用流程,经过各层valve,最后调用servlet完成业务逻辑
- 返回response,关闭socket
Xml代码- <Connector port="80" URIEncoding="UTF-8" protocol="HTTP/1.1"
- connectionTimeout="20000"
- redirectPort="7443" />
- HTTP/1.1
- org.apache.coyote.http11.Http11Protocol –BIO实现
- org.apache.coyote.http11.Http11NioProtocol –NIO实现
- 定制的接口
可以看见connector中三大块
- Http11Protocol
- Mapper
- CoyoteAdapter
Xml代码- <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
- maxThreads="150" minSpareThreads="4"/>
所以当connector组件启动的时候,会从JMX中查询出各层容器,然后再创建这个Mapper对象中的快照。 CoyoteAdapter 全路径org.apache.catalina.connector.CoyoteAdapter,此对象负责将http request解析成HttpServletRequest对象,之后绑定相应的容器,然后从engine开始逐层调用valve直至该servlet。在session管理中,已经说明,根据request中的jsessionid绑定服务器端的相应session。这个jsessionid按照优先级或是从request url中获取,或是从cookie中获取,然后再session池中找到相应匹配的session对象,然后将其封装到HttpServletRequest对象。所有这些都是在CoyoteAdapter中完成的。看一下将request解析为HttpServletRequest对象后,开始调用servlet的代码;
Java代码- connector.getContainer().getPipeline().getFirst().invoke(request, response);
Xml代码- <Connector port="80" URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioProtocol"
- connectionTimeout="20000"
- redirectPort="7443" />
还是可以看见connector中三大块
- Http11NioProtocol
- Mapper
- CoyoteAdapter
图中Acceptor及Worker分别是以线程池形式存在,Poller是一个单线程。注意,与BIO的实现一样,缺省状态下,在server.xml中没有配置<Executor>,则以Worker线程池运行,如果配置了<Executor>,则以基于java concurrent 系列的java.util.concurrent.ThreadPoolExecutor线程池运行。 Acceptor 接收socket线程,这里虽然是基于NIO的connector,但是在接收socket方面还是传统的serverSocket.accept()方式,获得SocketChannel对象,然后封装在一个tomcat的实现类org.apache.tomcat.util.net.NioChannel对象中。然后将NioChannel对象封装在一个PollerEvent对象中,并将PollerEvent对象压入events queue里。这里是个典型的生产者-消费者模式,Acceptor与Poller线程之间通过queue通信,Acceptor是events queue的生产者,Poller是events queue的消费者。 Poller Poller线程中维护了一个Selector对象,NIO就是基于Selector来完成逻辑的。在connector中并不止一个Selector,在socket的读写数据时,为了控制timeout也有一个Selector,在后面的BlockSelector中介绍。可以先把Poller线程中维护的这个Selector标为主Selector。 Poller是NIO实现的主要线程。首先作为events queue的消费者,从queue中取出PollerEvent对象,然后将此对象中的channel以OP_READ事件注册到主Selector中,然后主Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。整个过程是典型的NIO实现。 Worker Worker线程拿到Poller传过来的socket后,将socket封装在SocketProcessor对象中。然后从Http11ConnectionHandler中取出Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑,跟BIO实现一样。在Worker线程中,会完成从socket中读取http request,解析成HttpServletRequest对象,分派到相应的servlet并完成逻辑,然后将response通过socket发回client。在从socket中读数据和往socket中写数据的过程,并没有像典型的非阻塞的NIO的那样,注册OP_READ或OP_WRITE事件到主Selector,而是直接通过socket完成读写,这时是阻塞完成的,但是在timeout控制上,使用了NIO的Selector机制,但是这个Selector并不是Poller线程维护的主Selector,而是BlockPoller线程中维护的Selector,称之为辅Selector。 NioSelectorPool NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。
Java代码- public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {
- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
- if ( key == null ) throw new IOException("Key no longer registered");
- KeyAttachment att = (KeyAttachment) key.attachment();
- int written = 0;
- boolean timedout = false;
- int keycount = 1; //assume we can write
- long time = System.currentTimeMillis(); //start the timeout timer
- try {
- while ( (!timedout) && buf.hasRemaining()) {
- if (keycount > 0) { //only write if we were registered for a write
- //直接往socket中写数据
- int cnt = socket.write(buf); //write the data
- lastWrite.set(cnt);
- if (cnt == -1)
- throw new EOFException();
- written += cnt;
- //写数据成功,直接进入下一次循环,继续写
- if (cnt > 0) {
- time = System.currentTimeMillis(); //reset our timeout timer
- continue; //we successfully wrote, try again without a selector
- }
- }
- //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败
- try {
- //开始一个倒数计数器
- if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
- //将socket注册到辅Selector,这里poller就是BlockSelector线程
- poller.add(att,SelectionKey.OP_WRITE);
- //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒
- att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
- }catch (InterruptedException ignore) {
- Thread.interrupted();
- }
- if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {
- keycount = 0;
- }else {
- //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket
- keycount = 1;
- att.resetWriteLatch();
- }
- if (writeTimeout > 0 && (keycount == 0))
- timedout = (System.currentTimeMillis() - time) >= writeTimeout;
- } //while
- if (timedout)
- throw new SocketTimeoutException();
- } finally {
- poller.remove(att,SelectionKey.OP_WRITE);
- if (timedout && key != null) {
- poller.cancelKey(socket, key);
- }
- }
- return written;
- }
Java代码- public void run() {
- while (run) {
- try {
- ......
- Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
- while (run && iterator != null && iterator.hasNext()) {
- SelectionKey sk = (SelectionKey) iterator.next();
- KeyAttachment attachment = (KeyAttachment)sk.attachment();
- try {
- attachment.access();
- iterator.remove(); ;
- sk.interestOps(sk.interestOps() & (~sk.readyOps()));
- if ( sk.isReadable() ) {
- countDown(attachment.getReadLatch());
- }
- //发现socket可写状态恢复,将倒数计数器置位,通知Worker线程继续
- if (sk.isWritable()) {
- countDown(attachment.getWriteLatch());
- }
- }catch (CancelledKeyException ckx) {
- if (sk!=null) sk.cancel();
- countDown(attachment.getReadLatch());
- countDown(attachment.getWriteLatch());
- }
- }//while
- }catch ( Throwable t ) {
- log.error("",t);
- }
- }
- events.clear();
- try {
- selector.selectNow();//cancel all remaining keys
- }catch( Exception ignore ) {
- if (log.isDebugEnabled())log.debug("",ignore);
- }
- }
Java代码- ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>()
- ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>()
- ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>()
- ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>()