Resiprocate介绍.doc_第1页
Resiprocate介绍.doc_第2页
Resiprocate介绍.doc_第3页
Resiprocate介绍.doc_第4页
Resiprocate介绍.doc_第5页
已阅读5页,还剩43页未读 继续免费阅读

下载本文档

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

文档简介

Resiprocate介绍1.前言 本文主要内容来自互联网,特此感谢Steven的辛苦撰写和resiprocate开源组织的无私奉献以及sip协议的创造者Schulzrinne教授和Rosenberg大师的辛勤工作。2从SIP谈起 说明:不期待一次就把RFC3261或者其他的协议文档内容及其细节全部记住或者完全理解;把原理性的东西及其脉络厘清也许更重要;在调试程序和看协议栈源码的过程中我的做法是一直把RFC3261(经常的是那份中文文档J的文档打开;遇到忘记或者不是太明白的概念和内容就在文档中再搜索相关主题及内容来看看;经常会碰到这样的问题,我发个内容给SIP Proxy或者SIP Server,可是并没得到我希望的回复或者与期待的回复内容有出入,这时,我的经常做法是再去研读协议的相关定义,看看是不是我哪个细节并没理解深入或者引起注意,导致我发出去的内容与协议标准有出入或者我的流程与协议定义不吻合。接下来的内容是前人的文档整理,只是个大概,如果没兴趣,完全可以跳过不看;协议栈部分基本上是分成DUM与Stack两部份可以先后看,也可以先看Stack部分。补充说明:文档中的大部分图片都来自网上公开的资料,只有少数几幅是自绘,因此出现内容不清和误导,概不负责J 特此感谢借鉴资料和图片的原创者们,虽然他们并不知道又误导了一个菜鸟。2.1 SIP (Session Initiation Protocol) 简介最先由美国哥伦比亚大学的Henning Schulzrinne 教授在1998 年初开始发起,1999 年3 月由IETF 的MMUSIC(Multipart MultimediaSession Control)工作小组制定正式标准成为RFC 2543, 1999 年9月IETF 成立新的工作小组 ,负责SIP新版本2.0 的制定 , 并于2000年7 月释出初版RFC 2543bis,于2001 年发布了RFC 3261 。RFC 3261 的发布,标示着SIP 的基础已经确立,随后又发布了几个RFC 增定版本,充实了安全性及身份认证等几个领域的内容,例如RFC 3262对临时响应做了可靠性的规范。RFC 3263确立了SIP proxy的定位规则。RFC 3264提供了Offer/Answer Model,RFC3265则是确立了具体的事件通知。如同Internet 一样,SIP 易于理解、扩充、及实做,作为IETF 的规范,SIP 将Internet 开放标准的精神延伸至通讯领域,实现了不同计算机、电话、及软件的通讯。SIP 的讯息类似于HTTP (RFC 2068),其寻址方式,则是重用了SMTP 的寻址方式,SIP address (如: sip:.tw)与E-mail address 的结构相同,SIP 甚至利用Web 的体系结构,如DNS,而使得SIP 的使用者之间的通讯,有更高的扩充性。 SIP有下列几点特性 利用文字(Text-based)的方式来编码,类似HTTP/1.1 Client-Server的架构 Clients 端初始一个呼叫(caller) Servers 端响应呼叫 (callee) Signal与media独立,SIP负责signal部分,media传送部分可以使用RTP等,可与其它IETF 所制订的协议配合,例如:RFC2327(SDP), RFC2616(HTTP/1.1) ,RFC2396(URL)2.2 SIP命名方式SIP 的地址称做SIP URLs,其格式为 userhost:port ,使用者利用REGISTER的SIP request来结合自己的SIP URLs user 代表使用者名称或是电话号码 host 代表 domain name 或是数字型态的IP address举例来说 sip: 或是sip:2564358852.3 SIP 组件介绍在SIP 是一个Client and Server的架构,在此环境当中,有三个主要的组件分别为: User Agents, Servers 还有Location Servers。 User Agents在SIP 环境中是终端设备,主要负责产生SIP requests,用来建立多媒体会议(media session),并且传送及接收多媒体资料。UserAgents 又分成了User Agent Client (UAC) 及 User Agent Server 两种模式。UAC 负责产生一个Request 及处理一个Response,UAS 怎是接受一个Request 并且产生response。在Session建立过程中,UA通常需要接替着扮演这两个角色。这点并不像其它Client and Server 架构,例如HTTP,PC 一直扮演着HTTP client 的角色,而Web Server也一直扮演着HTTP Server 的角色。 Servers根据RFC 3261 中定义,Server主要分成了Proxy, Redirect,以及Registrar server。l SIP proxy:负责接受UA 或其它proxy 所发送的 SIPRequest,并且转 送Request 到其它地方。l Redirect Server:负责接受UA或其它proxy所发送的 SIPRequest,并且传回redirection response (3xx),指出这个Request应该送往何方。l Registrar Server:负责接受SIP registration requests,并且更新SIP UA在Location Server 或其它数据库当中的信息。SIP proxy, Redirect 还有Registrar servers 只有做单纯的signal转送,他们没有传送media及产生SIP Request 的能力。Proxy, Redirect,以及Registrar server只是逻辑概念定义的不同而物理实现上完全可以在同一物理位置实现。l Location Servers:在RFC 3261 中,通常当作一个数据库来使用。数据库当中可以存放使用者的信息,例如URLs, IP address, 或是其它资料等等。SIPUA 不能直接来存取Location server,而是透过proxy, redirect,或是registrar server。2.4 SIP messageSIP message 的语意及表头与HTTP/1.1(RFC 2616)相同。可以分成两类,一类是Request ,另外一类是Response。在RFC 3261 当中,定义了六个基本的SIP request 种类,如表2.1所示。方法说明INVITE建立会话SessionACK对INVITE 做最后的确认BYE结束一个已经存在的会话CANCEL取消尚未建立联机的会话REGISTER注册使用者的URLOPTIONS查询Server 及其功能表2.1、SIP methodsResponse 用status-codes 来表示响应的内容,符合且扩展了HTTP/1.1 response code。分成Provisional(暂时)及Final(最终)两类,Provisional 为1xx系列,Final 则包括了2xx,3xx,4xx,5xx,6xx等系列,表2.2 表示各个不同类别的Response。方法说明1xxInformational2xxSuccessful3xxRedirection4xxClient-Error5xxServer-Error6xxGlobal-Failure表2.2、SIP Response1xx Informational responses 指的是server 或是proxy正在执行一些未来的动作,并还没有一个定义好的response。2xx代表这个request是成功的,并且必须结束一个搜寻的动作。3xx Redirection 会回复欲通讯的使用者目前的位置信息。4xx response 是Server 对于UAC 所提出的request 回复一个失败的response。5xx response 是代表Server本身发生了错误。Global Failures 6xx指的是server 有一个关于特定的使用者最终的信息,不仅仅是这个特定的Request-URI,所有未来的对这个使用者的搜寻都应该被终止。表2.3 为几个常用的Response code 范例 Response codeReason Phrase100Trying200OK302Moved temporarily403Forbidden503Service Unavailable600Busy表2.3、SIP Response code2.5 SDPSIP message body当中,可以携带任何的资料,但通常是给通讯双方用来协商session 相关的信息。SIP 本身并没有提供多媒体协商的能力,多媒体协商必须仰赖 Session Description Protocol (SDP),SDP 本身并不是一个通讯协议,它的描述语言是基于文字的。SIP 利用answer/offer 的模式来使用SDP。呼叫者送出一个INVITE 讯息并携带着SDP,其中SDP 包含了呼叫者想使用的多媒体的格式、地址、Port,被呼叫方在响应的时候,便可以针对呼叫者所提出的SDP,做出接受或拒绝的响应,这种双方协商的结果,就可以得知多媒体资料格式及通讯的地址为何。SDP 定义在RFC 2327当中,后来经过修订及汇集draft 之后,发表了RFC 3264 “An Offer/Answer Model with SDP”,RFC 3264 明确的描述应该如何将SIP 与SDP 一起使用。SDP 简单地提供一个描述session 信息的格式。基本而言,一个session 是由数个media stream所组成,因此,要描述一个session必须要有数个相关的参数。SDP 当中有session-level与media-level 的参数, session-level的参数包含了 session的名称、 session的发起者、 session的期限等等。 Media-level 的信息包含了 media 的种类, port number、 传输协议以及media 的格式。图2.1 为SDP 的结构。Session DescriptionSession LevelProtocol VersionOriginator and Session IDSession NameSession TimeMedia Name and TransportConnection InformationMedia Description图 2.1 SDP session Description Structure2.6 SIP运作模式SIP 的呼叫建立如图2.2 所示,开始的时候caller 会送出SIP INVITE的讯息给callee,callee收到之后,会马上响应一个100 Ringing的讯息通知caller,说明目前callee 已经收到INVITE 讯息且正在处理中,如果callee 愿意与caller 通话,便会送出200 OK的response,最后caller 再送出ACK,此时便可以开始进行会议。当其中一方想结束通讯时,便会送出BYE 的讯息来通知对方,对方响应200 OK 便可以结束目前进行会议。Client (Caller/UA) Server(Callee/UAS)INVITE100:Trying180:Ringing200:OKACKBye200:OKRTP图2.2 SIP call flow2.6.1 SIP proxy 模式以图2.3 为例,caller () 先送出一个INVITE 讯息呼叫callee (),proxy server 收到之后便会去做查询, 查询完成之后便得知目前callee 实际的地址在,于是proxy server 便会以为对象发出INVITE 讯息。callee 在回复200 OK 的时候,会将200 OK 的response 响应给proxy server,再由proxy server 转送给1.INVITE 4. ResponseSIP Proxy2. INVITE3. Reponse2.6.2 SIP Redirect 模式如图2.4 所示,与proxy server 不同的地方,在于redirect server查询得知callee 实际的地址的时候,并不像proxy server 会直接代为处理之后session的建立,而是将callee的实际地址告知caller,让caller自行送出新的INVITE。使用redirect 的模式,可以减低server 的负担,但caller 必须有能力将request 的讯息传送到1.INVITE 2. Move temporally Contact: SIP Proxy/Redirect Server3. INVITE4. Reponse 3. 关于协议栈的系统设计SIP为应用层(Application-Layer)的协议,所以不需要改变操作系统便可以支持,以SIP 来支持行动通讯,对于real-time 的服务可以提升其效能。SIP 已经获得3GPP (Third GenerationPartnership Project)、3GPP2 (Third Generation Partnership ProjectNumber 2)等机构认证,成为未来第三代行动通讯 (3G) 的标准。事务用户层(Transaction User)事务层(Transaction )传输层(Transport)语法与编码层(Syntax and Encoding)下面是SIP的分层图示,IETF坚持分层,不同模块功能相对独立,各层之间松散耦合。以下是Sip客户、服务端数据传输示意图:Sip Server/ProxySIP ClientSIP ClientSIP/PSTN G/WIPGateway/Firewall3.1 Resiprocate SIP Stack解析之基础类 最期望的还是能把Resiprocate的源码脉络能理的清楚,大家能看个明白;并且还想搞清楚它为什么要这样设计,它设计的理念和考虑的关键是些什么。接下来的介绍其中有个人对源码设计的粗浅认识,其间会夹杂作者对面向对象设计的胡乱发挥。 首先还是要祭出这面大旗,”类是对概念的描述,面向接口编程;封装变化。” Resiprocate中大部分类就是对RFC3261各种SIP元素、组件的封装,并且也体现了RFC协议设计的层次。 在面向对象的设计中我们首先就要厘清问题域的所在;SIP Stack的设计就是要充分考虑完整展现RFC定义的各种元素和概念以及让这些独立而又关联的元素互动起来成为一个活的系统。 可以这样来考虑,比如我们知道RFC定义了一个SIP MESSAGE的概念;下面是从RFC文档拷贝的内容:SIP 消息 = 起始行 *消息头部 CRLF(空行) 消息体因此SIP Message这个概念元素还包括了更多的元素和概念;SIP Message中我们能抽象出更通用的概念我们暂且叫它Message; 起始行的概念E文RequestLine以及Sattus Line又包括了很多消息头(这是包容的关系),SIPURL也包括消息头,等等,还有什么参数什么的元素呢;当我们在考虑和提炼这些概念和元素的时候,我们思考怎么抽象他们呢,它们又有什么基本的元素及其共性呢?他们之间的关系如何组织呢?Resiprocate的源码告诉了我们如何去设计和封装这些概念的上佳实现。在Resiprocate中一些RFC3261中定义元素的对应:建议:利用CRC卡片的方式去记录理解Resiprocate中的大量的类及其关系。CRC:类、关系、控制。3.1.1 DUM的设计浅析:抽象接口:CLASS HANDLED ,CLASS InviteSessionHandler(诸如此类) 对象之源:CLASS HANDLED(多态和控制的基础)交互控制: CLASS Handle,CLASS HandleManager概念封装成类典型:CLASS Dialog, CLASS DialogId, CLASS DialogSet, CLASS DialogSetId, CLASS InviteSession.Utility工具类:CLASS BaseCreator , CLASS DestroyUsage, CLASS Profile流动之源:DialogUsageManager:process(),Dialog:dispatch(const SipMessage& msg)状态机的位置:DialogUsageManager:incomingProcess,DialogSet:dispatch,Dialog:dispatch这幅图期待加深对DISPATCH的理解:3.1.2 DUM部分设计的理解:观察者模式,工厂模式(大量容器的使用也是一种变体如:DialogSet),代理类句柄类(隐藏实现),句柄和代理的一个特点就是重载了operator-、operator*等。 T* operator-() return get(); const T* operator-() const return get(); T& operator- () return *get(); const T& operator*() const return *get(); Handled:Handled(HandleManager& ham) : mHam(ham), mId(Handled:npos) mId = mHam.create(this); StackLog ( & Handled:Handled mId this( this ) &ham );Handled:IdHandleManager:create(Handled* handled) mHandleMap+mLastId = handled;/ typedef HashMap HandleMap; /HandleMap mHandleMap; return mLastId;其他:Transaction(TransactionState之类),下面这幅图不完全针对Resiprocate,只是觉得它的层次可以帮助理解:这幅图的表达有借鉴理解的价值:SIP UA Finite State Machine 最完整的体现在:voidTransactionState:process(TransactionController& controller)3.2 Resiprocate SIP Stack解析之系统架构3.3 Resiprocate SIP Stack解析之UsaerAgent实例利用Resiprocate实现UA需要实现一个Sip客户服务端代理实现类来和UI等交互及完成事件回调控制。要处理sip的一系列事件,必须继承相关事件句柄控制接口,它们都是一些抽象类,如:InviteSessionHandler 会话的事件接口ClientRegistrationHandler 注册登陆事件接口。继承这些接口,说明客户端将要响应相关事件,一般而言肯定要组册和会话,因此上面的两个事件接口是一定要继承和实现的。因此一般来说sip客户代理类要管理和实现事件抽象接口。见相关例子。DialogUsageManager是sip事务层管理类,是一个大总管的角色;看其Makexxx系列的函数能明白它能发起一系列登陆、会话邀请的动作及其回复。因此这样的Sample就完成了一个会话邀请:NameAddr uacAor;被邀请方的Sip Uri地址uacAor.uri().scheme() = sip;uacAor.uri().user() = Data(“705147”); sip的注册帐号uacAor.uri().host() = Data(“”);注册服务器的URL或者IP地址/接下来就是一通初始化工作,应该能看明白/set up UAC SipStack stackUac; DialogUsageManager* dumUac = new DialogUsageManager(stackUac); dumUac-addTransport(UDP, 12005); SharedPtr uacMasterProfile(new MasterProfile); auto_ptr uacAuth(new ClientAuthManager); dumUac-setMasterProfile(uacMasterProfile); dumUac-setClientAuthManager(uacAuth); TestUac uac; dumUac-setInviteSessionHandler(&uac); dumUac-setClientRegistrationHandler(&uac); dumUac-addOutOfDialogHandler(OPTIONS, &uac); auto_ptr uac_dsf(new testAppDialogSetFactory); dumUac-setAppDialogSetFactory(uac_dsf); /your aor, credentials, etc here dumUac-getMasterProfile()-setDigestCredential(uacAor.uri().host(), uacAor.uri().user(), uacPasswd); dumUac-getMasterProfile()-setDefaultFrom(uacAor); dumUac-getMasterProfile()-setDefaultRegistrationTime(70);dumUac-send(dumUac-makeInviteSession(uasAor, uac.sdp, new testAppDialogSet(*dumUac, UAC(INVITE); /如此就生成了SIP会话邀请的消息发了出去。 参考代码:BasicCall.cxx/类似的登录注册更简单:SipMessage& regMessage = dumUac-makeRegistration(uacAor, new testAppDialogSet(*dumUac, UAS(Registration);dumUac-send(regMessage);然后就交给这样的一个后台处理流程去处理吧:unsigned long _stdcall UacThreadFunction(void* arg) UaAgent* uac = (UaAgent*)arg;resip:DialogUsageManager* dumUac = uac-mDum;resip:SipStack* m_hSipStack = uac-mStack;for(;) FdSet fdset;m_hSipStack-buildFdSet(fdset);int err = fdset.selectMilliSeconds(resipMin(int)m_hSipStack-getTimeTillNextProcessMS(),10);ASSERT( err != -1 );m_hSipStack-process(fdset);while(dumUac-process();return 0;在Dum(DialogUsageManager)的类中基本上这样一条线:DialogUsageManager产生Dialog Set,Dialog Set产生Dialog,Dialog产生InviteSession; InviteSession又分Client InviteSession和Server InviteSession。Dum部门还定义了很多句柄管理类,通过它我们能得到真实的对象,从而完成操作,这在事件响应中很有用,如:virtual void onNewSession(ServerInviteSessionHandle sis, InviteSession:OfferAnswerType oat, const SipMessage& msg)sis-provisional(180);/ sis是ServerInviteSession的句柄实例,通过它我们得到ServerInviteSession对象,从而完成相关动作。 /。 3.4 Resiprocate SIP Stack解析之消息流程 不管是SendMessage或者对RecvMessage都是把它首先放进先进先出的一个队列。给张图展示下先:看看Transport(Transport.hxx)类中的成员变量就是它们的归属: Fifo mTxFifo; / owned by the transport Fifo& mStateMachineFifo; / passed in在整个Resiprocate大家族中事务层概念的体现是TransactionUser类,而其真正的实例和管理类就是DialogUsageManager;从其:class DialogUsageManager : public HandleManager, public TransactionUser能看出来;HandleManager点出了DialogUsageManager的管理功能的本质,并且管理各种对象(Handle就是各类对象的句柄,关于后面会提到)。首先要明白在整个Resiprocate系统中不管是我们发出或者收到的SIP Message都是放进了先进先出的队列然后不断轮询处理,这有点点点象Windows的消息系统,对应收发的消息Resiprocate提供事件通知的机制。Message FiFo的大致流动走向如此: Outgoing messages 的源码流动走向大致如下,以下是INVITE MESSAGE产生及其流动示例:DialogUsageManager: makeInviteSession - DialogUsageManager:send DialogUsageManager:sendUsingOutboundIfAppropriate SipStack:sendTo - TransactionController:send( 在此处被添加进了FiFo - mStateMacFifo.add ) 这之后就是等待被处理( TransactionController:process )/ new sip msg from the TU/注解一下两个函数名sendToWire 这是往外发送,发到线上去就是往网线上朝外发/啦,sendToTU就是往TransactionUser送往事务层发啦一般是接收到的SipMessgae/从Transport往上递交。以发起一新的INVITE所进行的后台Process流程为例:voidTransactionState:process( TransactionController& controller ) voidTransactionState:processClientInvite( TransactionMessage* msg ) voidTransactionState:sendToWire(TransactionMessage* msg, bool resend ) 下面摘录一下TransactionState:sendToWire(TransactionMessage* msg, bool resend)中的部分源码注释一下: mDnsResult = mController.mTransportSelector.dnsResolve(sip, this); /这一部/分就是整个源码中Ares上场表演的时候了,这部分是DNS处理的实现,在我的移植/中我把这部分简单改写成了调用windows的dns函数如gethostbyname/gethostname/系列。handle(mDnsResult);void TransportSelector:transmit(SipMessage* msg, Tuple& target) 再给段transmit中源码: Data& encoded = msg-getEncoded(); encoded.clear(); DataStream encodeStream(encoded); msg-encode(encodeStream); encodeStream.flush(); target.transport-send(target, encoded, msg-getTransactionId();void Transport:send( const Tuple& dest, const Data& d, const Data& tid)void InternalTransport:transmit(const Tuple& dest, const Data& pdata, const Data& tid) SendData* data = new SendData(dest, pdata, tid); mTxFifo.add(data);最后到这儿就出去了(以UdpTransport为例):void UdpTransport:process(FdSet& fdset)/只列举关键代码 if (mTxFifo.messageAvailable() & fdset.readyToWrite(mFd) std:auto_ptr sendData = std:auto_ptr(mTxFifo.getNext(); const sockaddr& addr = sendData-destination.getSockaddr(); int count = sendto(mFd, sendData-data.data(), sendData-data.size(), 0, &addr, sendData-destination.length();流程图:Incoming messages 的源码流动就简单一些,收的处理是被动的,因此是在Process中后台接收然后上传:看看UdpTransport:process最后把接收到的Buffer组织成SipMessage后放到了哪里: stampReceived(message); mStateMachineFifo.add(message);还是放到了Fifo& mStateMachineFifo; / passed in这个先进先出队列中。我们看看接下来的几个函数就可以看到各个类里面的先进先出对列是如何关联起来的,也能明白上面的接收队列后来会在哪些地方会被处理到:TransactionController:TransactionController(SipStack& stack, bool stateless) : mStack(stack), mStateless(stateless), mRegisteredForTransactionTermination(false), mDiscardStrayResponses(true), mStateMacFifo(), /这个地方 mTuSelector(stack.mTuSelector), mTransportSelector(mStateMacFifo),/TransportSelector中的mStateMacFifo这/样就和TransactionController中的mStateMacFifo代表了同一个对象 mStatelessHandler(*this), StatelessIdCounter(1), mShuttingDown(false)再看看这儿(此处代码只是节选没有完全列出):voidSipStack:addTransport( TransportType protocol, int port, IpVersion version, const Data& ipInterface, const Data& sipDomainname, const Data& privateKeyPassPhrase, TransportProcessApproach threadApproach) InternalTransport* transport=0; Fifo& stateMacFifo = mTransactionController.transportSelector().stateMacFifo(); /看看,都是引用/类型,导致它们都指向了同一个先进先出的队列。 try switch (protocol) case UDP: transport = new UdpTransport(stateMacFifo, port, version, ipInterface);/注意传引用的stateMacFifo,导致TransactionController: /stateMacFifo其实和UdpTransport: stateMacFifo指向同一处。 break; case TCP: transport = new TcpTransport(stateMacFifo, port, version, ipInterface); break; default: assert(0); break; 上面所列举的源码是为了引出下面这一段,这儿是接收和发送SIP Message的后台处理的一一个Process;搜搜整个源码有好几个Process,正是这一串Process让各种SIP Message在各层和各种类型实例间流动了起来:voidTransactionController:process(FdSet& fdset) mTransportScess(fdset); while (mStateMacFifo.messageAvailable() /从这儿就能看到收发SipMessage/统一会在这儿处理。 if (mStateless) mStatelessHcess(); else /Uagent一般就在这儿处理啦 TransactionState:process(*this); voidTransactionState:process(TransactionController& controller) TransactionMessage* message = controller.mStateMacFifo.getNext();以上种种TransactionController是个事务控制类,它在SipMessage的流动中起到了一个中间桥梁的作用。接下来再看看接收到的SipMessage如何一步步上传到事务层/用户层。voidTransactionState:process(TransactionController& controller) voidTransactionState:sendToTU(TransactionUser* tu, TransactionController& controller, TransactionMessage* msg) 这段代码比较少,放上来:voidTransactionState:sendToTU(TransactionUser* tu, TransactionController& controller, TransactionMessage* msg) msg-setTransactionUser(tu); controller.mTuSelector.add(msg, TimeLimitFifo:InternalElement);接下来的处理:voidTuSelector:add(Message* msg, TimeLimitFifo:DepthUsage usage) /省去旁支末节代码后 msg-getTransactionUser()-postToTransactionUser(msg, usage);void TransactionUser:postToTransact

温馨提示

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

评论

0/150

提交评论