yate学习笔记_第1页
yate学习笔记_第2页
yate学习笔记_第3页
yate学习笔记_第4页
yate学习笔记_第5页
已阅读5页,还剩23页未读 继续免费阅读

下载本文档

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

文档简介

ubuntu安装编译运行可以使用 telnet 5038 登录到yate输入?可以显示支持的命令连按tab键可以补全命令先debug on 然后再debug level 10,可以打开log编译步骤要先安装apt-get install libqt4-dev在yate project的根目录下./autogen./configuremake install然后在clients目录下运行./run-qt4 脚本 ,会调用同样目录下的yate-qt4,这个是编译生成的。wimdows安装编译运行yate client安装qt-win-opensource-4.8.0-vs2010.exe qt-vs-addin-1.1.9.exe安装了这2个软件后就可以使用vs2010 vs2005了,下面的不走可以不用做,但要设置环境变量。在环境变量中的系统环境变量增加:QTDIR= C:Qt4.8.0QMAKESPEC=win32-msvc2005Path = C:Qt4.8.0bin;用vs2005打开yate-4.2.0-2yatewindowsYATE.slnyate-4.2.0-2 的main-client.cpp文件有错误,需要改成class WinClientPlugin : public Pluginpublic: WinClientPlugin(): Plugin(winyateclient),m_thread(0) virtual void initialize(void); virtual bool isBusy() const return true; private: WinClientThread* m_thread;Qt4相关的几个工程和_customtable _customtable _customtext _clientarchive需要加上qt库和头文件的路径。然后挨个编译每个工程,然后把run-qt4.bat中的release改成debug,然后双击之,就会运行yate client了。Yate代码学习笔记Yate开发向导1.Yate框架设计Yate的设计是为了提供一个可扩展性的电话引擎,试图以最简简洁的代码,在扩展所需功能与性能、稳定性之间达到最佳平衡。Yate设计分为三大部分:(1)引擎(Engine)该引擎已Yate C+类为基础,将把所有模块组件连接在一起,上图描述了各组件之间的交互过程。(2)模块(Modules)大部分功能由运行时加载的模块来实现。这些模块以动态链接库为载体,作为插件被引擎或外部程序的特定模块加载。被外部程序特定模块加载时,需能够与引擎或其他模块之间相互交互(通信)。(3)消息(Messages)Yate模块(包括插件和外部模块)之间的交互是依靠消息实现的。消息提供了一种可扩展,可定制,并且与具体技术无关的交互机制。每个模块在需要得到信息或者需要通知其他模块时只需要创建并向引擎提交消息,引擎负责会将消息传递给合适的目标。Yate以Class Engine为核心,构建了插件式的管理体系,按照观察者(发布-订阅)的设计模式来处理数据流。Engine类根据配置文件加载插件,缺省参数的情况下,会加载指定目录下所有的插件。然后运行插件的初始化函数initialize()完成插件的初始化。Class Yate提供了一些API(静态函数)用于加载分析配置参数,加载特定模块,和指定目录下的所有模块。/加载指定目录下模块参数relPath相对主模块的路径bool loadPluginDir(const String& relPath);/注册插件,只有注册过的插件才能被初始化bool Register(const Plugin* plugin, bool reg = true);/加载指定模块bool loadPlugin(const char* file, bool local,bool nounload);privatevoid loadPlugins();/从插件目录中加载插件privatevoid initPlugins();/初始化插件private基于列表的发布-订阅示例由客户端、服务和数据源程序组成。可以有多个客户端和多个数据源程序同时运行。客户端订阅服务、接收通知,然后取消订阅。数据源程序向服务发送将与所有当前订户共享的信息。Class Engine:m_dispatcher:m_handles维护着订阅者列表,每个订阅者都实现了以下接口用于接收通知。class MessageHandlervirtual bool received(Message& msg) = 0;Class Engine还提供了一些列的API(静态成员函数)/安装注册订阅者的接口,参数指定订阅的事件类型static bool install(MessageHandler* handler);static bool uninstall(MessageHandler* handler);/发送事件通知,所有注册了该事件类型的都有机会得到事件内容/enqueue为非阻塞函数,即将事件通知加入消息列表中,不关系事件处理的结果/dispatch为阻塞函数,即必须等到订阅者处理完事件才能返回。static bool enqueue(Message* msg);static bool dispatch(Message* msg);一般调用enqueue把message放进了一个消息队列里,再由一个Dispatcher类来对消息顺序处理,相当于向所有订阅者发布消息。插件必须从Module类派生,并实现initialize()这个虚函数,另外,根据需要实现Message的处理类MessageHandler。在initialize()里,按照主题安装MessageHandler,如下所示:Engine:install(new AuthHandler(s_cfg.getIntValue(general,auth_priority,70);上例的AuthHandler即是从MessageHandler中派生而来,它必须实现received()虚函数,以处理接收到的message。如果处理成功,则received()应该返回true,否则返回false。如果某类DrModule需要处理多个事件,首先订阅者必须从MessageReceiver派生,Module就是从MessageReceiver派生的,可以处理多了事件。另外消息处理类必须由MessageRelay派生,当然MessageRelay,也是从MessageHandler的。同样在Dr-Module:initialize中,安装如下TokenDict DrModule:s_messages = engine.halt,DrModule:Halt , gress,DrModule:Progress , call.route,DrModule:Route , chan.text,DrModule:Text , msg.route,DrModule:ImRoute ,0,0DrModule:installRelay(Halt);DrModule:installRelay(Progress);DrModule:installRelay(Route,200);DrModule:installRelay(Text);DrModule:installRelay(ImRoute);DrModule:installRelay(ImExecute);YATE的架构是典型的发布-订阅机制,很好的实现了平台的可扩充性,减少了模块与平台、模块与模块之间的耦合,可以说架构是非常清晰的。比如用户认证的功能,YATE里有三个模块提供三种方式对用户进行认证:文件方式(Regfile),Radius方式,数据库方式。这三种方式都是接收并处理user.auth、user.register等消息,其先后顺序根据install时定的优先级排序。处理的结果由message的retValue()带回。这种处理机制虽然优点很突出,但性能上的缺点也很明显,因为在对消息队列的处理是单线程而且要对所有订阅者进行遍历,效率比较低。2 Yate中的消息在Yate中,消息取代函数成为模块间主要的交互方式。这样的好处在于,当一个模块改变时,其他独立的模块不用做任何修改。另外,因为我们能够轻松的跟踪到消息的传递过程,所以调试起来相对容易。消息包括以下几个组成部分:(1)名字(name)消息类型的标识,允许消息处理器通过名字进行匹配 (2)返回值(return value)一个用字符串表示的处理消息之后的返回值 (3)时间(time)消息被创建的时间;这对于排队的消息检查等待时长非常重要 (4)参数(parameters)可能有多个或0个参数组成,每个参数由名称、值对构成。每个处理器都能根据参数进行不同的动作,或者修改参数本身。未定义参数必须忽略。所有的消息在YATE内部是二进制形式的。然而我们可以通过rmanager模块提供一个对人可读的形式。 YATE内部消息传递通过内存共享(memory sharing)的方式,提高系统的性能。其他传递方式如管道或Sockets,没有内存共享灵活和高效。当被传递到外部模块(external modules)时,消息可被转换成字符串编码的形式,这样所有能处理文本的外部模块都可以处理消息了。可参考文档external module,获取更多详细信息。消息被消息处理器(MessageHandler)处理。消息处理器接收名字匹配的消息,可以对其中的组成部分进行修改,然后停止处理此消息(释放),或让此消息滑动到下一个操作者。消息处理器接收消息分发器通知的顺序在其向引擎注册时提供的优先级决定。优先级数字越小,优先级越高。对于相同优先级的消息处理器,调用顺序是不确定的。调用顺序按以下的规则: *同名的消息调用顺序是不会改变的 *为了避免不确定性,如果消息处理器被移除,并插入一个同等优先级的消息处理器,则他们的顺序由她的的内存地址决定。2.1消息系统工作示例以下是“call.rotue”在消息系统重的工作过程例子当某个电话打来时,消息是这样产生的:1. Message *m = new Message(call.route);2.m-addParam(driver,iax);3.if (e-ies.calling_name)4.m-addParam(callername,e-ies.calling_name);5.else6.m-addParam(callername,e-session-callerid);7.if (e-ies.called_number)8.m-addParam(called,e-ies.called_number);9.else10.m-addParam(called,e-session-dnid);然后我们将消息发送给引擎,检查是否有模块(module)接收并处理了,最后必须将消息销毁。1.if(Engine:dispatch(m)2.Output(Routingreturned:%s,m-retValue().c_str();3.else4.Output(Nobodyroutedthecall!);5.m-destruct();上面的处理方式是阻塞式的,模块发送消息之后需要等待该消息被发送之后才进行后续的处理。Yate中还有一种“发射后忘记”(fire-and-forget)的消息机制,非阻塞式消息机制,这种消息被存储在引擎中的一个队列中,当消息被分发后,由引擎负责释放。这种消息一般都是事关系统全局的重要消息,例如错误报警,如下代码所示:1.Message*m=newMessage(alert);2.m-addParam(reason,Hardwaremalfunction);3.Engine:enqueue(m);如果我们编写的模块需要处理一个路由请求(极有可能是其他模块产生的),我们首先需要声明一个名为RouteHandler的类,并从MessageHandler基类继承。1.classRouteHandler:publicMessageHandler2.3.public:4.RouteHandler(intprio)5.:MessageHandler(call.route,prio)6.virtualboolreceived(Message&msg);7.;然后,由于在received方法中实现,他是类MessageHandler中是纯虚函数,我们必须重载。1.boolRouteHandler:received(Message&msg)2.3.constchar*driver=msg.getValue(driver)4.Debug(DebugInfo,Routefordriver%s,driver);5./dontactuallyrouteanything,letmessagecontinue6.returnfalse;7.最后,在插件的initialized方法中,安装此消息处理器1.voidRegfilePlugin:initialize()2.3.m_route=newRouteHandler(priority);4.Engine:install(m_route);5.这样,该插件就能处理“call.route”消息了,这个例子中实际上只是接收了消息但没有做任何动作,如果需要什么操作,在received方法里实现即可。Yate中的几乎所有消息操作者都是按照这样的框架实现的。2.2消息流示例以呼叫进入为例:路由当一个通道模块检测到有呼叫进入(1),它便发送call.route(2)消息来决定将此呼叫路由到哪个位置。Call.route消息将被叫号码映射到一个呼叫目标。连接当呼叫对象已知以后,呼入通道将其呼叫端点(CallEndPoint)附在call.execute消息上(4)。接收方应该将它的呼叫端点连接到call.execute中携带的呼叫端点上。在等待对端接受呼叫期间应该发送call.ringing消息(6),当呼叫被接受时,call.ansered被发送。会话期间在会话期间,chan.dtmf消息(8,9)能在两个方向上发送。挂机当呼入通道检测到挂机(10),它将断开其呼叫端点。断开呼叫端点将引发两个通道chan.disconnected消息和chan.hangup消息的(图中未包括)发送。从消息流的示例我们可以看到,Yate对呼叫的抽象很清晰,在逻辑上符合人们的思维习惯,比较容易理解。2.3消息类型1.引擎消息(Engine messages)engine.start由引擎发送给普通模块,通知他们Yate准备就绪,并已进入主循环mandengine.statusengine.timer2.通道消息(Channel messages)chan.attachchan.connectedchan.disconnectedchan.dtmf双音多频信号(Dual-tone multi-frequency signaling)chan.hangupchan.masqueradechan.notifychan.recordchan.rtpchan.startupchan.textchan.connectchan.locatechan.controlchan.replaced3.呼叫消息(Call messages)gresscall.ringingcall.routecall.preroutecall.updatecall.conference4.用户消息(User messages)user.accountuser.authuser.loginuser.notifyuser.registeruser.unregister5.资源描述/通知消息(Resource subscribe/notify messages)resource.subscriberesource.notify6. SIP消息(SIP messages)sip.xsip.generate7.编解码特定协议消息(Encode or decode protocol specific messages)isup.decodeisup.encode?isup.mangle8.网络操作消息(Socket operation messages)socket.stunsocket.sslsocket.peel?9.集群相关消息(Clustering related messages)cluster.locate10.即时信息相关消息(Instant messaging related messages)msg.route?msg.execute?11. Jabber / XMPP messagesxmpp.generate?xmpp.iq12.其他消息(Miscellaneous messages)databasemonitor.querymonitor.notify3.Yate模块创建方法在实际应用中,可能需要编写自己的模块插入到系统中或者替换掉自己的模块,例如,使用商用的SIP协议栈替代Yate中开源的SIP协议栈,所以,编写在Yate框架下编写自己的模块是使用Yate平台必备的技能。Yate可分为两个部分 * Yate内核 * Yate模块 *Yate内核提供基础,辅助API以及消息系统 *Yate模块使用Yate内核实现特定的功能Yate模块的类型Yate模块可分为一下几种 1.通道 2.路由器 3.电话历史记录(Call Detail Recorder) 4.计费程序 5.其他模块如何指定模块的类型模块类型在这里涉及许多概念性的东西。Yate的设计中并不区分模块的种类,而是根据模块处理的消息类型来区分模块类型。例如一个通道模块接受call.execute消息,并创建一个通道来接受处理它。有此特征的模块我们称之通道模块。另一方面即使如果模块可能接受call.execute消息并处理一个事情,但并不创建一个通道/终端,则它不是一个通道模块。CDRBuilder就是这样的模块。如果你还不清楚,稍等,接下来的例子会说明清楚。程序员眼里的消息 消息应正确派发到注册了并在监听该消息的模块中。模块可以指定接受消息的优先级。如果一模块监听的call.execute消息优先级为50,其他模块也在监听call.execute消息,但优先级值大于50,则该模块应该先于其他模块获取call.execute消息。一旦接收到该消息,模块可向派发器返回true或fale,并附带一些额外信息。如果返回true,则派发器停止向后续的模块发送消息。返回false,则允许消息按照优先级继续派发到其他模块中。 Yate消息不会同Windows消息相混淆,因为他只在Yate系统范围内发送而没有使用操作系统机制发送消息。此外,Yate消息构造是以字符串定义的,而OS消息使用的是数值。TelEngine命名空间 所有的Yate API都什么在TelEngine命名空间中,命名空间是C+的一个概念,用于减少程序中变量名和函数名重名冲突。因此,要使用命名空间TelEngine,如不想写TelEngine:blahblah,你可以使用:1. usingnamespaceTelEngine;我们的第一个模块 我们现在开始写我们的第一个模块,可以接受call.execute消息,并将其呼叫和被叫的号码输出到控制台中。我们设模块名为mymodule1.ln。在这个模块中我们需要讨论以下几个类。 *Module *Message *MessageReceiver *String同样还得介绍call.routeCPP文件准备我们需在模块目录下创建一个名为mymodule1.cpp的文件包含telengine.h文件则可使用YATE的API第一步-模块代码和前面解释的一样,Yate模块要申明他是一个Yate模块都需从Module类派生。Module是从多个类派生的。在Yate API继承关系如下:1. classYATE_APIModule:publicPlugin,publicMutex,publicMessageReceiver,publicDebugEnabler2. .注意Plugin类向Yate提供了加载模块的功能在我们的模块中声明类如下:1. classMyModule:publicModule2. 3. public:4. MyModule()5. :Module(MyModule1,misc),m_init(false)6. virtualvoidinitialize();7. virtualboolreceived(Message&msg,intid);8. private:9. enum10. 11. CallRoute=Private+112. PrivateRelayID;13. boolm_init;14. ;第二步:创建Module类的静态变量当Yate模块加载一个模块是,他首先会在模块中(Plugin)查找名为_plugin的静态变量。在我们的例子中,我们需要定义类MyModule(派生于Plugin)的一个静态变量。Yate提供了帮助做这个,仅需要这样使用:1. INIT_PLUGIN(MyModule);第三步:模块initialize()的重要性你需特别注意到我们在MyModule声明定义的initialize方法。initialize函数在Plugin类是虚函数,在Yate加载模块时会被调用。MyModule需要重载它,做一些模块初始化工作。通常在这里注册模块想监听的消息。我们可这样写代码:1. voidMyModule:initialize()2. 3. Output(Initializingmodulemymodule1);4. if(!m_init)5. m_init=true;6. 7. Yate提供的Output函数输出信息到控制台。它只在必要的时候使用。Yate同样提供了一些其他API函数将调试信息输出到控制台。第四步:添加代码接受消息模块通常需要接受一个或多个消息。模块接受到一个消息并执行程序制度的工作。Module类派生与MessageReceiver,MessageReceiver通过虚函数received()提供了接受消息能力。我们可按自己爱好添加代码接受消息,像这样:1. voidMyModule:initialize()2. 3. Output(InitializingModulemymodule1);4. if(!m_init)5. Engine:install(newMessageRelay(call.route,this,CallRoute,55);6. m_init=true;7. 8. Yate中的类Engine。Yate创建了一个Engine的静态对象作为他(Yate)的启动(不是模块的启动)。这个类启动真个Yate的服务器/服务。这个类全是静态成员函数。Install()同样是静态函数,这也就是我们为什么Engine:install这样调用的原因。install向yate提供了我们的MessageRelay类对象,指定了我们想要监听消息的消息id、监听优先级等。第三个参数CallRoute是枚举值,它仅指定我们想关联消息的消息ID。枚举值CallRoute为Private+1,在类Module中定义,用于指定(说明)该消息在模块中没有任何RelayID。可看看Yate中Module类的枚举值。当你监听到多个消息,你需要识别监听到的消息,当然这有多种方式实现。注意在MessageRelay的最好一个参数值为55,这说明我们想监听的消息优先级别值为55。因此如果其他模块也在监听同样的消息且优先级更高,将先接收到这个消息并且如果他返回非ture,我们的这个模块才能接受到这个消息。MessageRelay被Yate用例发送消息,它的构造函数拥有一个MessageReceiver类的对象用户消息的通知。我们MyModule继承于Module,而Module继承于MessageReceiver,因此,我们在MyModule可提供这个对象(MessageReceiver)。第五步:重载received类MessageReceiver的received(Message &msg,int id)方法由Yate调用,用于派发注册过监听的消息。received在MessageReceiver中声明为虚函数。注意,返回值为bool,如果处理完这个消息,并不想后面的模块接受处理该消息,返回true。希望后续的模块继续处理该消息返回false即可。这里(received)我们可以添加自己的逻辑代码。在received中我们可写一些代码,在call.route消息来临时,输出呼叫者和被呼叫者名。1. boolMyModule:received(Message&msg,intid)2. 3. Stringcalled,caller;4. called=msg.getValue(called);5. caller=msg.getValue(caller);6. Output(mymodule1:call.routecalled7. caller%s,andcalled%s,caller.c_str(),called.c_str();8. returnfalse;9. 上述代码使用了Yate API提供的String类。从名可知这个类提供了字符串的相关操作。Message继承与NamedList,这是YATE提供的另一个类,用于更好管理字符串与字符串直接的映射链表。类NamedList的函数getValue(),我们先获取被叫号码(熟知的DNID),然后获取呼叫者的号码,作为电信运营商的ANI/CLI(被叫者ID/呼叫者ID)。c_str为String的成员函数,返回字符串存储的数据,类型为const char*.1. #include2. #include3. #include4. #include5. usingnamespaceTelEngine;6. classMyModule:publicModule7. 8. public:9. MyModule()10. :Module(MyModule1,misc),m_init(false)11. virtualvoidinitialize();12. virtualboolreceived(Message&msg,intid);13. private:14. enum15. 16. CallRoute=Private+117. PrivateRelayID;18. boolm_init;19. ;20. boolMyModule:received(Message&msg,intid)21. 22. Stringcalled,caller;23. called=msg.getValue(called);24. caller=msg.getValue(caller);25. Output(mymodule1:call.routecalled26. caller%s,andcalled%s,caller.c_str(),called.c_str();27. returnfalse;28. 29. voidMyModule:initialize()30. 31. Output(InitializingModulemymodule1);32. if(!m_init)33. Engine:install(newMessageRelay(call.route,this,CallRoute,55);34. m_init=true;35. 36. 37. INIT_PLUGIN(MyModule);38. /*vi:setts=8sw=4sts=4noet:*/YATE消息机制消息是YATE的一个主要组成部分。所有的模块间通信都使用消息而不使用函数调用,这主要是因为我们考虑当一个模块发生变化的时候不影响或不依赖于其他模块,也因为我们知道模块直接传递的参数,使得我们的调试模块可以更容易些。一个消息由以下几个部分组成: * 名字(name)消息类型的标识,允许消息处理器通过名字进行匹配 * 返回值(return value) 一个用字符串表示的处理消息之后的返回值 * 时间(time) 消息被创建的时间;这对于排队的消息检查等待时长非常重要 * 参数(parameters) 可能有多个或0个参数组成,每个参数由名称、值对构成。每个处理器都能根据参数进行不同的动作,或者修改参数本身。不知道的参数必须忽略。所有的消息在YATE内部是二进制形式的。然而我们可以通过rmanager模块提供一个对人可读的形式。 YATE内部消息传递通过内存共享(memory sharing)。在这种方式下,可以提高系统的性能。其他可以考虑的形式如管道或Sockets,没有内存共享灵活和高效。当被传递到外部模块(external modules)时,消息被转换成字符串编码的形式,这样所有能处理文本的外部模块都可以处理消息了。 可参考文档external module,获取更多详细信息。 消息由消息处理器处理,每个消息处理器仅处理与它名字相同的消息。他们可以任意修改消息元素(参数,返回值,甚至消息名称),一个消息处理器处理完之后,可由下一个消息处理器处理。 消息处理器处理消息的顺序,再插入派发器中就已经决定。处理顺序按照消息处理器的优先级决定,优先级值越低的预处理器优先级越高,先与优先级值高的接受到消息。 对于相同优先级的消息处理器,调用顺序是不确定的。调用顺序按以下的规则: *同名的消息调用顺序是不会改变的 *为了避免不确定性,如果消息处理器被移除,并插入一个同等优先级的消息处理器,则他们的顺序由她的的内存地址决定。 以下是“call.rotue”在消息系统重的工作过程例子 当某个电话打来时,消息是这样产生的:1. Message*m=newMessage(call.route);2. m-addParam(driver,iax);3. if(e-ies.calling_name)4. m-addParam(callername,e-ies.calling_name);5. else6. m-addParam(callername,e-session-callerid);7. if(e-ies.called_number)8. m-addParam(called,e-ies.called_number);9. else10. m-addParam(called,e-session-dnid);然后我们将消息发送给引擎,检查是否有模块(module)接收并处理了,最后必须将消息销毁。1. if(Engine:dispatch(m)2. Output(Routingreturned:%s,m-retValue().c_str();3. else4. Output(Nobodyroutedthecall!);5. m-destruct();引擎在得到上述消息,将其发送到所有注册名为“route”消息处理器模块中。对于fire-and-forget类型消息,他们先存储在引起的消息队列中并由引擎内部的线程池进行延后派发。一旦消息派发完成后则由引擎将其销毁。1. Message*m=newMessage(alert);2. m-addParam(reason,Hardwaremalfunction);3. Engine:enqueue(m);为了处理route消息请求(很有可能在另外一个模块中),我们首先声明一个从MessageHandler派生的类RouteHandler。1. classRouteHandler:publicMessageHandler2. 3. public:4. RouteHandler(intprio)5. :MessageHandler(call.route,prio)6. virtualboolreceived(Message&msg);7. ;然后,由于方法received在类MessageHandler中是纯虚函数,我们必须重载。1. boolRouteHandler:received(Message&msg)2. 3. constchar*driver=msg.getValue(driver)4. Debug(DebugInfo,Routefordriver%s,driver);5. /dontactuallyrouteanything,letmessagecontinue6. returnfalse;7. 最后,在插件的initialized方法中,安装此消息处理器1. voidRegfilePlugin:initialize()2. 3. m_route=newRouteHandler(priority);4. Engine:install(m_route);5. 如何编写Yate的编解码模块Yate的编解码模块不处理任何消息,而是通过API方式调用。其他模块通常两者机制都使用。实现一个编解码模块要求需要对编解码库和算法有相当深的理解与研究。在Yate中实现它是相当简单的。希望这个教程对你有帮助。在这个教程中我们将使用到以下的ate API 1. class TranslatorFactory 2. class DataTranslator 3. class DataBlock 4. class FormatRepository 5. struct FormatInfo 6. struct TranslatorCaps假定你已经看过我的第一个教程。这里我们iLBC编解码库为例子。我们使用iLBC 20ms的编解码器。本教程的编解码库和项目中的代码并不是100%的相同。所以如果你想使用iLBC,

温馨提示

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

评论

0/150

提交评论