首页 [尚硅谷]_张晓飞_Tomcat系统架构分析

[尚硅谷]_张晓飞_Tomcat系统架构分析

举报
开通vip

[尚硅谷]_张晓飞_Tomcat系统架构分析 “玩转”Java 系列 ————————————————————————————— 1 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 Tomcat系统架构分析 Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。 1. Tomcat整体结构 1.1. Tomcat总体结构...

[尚硅谷]_张晓飞_Tomcat系统架构分析
“玩转”Java 系列 ————————————————————————————— 1 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 Tomcat系统架构分析 Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。 1. Tomcat整体结构 1.1. Tomcat总体结构图 从上图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是 可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组 件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景 “玩转”Java 系列 ————————————————————————————— 2 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 也十分相关,所以一个 Container 可以选择对应多个 Connector。 多个 Connector 和一个 Container 就形成了一个 Service,Service 的概念大家都很熟悉了,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生 死大权,那就非 Server 莫属了。所以整个 Tomcat 的生命周期由 Server 控制。 1.2. 以 Service 作为“婚姻” 我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情 侣的话,Connector 主要负责对外交流,可以比作为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作 为 Girl。那么这个 Service 就是连接这对男女的结婚证了。是 Service 将它们连接在一起,共同组成一个家庭。当然要组成一个家 庭还要很多其它的元素。 说白了,Service 只是在 Connector 和 Container 外面多包一层, 把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。这个 Service 接口的 方法 快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载 列表如下: “玩转”Java 系列 ————————————————————————————— 3 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1) Service 接口 从 Service 接口中定义的方法中可以看出,它主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,注意接 口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的 生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设 计模式,关于这个接口将在后面介绍。 Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅 实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控 制它下面的组件的生命周期了。StandardService 类结构图如下: “玩转”Java 系列 ————————————————————————————— 4 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 2) StandardService 的类结构图 从上图中可以看出除了 Service 接口的方法的实现以及控制组件生 命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的 方法的实现,不仅是这个 Service 组件,Tomcat 中其它组件也同样 有这几个方法,这也是一个典型的设计模式,将在后面介绍。 “玩转”Java 系列 ————————————————————————————— 5 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 下面看一下 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码: 3) StandardService. SetContainer public void setContainer(Container container) { Container oldContainer = this.container; if ((oldContainer != null) && (oldContainer instanceof Engine)) ((Engine) oldContainer).setService(null); this.container = container; if ((this.container != null) && (this.container instanceof Engine)) ((Engine) this.container).setService(this); if (started && (this.container != null) && (this.container instanceof Lifecycle)) { try { ((Lifecycle) this.container).start(); } catch (LifecycleException e) { ; } } synchronized (connectors) { for (int i = 0; i < connectors.length; i++) connectors[i].setContainer(this.container); } if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) { try { ((Lifecycle) oldContainer).stop(); } catch (LifecycleException e) { ; } } support.firePropertyChange("container", oldContainer, this.container); “玩转”Java 系列 ————————————————————————————— 6 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 } 这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关 联了 Container,如果已经关联了,那么去掉这个关联关系—— oldContainer.setService(null)。如果这个 oldContainer 已经被启动 了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个 新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监 听程序。这里值得注意的地方就是,修改 Container 时要将新的 Container 关联到每个 Connector,还好 Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。 4) StandardService. addConnector public void addConnector(Connector connector) { synchronized (connectors) { connector.setContainer(this.container); connector.setService(this); Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results; if (initialized) { try { connector.initialize(); } catch (LifecycleException e) { e.printStackTrace(System.err); } } “玩转”Java 系列 ————————————————————————————— 7 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 if (started && (connector instanceof Lifecycle)) { try { ((Lifecycle) connector).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange("connector", null, connector); } } 上面是 addConnector 方法,这个方法也很简单,首先是设置关联关 系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注 意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑可 以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始 就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当 前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这 种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后 拿来借鉴。 最新的 Tomcat6 中 StandardService 也基本没有变化,但是从 Tomcat5 开始 Service、Server 和容器类都继承了 MBeanRegistration 接口,Mbeans 的管理更加合理。 “玩转”Java 系列 ————————————————————————————— 8 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1.3. 以 Server 为“居” 前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家 庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存 之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。 Server 要完成的任务很简单,就是要能够提供一个接口让其它程序 能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问 的 Service。还有其它的一些次要的任务,如您住在这个地方要向当 地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么 的。 Server 的类结构图如下: 1) Server 的类结构图 “玩转”Java 系列 ————————————————————————————— 9 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 它的标准实现类 StandardServer 实现了上面这些方法,同时也实现 了 Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看 一下 StandardServer 重要的一个方法 addService 的实现: 2) StandardServer.addService public void addService(Service service) { service.setServer(this); synchronized (services) { Service results[] = new Service[services.length + 1]; System.arraycopy(services, 0, results, 0, services.length); results[services.length] = service; services = results; if (initialized) { try { service.initialize(); } catch (LifecycleException e) { e.printStackTrace(System.err); } } if (started && (service instanceof Lifecycle)) { try { ((Lifecycle) service).start(); } catch (LifecycleException e) { ; } } support.firePropertyChange("service", null, service); } } “玩转”Java 系列 ————————————————————————————— 10 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 从上面第一句就知道了 Service 和 Server 是相互关联的,Server 也是和 Service 管理 Connector 一样管理它,也是将 Service 放在 一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生 命周期。Tomcat6 中也是没有什么变化的。 1.4. 组件的生命线“Lifecycle” 前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它 们是如何管理的呢? Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件只 要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制 了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中 所有组件的生命周期,这个最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat。 下面是 Lifecycle 接口的类结构图: 1) Lifecycle 类结构图 “玩转”Java 系列 ————————————————————————————— 11 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生 命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架 中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。 Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组 件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是 调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的 Start 方法代码如下: 2) StandardServer.Start public void start() throws LifecycleException { if (started) { log.debug(sm.getString("standardServer.start.started")); return; } lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; synchronized (services) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).start(); } } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); } “玩转”Java 系列 ————————————————————————————— 12 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 监听的代码会包围 Service 组件的启动过程,就是简单的循环启动所 有 Service 组件的 Start 方法,但是所有 Service 必须要实现 Lifecycle 接口,这样做会更加灵活。 Server 的 Stop 方法代码如下: 3) StandardServer.Stop public void stop() throws LifecycleException { if (!started) return; lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) ((Lifecycle) services[i]).stop(); } lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null); } 它所要做的事情也和 Start 方法差不多。 2. Connector 组件 Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是 负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 “玩转”Java 系列 ————————————————————————————— 13 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 Response 对象分别用于和请求端交换数据,然后会产生一个线程来 处理这个请求并把产生的 Request 和 Response 对象传给处理这 个请求的线程,处理这个请求的线程就是 Container 组件要做的事 了。 由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释: 2.1. Connector 处理一次请求顺序图 “玩转”Java 系列 ————————————————————————————— 14 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以 选择替换的。Connector 最重要的功能就是接收连接请求然后分配线 程让 Container 来处理这个请求,所以这必然是多线程的,多线程 的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化, 它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象。 下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下 Connector 的主要类图: 2.2. Connector 的主要类图 “玩转”Java 系列 ————————————————————————————— 15 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 看一下 HttpConnector 的 Start 方法: 2.3. HttpConnector.Start public void start() throws LifecycleException { if (started) throw new LifecycleException (sm.getString("httpConnector.alreadyStarted")); threadName = "HttpConnector[" + port + "]"; lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; threadStart(); while (curProcessors < minProcessors) { if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) break; HttpProcessor processor = newProcessor(); recycle(processor); } } threadStart() 执行就会进入等待请求的状态,直到一个新的请求到 来才会激活它继续执行,这个激活是在 HttpProcessor 的 assign 方 法中,这个方法是代码如下 : 2.4. HttpProcessor.assign synchronized void assign(Socket socket) { while (available) { try { wait(); } catch (InterruptedException e) { “玩转”Java 系列 ————————————————————————————— 16 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 } } this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } 创建 HttpProcessor 对象是会把 available 设为 false,所以当请求 到来时不会进入 while 循环,将请求的 socket 赋给当期处理的 socket,并将 available 设为 true,当 available 设为 true 是 HttpProcessor 的 run 方法将被激活,接下去将会处理这次请求。 Run 方法代码如下: 2.5. HttpProcessor.Run public void run() { while (!stopped) { Socket socket = await(); if (socket == null) continue; try { process(socket); } catch (Throwable t) { log("process.invoke", t); } connector.recycle(this); } “玩转”Java 系列 ————————————————————————————— 17 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 synchronized (threadSync) { threadSync.notifyAll(); } } 解析 socket 的过程在 process 方法中,process 方法的代码片段如 下: 2.6. HttpProcessor.process private void process(Socket socket) { boolean ok = true; boolean finishResponse = true; SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize()); } catch (Exception e) { log("process.create", e); ok = false; } keepAlive = true; while (!stopped && ok && keepAlive) { finishResponse = true; try { request.setStream(input); request.setResponse(response); output = socket.getOutputStream(); response.setStream(output); response.setRequest(request); ((HttpServletResponse) response.getResponse()) “玩转”Java 系列 ————————————————————————————— 18 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 .setHeader("Server", SERVER_INFO); } catch (Exception e) { log("process.create", e); ok = false; } try { if (ok) { parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol().startsWith("HTTP/0")) parseHeaders(input); if (http11) { ackRequest(output); if (connector.isChunkingAllowed()) response.setAllowChunking(true); } } 。。。。。。 try { ((HttpServletResponse) response).setHeader ("Date", FastHttpDateFormat.getCurrentDate()); if (ok) { connector.getContainer().invoke(request, response); } 。。。。。。 } try { shutdownInput(input); socket.close(); } catch (IOException e) { ; } catch (Throwable e) { “玩转”Java 系列 ————————————————————————————— 19 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 log("process.invoke", e); } socket = null; } 当 Connector 将 socket 连接封装成 request 和 response 对象后 接下来的事情就交给 Container 来处理了。 回页首 3. Servlet 容器“Container” Container 是容器的父接口,所有子容器都必须实现这个接口, Container 容器的设计用的是典型的责任链的设计模式,它有四个子 容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个 组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多 个 Wrapper 就要定义一个更高的 Container 了,如 Context, Context 通常就是对应下面这个配置: 3.1. Server.xml 3.2. 容器的总体设计 Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要 运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件, 这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表 一个完整的 Servlet 引擎。 那么这些容器是如何协同工作的呢?先看一下它们之间的关系图: “玩转”Java 系列 ————————————————————————————— 21 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1). 四个容器的关系图 当 Connector 接受到一个连接请求时,将请求交给 Container, Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么 把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet 处理。下面是这个过程的时序图: “玩转”Java 系列 ————————————————————————————— 22 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 2). Engine 和 Host 处理请求的时序图 这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中 也有用的,同样 Pipeline 的原理也基本是相似的,它是一个管道, Engine 和 Host 都会执行这个 Pipeline,您可以在这个管道上增加 任意的 Valve,Tomcat 会挨个执行这些 Valve,而且四个组件都会 有自己的一套 Valve 集合。您怎么才能定义自己的 Valve 呢?在 “玩转”Java 系列 ————————————————————————————— 23 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve 如下: 3). 11. Server.xml ……… ………… StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子 容器,以继续往下执行。 前面是 Engine 和 Host 容器的请求过程,下面看 Context 和 Wrapper 容器时如何处理请求的。下面是处理请求的时序图: “玩转”Java 系列 ————————————————————————————— 24 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 4). Context 和 wrapper 的处理请求时序图 从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保 存了当前请求正在处理的 Host、Context 和 wrapper。 3.3. Engine 容器 Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图 如下: “玩转”Java 系列 ————————————————————————————— 25 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1). Engine 接口的类结构 它的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也 只能是 Host 类型的,代码如下: 2) StandardEngine. addChild public void addChild(Container child) { if (!(child instanceof Host)) throw new IllegalArgumentException (sm.getString("standardEngine.notHost")); super.addChild(child); } public void setParent(Container container) { throw new IllegalArgumentException (sm.getString("standardEngine.notParent")); } “玩转”Java 系列 ————————————————————————————— 26 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监 听。 3.4. Host 容器 Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟 主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这 些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信 息。 “玩转”Java 系列 ————————————————————————————— 27 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 1). Host 相关的类图 从上图中可以看出除了所有容器都继承的 ContainerBase 外, StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接 口的主要方法,这些方法都是安装、展开、启动和结束每个 web application。 Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最 要的几个方法,Host 可以调用这些方法完成应用的部署等。 “玩转”Java 系列 ————————————————————————————— 28 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 3.5. Context 容器 Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环 境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。 Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实 例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如 何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一 个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就可以发现获取子容器都是通过 request 来 分配的。 Context 准备 Servlet 的运行环境是在 Start 方法开始的,这个方法 的代码片段如下: 1). StandardContext.start public synchronized void start() throws LifecycleException { ……… if( !initialized ) { try { init(); } catch( Exception ex ) { throw new LifecycleException("Error initializaing ", ex); } } “玩转”Java 系列 ————————————————————————————— 29 【更多 Java – Android 资料下载,可访问尚硅谷(中国)官网 www.atguigu.com 下载区】 ……… lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); setAvailable(false); setConfigured(false); boolean ok = true; File configBase = getConfigBas
本文档为【[尚硅谷]_张晓飞_Tomcat系统架构分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_221943
暂无简介~
格式:pdf
大小:1MB
软件:PDF阅读器
页数:36
分类:互联网
上传时间:2014-03-20
浏览量:100