第8章 分布式计算_第1页
第8章 分布式计算_第2页
第8章 分布式计算_第3页
第8章 分布式计算_第4页
第8章 分布式计算_第5页
已阅读5页,还剩62页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2020年1月5日 第8章分布式计算 从计算机网络的发展历史看 它已经涉及到两大类应用程序 一类是应用程序在计算机之间移动文件和数据 主要由譬如FTP HTTP IMAP SMTP和POP及其他一些协议来处理 另一类应用程序允许一台计算机运行另一台计算机上的程序 譬如telnet rlogin 远程过程调用 RemoteProcedureCall RPC 及大量数据库中间件等许多领域 前几章所讨论的内容主要是文件和数据传输技术 本章则讨论分布式计算 将涉及如何从一台计算机上调用位于远程主机上的Java程序的方法 即远程方法调用技术 同时也介绍CORBA标准 它允许用户在Java对象和其他语言编写的对象间进行调用 远程方法调用属于网络编程的特有高层API之一 8 1分布式计算与RMI 分布式计算是当前软件开发技术的重要发展方向之一 在计算机网络中 分布式计算系统结构允许用户共享网络中的计算机资源 本节主要介绍分布式计算与远程方法调用 RemoteMethodInvoke RMI 的一些基本概念 8 1 1何谓分布式计算 分布式计算的英文为DistributedComputing 如果说某项工作是分布式的 那么 参与这项工作的一定不只是一台计算机 而是一个计算机网络 计算机具有两种功能 一是它能够存储信息 或者称为数据存储 另一个功能是它具有处理数据的能力 它能够计算 按这一分类原则 可以把计算机网络所做的工作分为分布式数据存储和分布式计算两种类型 在分布式计算中 计算机网络侧重于它的计算功能 在分布式数据中 完成一件工作时 数据可能来自于网络中不同的计算机 但对于这些数据的处理却是由本地主机完成的 而在分布式计算环境中 数据的处理不只是由一台主机完成 而是由多台计算机协同完成的 比如 一些对CPU和内存要求都很高的应用程序 在客户机上不能很好地运行 则可以在网络中的服务器上执行 那么它们就形成了一个分布式的计算 由于计算机的计算总是离不开数据 所以 在大部分情况下 分布式计算总是伴随着分布式数据存储 分布式计算往往是一个表示程度的量词 为此在分布式计算环境中制定了一些共享网络计算机资源的标准和协议 例如远程过程调用 RPC 标准和远程信息交换协议 RemoteMessagingProtocol RMP 等 Java分布式对象计算技术通常指远程方法调用 RMI 和企业级JavaBean EnterpriseJavaBeans EJB RMI提供了一个Java对象远程调用另一Java对象的方法 与传统RPC类似 只能支持初级的分布式对象互操作 Sun公司基于RMI 提出了EJB 基于Java服务器端组件模型 EJB框架提供了像远程访问 安全 交易 持久和生命期管理等多种支持分布式对象计算的服务 目前 Java技术和CORBA技术有融合的趋势 Java是一门适合于分布式计算环境的语言 除了具有很好的安全性和可移植性外 还提供了丰富的网络类库和包 譬如java rmi java rmi server和java rmi registry等 利用这些类库和包 可以很轻松地编写多种类型的分布式计算程序 8 1 2远程方法调用 RMI 如何通过网络执行位于其他计算机上的程序代码 显然解决这个问题的最佳方式是 某些对象恰好位于另一台远程计算机 可以向它们发送一条消息 并获得返回结果 就像那些对象位于本地计算机一样 远程方法调用 RMI 采用的正是这种抽象 RMI是一种分布式系统技术 它允许一个JVM调用运行在网络中另一JVM上的对象方法 这种技术对开发大型系统尤其重要 因为它使得在多台计算机上分布的资源和处理负载的想法成为可能 1 RMI系统体系结构的分层模型为了与对象调用的语义匹配 分布式对象系统需要进行远程方法调用 该方法是一种本机存根 Surrogate 对象管理远程对象的调用 RMI用面向对象的思想继承和发展了RPC 其体系结构的分层模型如图8 1所示 RMI系统体系结构分层模型 2 Java的远程方法调用Java2EnterpriseEdition J2EE 远程方法调用 RMI 骨架允许创建透明的 分布式的服务和应用程序 基于RMI的应用程序由Java对象构成 这些对象相互调用 同时忽略对方的位置 换言之 一个Java对象可调用另一个JVM上的某个Java对象的方法 其调用过程就像这些远程对象位于本地一个JVM上的某个Java对象的方法一样 如图8 2给出了调用过程的一个示意 运行在一个JVM上的对象调用另一个JVM上的对象方法 8 2RMI应用编程示例 Java的RMI技术能够使在一台计算机上运行的Java程序可以远程执行另外一台计算机上的对象方法 执行结果可以通过方法的参数返回给Java程序 因此 它涉及到服务器和客户机两方面的应用程序 服务器程序创建一系列的对象 并向RMI注册服务器注册 等待客户机来执行 而客户机程序获取服务器上对象的引用 然后远程执行其方法 这些方法将在服务器程序所在的计算机上执行 执行的结果可以通过方法的返回值返回给客户机程序 因此 编写JavaRMI分布式应用程序是比较复杂的 可以将其分为服务器端与客户机端两大任务 主要步骤包括 定义一个远程接口 实现远程服务接口 服务器端开发 客户机端开发 生成骨架代码 启动RMI 服务器和客户机 本节结合一个比较简单的RMI应用程序示例 来介绍RMI应用程序的编制与运行过程 8 2 1服务器端的编程与运行 1 创建远程接口Java提供了一个完全限定名称为java rmi Remote的接口 任何对象要想参与和另一个Java对象的远程会话 就必须直接或间接地实现该接口 因此 为创建新的对象 首先要定义一个扩展了java rmi Remote接口的子接口 Remote是一个标记接口 没有任何自己的方法 它惟一的目的就是将远程对象标记出来 需要被远程对象客户机端调用的方法应该定义在这个Remote的子接口中 远程对象可能有很多公共方法 但只有在远程接口中声明的方法才能从远程调用 子接口的每个方法都必须声明抛出java rmi RemoteException异常 RemoteException是当使用RMI时可能抛出的大多数异常的超类 其中许多异常都与外部系统和网络有关 例如 程序示例example8 1在服务器端定义了一个远程对象的简单接口Fibonacci接口 以计算任意大小斐波那契数 注 斐波那契数是1 2 3 5 8 13开始的数列 每个数都是前两个数的和 该远程对象可以在高性能计算机 作为服务器 上运行 为低性能客户计算机计算结果 创建一个远程接口所必须遵守的规则 1 远程接口必须声明为public属性 2 远程接口必须对java rmi Remote这个接口进行扩展 3 除与应用程序本身有关的异常之外 远程接口中的每个方法都必须在自己的throws从句中声明java rmi RemoteException 4 作为参数或返回值传递的一个远程对象必须声明为远程接口 不可声明为具体的实施类 因此 定义远程接口java rmi Remote接口的典型格式是 importjava rmi importjava rmi RemoteException publicinterface接口名extendsRemote 需要被远程调用的方法 2 实现RMI远程接口创建了一个远程接口之后 接下来需要编写一个实现这个远程接口的类 重要的是实现这个实现要给出每个方法的功能 为了使远程类的实例变成能为远程客户机提供服务的远程对象 比较常用的途径是使远程类继承java rmi server UnicastRemoteObject类 并使远程类的构造函数声明抛出RemoteException异常 例如 在示例example8 2中 所编写的FibonacciImpl类实现了远程接口Fibonacci中定义的两个getFibonacci 方法 只有这两个getFibonacci 方法才在客户机端可被调用 因为它们是由Fibonacci接口惟一定义的方法 3 编制服务器程序并注册远程对象远程接口的类定义好之后 需要编写服务器程序创建Fibonacci类型的对象 并进行注册 以便客户机可以调用 这项工作分为以下几个步骤完成 1 创建服务器程序RMI采取一种客户机 服务器结构进行通信 这意味着在RMI会话的某一端 必须有一个对象充当服务器 另一端的对象则充当客户机 服务器负责创建每个远程对象的实例 然后向远程方法调用注册表 rmiregistry 注册 在RMI中 rmiregistry注册表承担了为远程对象提供命名服务的任务 服务器可以自主 这要求它实现一个main方法 避免必须依赖其他类才能执行 首先创建安全管理器 方法如下 System setSecurityManager newRMISecurityManager 然后用FibonacciImpl创建远程对象并通过RMI协议将对象向RMI注册表注册 即把对象fib与名字fibonacci绑定 方法如下 FibonacciImplfib newFibonacciImpl Naming rebind fibonacci fib 2 创建存根类和骨架类存根类和骨架类负责发出并处理RMI请求 不过并不需要程序员编写这两个类 而是用JDK给出的rmic工具根据远程类的字节码自动创建 首先编译实现与接口 例如若以上程序放在目录D jnp chapter8 中 键入编译命令 D jnp chapter8 javac java编译后生成三个文件Fibonacci class FibonacciImpl class和FibonacciServer class 对欲远程执行的FibonacciImpl class生成存根 Stub 和骨架 Skeleton 文件 例如键入如下命令来生成存根类和骨架类 D jnp chapter8 rmicFibonacciImpl在这种情况下 就生成了FibonacciImpl Stub class和FibonacciImpl Skel class两个文件 RMI和RMI注册处都需要这两个类以及服务接口类 3 启动RMI服务器完成以上各项工作后 现在可以启动RMI服务器了 但是为了获取对远程对象的引用 RMI提供了名为注册表 registry 的一个远程对象 它将名称与远程对象关联起来 RMI服务器要向注册表注册每一个远程对象 以便定位和检索对象 RMI客户机端希望调用远程对象上的一个方法时 首先必须根据远程对象的名称在注册表中定位远程对象 如果远程对象存在 注册表就返回对那个对象的一个引用 然后 要使用这个引用来发出对远程对象的方法调用 在Windows中 在DOS提示符下启动注册表的方法是 D jnp chapter8 startrmiregistry注册表在默认情况下尝试侦听1099端口 在后台运行 如果运行失败尤其是收到端口已经使用的消息时 可以通过附加一个另外的端口号运行注册表 最后是启动服务器 譬如 D jnp chapter8 startjavaFibonacciServerFibonacciServerready 这说明该RMI服务器现在与注册表都可以接受远程方法调用了 8 2 2调用远程服务的客户机程序 客户机欲远程调用RMI服务器上的方法 只需取得指向远程接口的对象引用即可 并不关心怎样发送和接收消息以及服务器的位置 但为了找到服务对象 首先需要到RMI服务器的注册表上去查找 一般是通过调用注册表的lookup 方法进行查找对象 这个操作完成之后 客户机就能调用服务接口的方法 就好象该接口是本地对象一样 譬如 程序示例example8 4是Fibonacci接口的一个简单客户机程序 Chapter8 example8 4 FibonacciClient D jnp chapter8 javaFibonacciClientrmi 202 119 167 17 fibonacci01351050150其中 202 119 167 17为运行FibonacciServer的主机IP地址 运行结果如图8 3所示 在RMI注册服务器端显示的信息如图8 4所示 8 2 3RMI系统的工作方式及运行 1 启动注册表为了使客户机找到服务的对象方法 RMI服务器必须注册查找服务 作为Java平台的一部分 rmiregistry应用程序作为独立进程运行 允许应用程序注册RMI服务或取得指向命名服务的引用 一旦服务器已经注册 它就将等待来自于客户机的传入请求 2 远程对象交互RMI客户机发送消息远程调用对象方法 值得注意的是 在调用任何远程方法之前 客户机必须拥有远程对象引用 这通常通过在RMI注册表查找服务得到 客户机请求特殊的服务名 并接收远程资源的URL 表示引用远程对象的URL格式为 rmi hostname port servicename其中 hostname代表服务器的名字或IP地址 port代表服务在主机上的位置 而servicename是对服务的字符串描述 以斐波那契数的分布式计算为例 运行RMI系统的内部工作时序关系如图8 5所示 3 运行RMI系统运行RMI系统时必须按照启动注册表 启动服务器 然后再运行客户机这样一个顺序来执行应用程序 还要记住在RMILightBubImpl类上运行rmic工具 在复制类文件之前生成存根 Stub 和骨架 Skeleton 类 通常运行RMI系统的一般步骤如下 把所有必须的文件复制到客户机和服务器的本地文件系统上的某个目录下 检查当前目录是否是存放类文件的目录 且已包含在类路径中 将当前目录更改为存放类文件的目录 并调用执行rmiregistry应用程序 不带任何参数 启动RMI服务器 在独立的控制台窗口中 运行服务器端程序 在独立的控制台窗口中 而且最好是在另一台联网的计算机上 启动客户机端程序 如有必要还需要指定运行rmiregistry应用程序的主机IP地址 或主机名 8 3远程方法调用包和类 通过前两节的内容 已经初步熟悉了RMI的基本概念 并且使用引用RMI包和类等介绍了怎样编写RMI服务 服务器和客户机 现在进一步讨论构成JavaAPI的RMI子集的包和类 处理RMI的包共有5个 其中最常用的是java rmi java rmi server和java rmi registry三个包 操作远程对象所需要的最常用方法都放在这三个包中 下面将重点介绍 包java rmi activation为RMI对象激活提供支持 利用RMI激活机制 可以按需要启动服务 而不是持久地运行从而降低资源消耗 在8 6节介绍 包java rmi dgc为RMI分布式垃圾回收提供了类和接口 8 3 1java rmi包 java rmi包定义了客户机端常见的类 接口和异常 当要编写访问远程对象但不是远程对象的程序时需要这个包 java rmi包共定义了一个接口 三个类以及大量异常 1 Remote接口在Remote接口中声明可以从非本地虚拟机上访问的远程方法 只有在 远程接口 扩展java rmi Remote的接口 中指定的方法才可远程使用 远程接口需符合以下两个条件 任何远程对象都必须直接或间接继承java rmi Remote接口 接口中的所有方法需声明抛出java rmi RemoteException异常 若不遵循这个条件将导致生成存根和骨架类时发生错误 2 远程类 1 类MarshalledObjectMarshalledObject包含一个字节流 该流具有给定了其构造函数的对象的序列化表示形式 在从包含的字节流进行反序列化时 get 方法会返回原始对象的一个新的副本 包含的对象使用相同的用来编组和解组参数的序列化语义进行序列化和反序列化 并返回RMI调用的值 2 类NamingNaming类提供在对象注册表中存储和获得远程对远程对象引用的方法 Naming类的每个方法都可以带一个参数 该参数表示到某个注册表的URL的Java字符串 并使用以下形式的URL格式 rmi registry hostname registry port servicename其中registry hostname是注册表所在的主机名或IP地址 远程或本地 registry port是注册表接受调用的端口号 servicename是未经注册表解释的简单字符串 registry hostname和registry port两者都是可选项 如果省略了registry hostname 则主机默认为本地主机 如果省略了registry port 则端口默认为1099 该端口是RMI的注册表rmiregistry使用的 熟知 端口 在客户机端使用安全管理器包括以下两个步骤 1 创建安全策略文件 例如具有如下所列内容的java policy文件就是一个安全策略文件 它允许客户机建立任何网络连接 只要端口号不小于1024 由于rmiregistry注册表在默认情况下侦听端口1099 远程对象侦听的端口号大于或等于1024 因此这个安全策略文件允许客户机访问rmiregistry注册表及远程对象 2 为服务器与客户机程序设置安全策略文件和RMISecurityManager安全管理器 譬如 在本章的示例example8 4程序中的FibonacciClient类的main 方法开头的两行程序代码 System setProperty java security policy FibonacciClient class getResource java policy toString System setSecurityManager newRMISecurityManager 其中System setProperty 方法用来设置安全策略文件的加载路径 注意java policy文件与FibonacciClient class要位于同一目录下 System setSecurityManager 方法用来设置安全管理器 它从java policy文件中读取安全策略 8 3 2java rmi server包 java rmi server定义了大量与RMI服务器和远程对象有关的接口 对象和异常 这个包是RMI包中最复杂的一个 包含了构建远程对象的基础类 其方法由客户机所调用的对象使用 这个包定义了9个接口 8到12个类 取决于Java版本 和6种异常 如表8 1 表8 2所列 当编写被客户机端调用的远程对象时要用到这些类 然而实际上尽管定义了大量的类 但在开发RMI服务时 通常很少使用这个包中的大多数类 只要熟悉其中的几个就可以 8 3 3java rmi registr包 java rmi registr包定义了用于查找RMI注册表和命名远程对象的一个类 接口和异常 注册表是一个将名称映射到远程对象的远程对象 服务器使用注册表注册其远程对象 以便于查找这些对象 当某个对象希望在远程对象上调用某个方法时 则必须首先使用其名称查找该远程对象 注册表将为调用对象返回一个对远程对象的引用 使用这种方法可以调用远程方法 1 类LocateRegistry 1 类定义publicfinalclassLocateRegistryextendsObjectLocateRegistry用于获得对特定主机 包括本地主机 上引导远程对象注册表的引用 或用于创建一个接受对特定端口调用的远程对象注册表 注意 getRegistry调用并不实际生成到远程主机的连接 它只创建对远程注册表的本地引用 即便远程主机上没有正运行的注册表 它也会成功创建一个引用 因此 调用作为此方法的结果返回的远程注册表的后续方法可能会失败 2 方法publicstaticRegistrygetRegistry throwsRemoteException 返回本地主机在默认注册表端口1099上对远程对象Registry的引用 publicstaticRegistrygetRegistry intport throwsRemoteException 返回本地主机在指定port上对远程对象Registry的引用 publicstaticRegistrygetRegistry Stringhost throwsRemoteException 返回指定host在默认注册表端口1099上对远程对象Registry的引用 如果host为null 则使用本地主机 2 Registry接口 1 接口定义publicinterfaceRegistryextendsRemoteRegistry是简单远程对象注册表的一个远程接口 它提供存储和检索绑定了任意字符串名称的远程对象引用的方法 bind unbind和rebind方法用于改变注册表中的名称绑定 lookup和list方法用于查询当前的名称绑定 2 方法这个接口定义了5个公共方法 用于访问 创建或修改RMI对象注册表的注册项 staticvoidbind Stringname Remoteobj throwsRemoteException AlreadyBoundException AccessException 绑定对此注册表中指定name的远程引用 staticString list throwsRemoteException AccessException 返回在此注册表中绑定名称的数组 该数组将包含一个此注册表中调用此方法时绑定的名称 staticRemotelookup Stringname throwsRemoteException NotBoundException 返回注册表中绑定到指定name的远程引用 staticvoidunbind Stringname throwsRemoteException NotBoundException 注销此注册表中指定的name绑定 staticvoidrebind Stringname Remoteobj throwsRemoteException AccessException 用提供的远程引用替换此注册表中指定的name绑定 如果存在指定name的现有绑定 则会丢弃此现有的绑定 8 4代码与类的动态加载 动态加载代码的基本思想是 在客户机端编制需要动态传递到服务器上执行的代码 用其创建对象 在远程执行服务器上的方法method 时 将该对象作为参数传递给method 其结构关系如下 服务器客户机对象obTheTaskss newTheTask 方法method Objectxx Naming lookup downcodeyy downcode xx st yy method ss 8 4 1服务器端的程序编制 编制Java类使其方法能被远程调用 首先要定义java rmi Remote的子接口TaskInterface 将被远程调用的方法method 定义在这个接口中 程序示例如example8 6 Chapter8 example8 6 TaskInterface 8 4 2客户机端的程序编制 根据服务器中method 方法的定义 首先要编写需要动态上传的代码的TaskInterface接口实现 将需要执行的任务定义在doWork 方法中 程序示例如example8 10 Chapter8 example8 10 TheTask 8 4 3客户机 服务器程序部署及类的动态加载 RMI的动态加载类的机制能克服这些不足 动态加载类的过程在程序运行时才发生 对客户机是透明的 更为重要的是 这些被动态加载的类的文件都集中在网络上的同一个地方 如果类被修改 只要更新这个地方的类文件就可以了 现在以客户机 服务器程序的编译 部署及执行为例 介绍如何在分布式系统中部署RMI应用系统及类的动态加载 1 创建目录结构 2 部署RMI应用程序具体地说 在服务器端 需要将服务器端各程序代码放在一个Server目录中 编译生成四个TaskInterface class downcode class TaskInterfaceImpl class和downcodeserver class文件 然后使用RMIC命令生成存根TaskInterfaceImpl Stub class文件 Java1 4以下需要 3 在本地运行测试客户机 服务器程序为便于进行程序调试 下面先在同一台主机上运行 测试程序 然后发布到网络上运行 首先启动Web服务器 D jnp chapter8 dyncode download javaThreadWebServerThreadWebServerOk 1 注册RMI服务器在DOS下 在C盘根目录下或者其他任意目录下 使用命令 startrmiregistry启动RMI注册服务器 之所以在任意一个目录下启动rmiregistry注册表 是为了测试rmiregistry注册表能否从java rmi server codebase系统属性指定的位置动态加载类 2 运行服务器程序在DOS下 转到D jnp chapter8 dyncode server目录下 执行如下命令 执行程序时需要使用如下参数 java Djava rmi codebase http 127 0 0 1 Djava security policy java policydowncodeserver 3 运行客户机程序在DOS下 转到D jnp chapter8 dyncode client目录下 运行命令 java Djava rmi server codebase http 127 0 0 1 Djava security policy java policydowncodeclientfromremoteOK returnfromdynamiccode 其中127 0 0 1是放置TheTask class的Web服务器 或者执行命令 java Djava rmi server codebase file d jnp chapter8 dyncode download Djava security policy java policydowncodeclient 运行成功后 客户机窗口将显示 fromremoteOK returnfromdynamiccode 说明远程方法method 的返回值可以把服务器上运行的动态代码TheTask class执行的结果 returnfromdynamiccode 返回给了客户机 运行成功后 在服务器窗口将显示 Aremotecall printedbydynamiccode 这表明客户机执行的method 方法已在服务器上执行 客户机定义的动态代码TheTask class中的输出语句 printedbydynamiccode 在服务器上输出出来 说明动态代码在服务器上已经执行 8 5线程的动态加载 在客户机动态上传任务时 不仅有计算工作 还有一些工作要以线程的方式一直运行 譬如一个监控网络状态的程序 就需要在服务器上一直运行以进行监控 然而 若将线程动态加载到服务器有许多问题需要解决 其中最主要的问题是动态代码必须实现Serializable接口 一种解决方法是只将线程要执行的代码动态传递给服务器 而在服务器上执行分配CPU时间 启动线程等操作 8 5 1编制动态加载的线程代码 很显然 若向服务器动态加载某一段线程代码并执行所加载的代码 客户机端程序需要实现两种接口 一种是需要实现Serializable接口 以便能动态上传加载 另一种是需要实现Runnable接口 以便动态加载到服务器后由服务器创建线程 程序示例代码如example8 12 Chapter8 example8 12 TheTask 8 5 2实现RMI远程接口 1 创建RMI远程接口首先要定义远程接口 将需要远程调用的方法定义在这个接口中 并声明抛出RemoteException异常 程序示例代码如example8 13 2 实现RMI远程接口创建RMI远程接口以后 接下来定义一个远程接口实现类InterfaceImpl来实现downcode接口 以便被远程执行 程序示例代码如example8 14 Chapter8 example8 14 InterfaceImpl 8 5 3客户机 服务器程序 1 编制服务器程序并运行InterfaceImpl类定义好之后 同样要编写服务器程序创建该InterfaceImpl类的对象 并进行注册 以便客户机程序远程调用 程序示例代码见example8 15 Chapter8 example8 15 downcodeserver 2 编制客户机程序并运行部署运行客户机端的程序也基本与上一节中关于动态加载代码时相同 注意 将在example8 12中客户机自己定义的动态代码TheTask class以及在example8 13中创建的远程接口downcode class字节码文件放到客户机端目录中 客户机端的程

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论