第八章 Socket程序设计_第1页
第八章 Socket程序设计_第2页
第八章 Socket程序设计_第3页
第八章 Socket程序设计_第4页
第八章 Socket程序设计_第5页
已阅读5页,还剩59页未读 继续免费阅读

下载本文档

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

文档简介

1、第八章 Socket程序设计,1 基本概念,网间进程通信 网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。 其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。,1 基本概念,端口端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都

2、被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问。,1 基本概念,端口 端口号的分配有两种基本分配方式:全局分配;本地分配 TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。,1 基本概念,地址 网络通信中通信的两个

3、进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:1. 某一主机可与多个网络相连,必须指定一特定网络地址;2. 网络上每一台主机应有其唯一的地址;3. 每一主机上的每一进程应有在该主机上的唯一标识符。 通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。,基本概念,网络字节顺序 不同的计算机存放多字节值的顺序不同,因此为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式。 连接 两

4、个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。,基本概念,半相关 网络中用一个三元组可以在全局唯一标志一个进程: (协议,本地地址,本地端口号) 这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。 全相关 一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。因此一个完整的网间通信需要一个五元组来标识: (协议,本地地址,本地端口号,远地地址,远地端口号) 这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。,

5、Socket 概述,网络协议规定了两台计算机之间进行数据交换的共同规则,包括交换数据的格式和动作序列。 UNIX中传输层和传输层以下的协议在操作系统内核中实现,那么就必须规定一种应用程序使用内核的这些网络功能的方法。 UNIX访问网络也用文件描述符引用一个特殊文件的方法。 目前,应用程序与网络之间接口有socket和TLI(Tansport Layer Interface),8,TCP和UDP,TCP/IP对应用程序提供的服务主要有两种:一种是面向连接的可靠的数据流传输TCP,另一种是无连接不可靠数据报传输UDP。 应用程序员在使用TCP/IP网络编写通信程序之前,应当首先在TCP和UDP协议

6、之间作出选择,它们决定了由系统提供的通信可靠性。,9,TCP和UDP,TCP提供了完全可靠的通信服务,它能够自动地重传;计算校验和以保证数据的正确性;TCP协议保证数据在接收端按在发送端发送的次序接收,不会出现后发送的数据先到达的情况;能自动地扔掉那些重复传输的数据;它提供了流量控制机制,保证发送者发送的数据不要太快以至于接收者来不及处理它们,甚至还考虑了不以过快的速度发送数据以防整个网络拥堵。当由于某种原因TCP通信无法进行时,会通过一定的手段通知应用程序。 使用UDP,所有上述TCP的可靠机制都没有。由应用程序想办法解决可靠性问题,这些问题包括:错报、重报、丢报、乱序和流量控制。,10,T

7、CP和UDP,选择UDP协议的程序,应当进行周密的测试。否则,一个在时延短误码率低的本地局域网上调试好的程序在时延长差错率高,或者时延和误码率经常变化的广域网上可能会出现问题。或者,在数据量较小时,运行正常,但数据量增大时运行不稳定。 选择UDP协议通信的程序,必须利用一些技术,以保证数据的可靠传输,或者是高层的软件在UDP不可靠的服务面前也能够正常工作。相对来说,要比直接使用TCP难度更大。使用UDP协议的典型应用有域名服务DNS和简单文件传送协议TFTP,简单网管协议SNMP,以及路由信息协议RIP。需要使用广播(broadcast)或者组播(multicast)功能时,只能选择UDP。,

8、11,TCP通信,socket给出的编程接口,无论使用TCP还是UDP协议,都是一种client/server风格的软件结构。 事实上,UNIX设计的socket机制,不仅仅是面向TCP/IP通信协议的,而是面向所有的网络通信,包括其他的通信协议栈。 client/server结构的协议软件包括客户端软件和服务器端软件。,12,TCP通信,文件传送协议FTP为例,UNIX提供的ftp命令就是客户端软件,在提供文件传送服务的远程计算机上,运行服务器端软件。 这些服务器端的软件,在UNIX中由守护进程inetd控制,当TCP连接到达时,inetd创建ftpd服务进程负责与客户端软件的ftp通信,以

9、完成文件传送操作,文件传送结束后,ftpd进程结束。,13,TCP通信,为了进行类比,先看一下文件操作的模式。访问文件有一组函数,而且这些函数调用的先后顺序也有一定的规则,先open()得到文件描述符fd,然后可以执行read()和write()访问文件内容,另有一些可以施加在fd上的函数,例如:lseek()定位文件指针,fstat()获得文件的状态等等。 使用管道时,就不再用open()获得文件描述符,而是用pipe()一次获得两个文件描述符。,14,TCP通信,使用socket的情况类似,先用socket()创建一个文件描述符,在这个文件描述符上,先施行一些connect(),bind(

10、),listen()等操作控制建立TCP连接,然后才能使用read()和write()收发数据。 通信过程中,可以使用fcntl(),setsockopt()和getpeername()等函数,获得一些通信的状态,或者设置一些与通信有关的参数。 最终,用close()关闭连接。这些函数调用的先后顺序,也遵循一定的规则。,15,TCP通信,TCP客户端程序 客户端程序首先和远程的服务器端程序建立连接,然后,从标准输入读取一个字符串,将该字符串发送到服务器。 client.c基本上分几部分:创建socket文件描述符,建立连接,发送数据,最后关闭文件描述符,也就关闭了网络连接。 connect调用

11、,建立一个TCP连接。执行connect调用,内核的TCP协议软件开始3次握手,并等待对方计算机的应答。,16,TCP通信,TCP服务器程序 服务器端程序也一样需要用socket()调用创建一个文件描述符,然后,执行bind()调用。 执行了socket()调用之后,内核中为这一文件描述符记录的本地端点名和远地端点名都为空,IP地址和端口号都是0。服务器端的程序,必须把本地端点设置为一个约定好的TCP端口。 系统调用bind()完成这项任务,bind调用的目的是指定本地的一个“端点名”。这里的程序本地端点名中的端口号,使用常数PORT_NO,需要事先转换为网络字节顺序。,17,TCP通信,TC

12、P服务器程序 listen()调用是通知内核,以后凡是到达本地主机的TCP连接,如果端点名和这个文件描述符的本地端点名相同,就转交到这个文件描述符上。 一般面向连接的通信的服务器端程序需要listen(),而且listen()调用安排在程序的bind()调用之后。 accept()在文件描述符admin_sock上等待一个远程主机发送来的连接请求,,18,TCP通信,socket()系统调用创建一个socket,19,#include #include int socket (int domain, int type, int protocol); 成功返回socket文件描述符; 出错返回-

13、1,TCP通信,socket()系统调用创建一个socket,20,TCP通信,socket()系统调用创建一个socket protocol参数通常情况下为0,用来表明默认的domain和socket类型。 SOCK_STREAM这种socket类型的话,默认为AF_INET的IPPROTO_TCP ; SOCK_DGRAM这种socket类型的话,默认为AF_INET的IPPROTO_UDP ;,21,TCP通信,connet()系统调用建立连接,22,#include #include int connect (int sockfd, const struct sockaddr *add

14、r, socklen_t len); 成功返回0; 出错返回-1,connect()的第一个参数决定了协议类型,有了“端点名”,connect()才知道连接的对方是谁。为了描述一个连接的“端点名”,不同协议栈需要不同的描述方法。,5 命名套接字,示例:,#include void make_socket( unsigned short int port ) int i,sock; struct sockaddr_in address; sock = socket( AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET; address.s

15、in_port = htons( port ); address.sin_addr.s_addr = htonl( INADDR_ANY ); i = bind( sock, (struct sockaddr*) ,TCP通信,connet()系统调用建立连接,24,struct sockaddr short sin_family; /* AF_INET */ u_short sin_port; /* port number */ struct in_addr sin_addr; /* IP address */ char sin_zero8; /* unused */ ; struct in

16、_addr u_long s_addr; ;,sin_family必须为AF_INET,sin_port是TCP端口号,sin_addr是IP地址。这两个参数都需要按网络字节顺序存放,所以分别使用了htonl和htons。,TCP通信,connet()系统调用建立连接,25,对于TCP来说,指定了通信的对端,在本地和远地主机之间建立一个连接。所连接对端的端点名放在name缓冲区中,namelen为存放端点名的缓冲区的有效数据长度。这个系统调用可能会导致进程睡眠等待连接建立成功。 对于UDP来说,connect并不和远程主机之间建立任何连接,而是仅仅将通信对端的“端点名”记录到内核的sockfd

17、对应的数据结构中。当然,这也不会导致进程睡眠。随后的write(sockfd,.)将使用这个sockfd的对端的端点名记录的IP地址和端口号,作为每个UDP报的目的地址。,TCP通信,bind()系统调用指定本地端口,26,#include #include int bind (int sockfd, const struct sockaddr *addr, socklen_t len); 成功返回0; 出错返回-1,bind用于指定通信的本地“端点名”。在服务器端的程序中,这个调用是必需的,指定一个约定的端口号作为本地的端口号。随后的listen才使得内核的TCP协议模块了解哪些到达的连接该

18、转交到这个sockfd。,TCP通信,bind()系统调用指定本地端口 bind()调用的目的是为文件描述符选定一个本地端点名,这个选定的端点名被内核记录下来。 bind()函数并不是服务器端程序专用的。在客户端的程序中,包括TCP和 UDP,可以不指定本地端点名,执行connect调用,或者对UDP来说发送数据时,如果本地端点名还是空,那么系统会自动为本地端点分配一个端点名,包括IP地址和端点号。 客户端程序在connect()前也可以用bind()强制选择本地端点名,即本地IP地址和端口号。本地主机有多个IP地址时,bind()可以为本地端点强制选定其中的一个地址,或者强制选定一个本地端口

19、号。,27,5 命名套接字,示例:,#include void make_socket( unsigned short int port ) int i,sock; struct sockaddr_in address; sock = socket( AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET; address.sin_port = htons( port ); address.sin_addr.s_addr = htonl( INADDR_ANY ); i = bind( sock, (struct sockaddr*) ,T

20、CP通信,listen()监听到达的连接请求,29,#include #include int listen (int sockfd, int back_log); 成功返回0; 出错返回-1,TCP通信,listen()监听到达的连接请求 一个已经执行过bind() 指定了本地“端点名”的文件描述符,执行了listen()后才开始监听有没有连接请求到达。 listen()只用在面向连接的网络协议对应的文件描述符。backlog指明最多可以排队多少个连接请求。 每次accept()就从这个队列里取走一个连接请求,创建新的文件描述符。 在accept()之后,下次accept()之前的时间段到达

21、的连接请求就排队等候,backlog指定允许的最大排队数。此参数常指定为5,是目前允许的最大值。,30,TCP通信,accept()系统调用指定本地端口,31,#include #include int accpet (int sockfd, const struct sockaddr *addr, socklen_t len); 成功返回文件描述符; 出错返回-1,在一个面向连接的服务器中,先用socket创建一个套接字,再用bind指定一个本地“端点名”,然后执行listen后就开始监听。,TCP通信,accept()系统调用指定本地端口 accept()就是取得排队在套接字上的队列的第一

22、个连接请求,并建立起一个新的文件描述符,函数返回值就是这个文件描述符。 如果addr和len不为0,对端的端点名返回在addr中。len用于传入存放端点名的缓冲区addr的大小,函数调用返回时,len存放已记录到addr中的远端主机的端点名的有效长度。前面的程序例子中给出了这后两个参数的用法。 如果accept()执行时,sockfd上尚没有连接请求到达,那么accept()调用被阻塞,进程处于睡眠状态,等待连接请求的到达。这一系统调用可以被信号打断。,32,3 套接字地址,(a) 主机的IP地址,/ IP地址的内部表示: #include struct in_addr in_addr_t s

23、_addr; ,32位无符号整数,3 套接字地址,(a) 主机的IP地址,字符串IP和32位IP地址转换函数,int inet_aton (char *name, struct in_addr *addr);,char* inet_ntoa (struct in_addr in);,函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式.,3 套接字地址,(b) 主机名,为了方便记忆,计算机都有一个主机名,这样就不用记复杂的数点形式的IP地址,在目前所有的操作系统中都

24、有一个hosts文件,用于记录主机名和数点形式的IP地址的映射。,3 套接字地址,UNIX系统为处理主机信息,定义了 hostent 结构描述主机名相关信息,include struct hostent char * h_name; /* 主机的正式名称*/ char * h_aliases; /* 主机的别名 */ int h_addrtype; /* 主机的地址类型AF_INET*/ int h_length; /* 主机的地址长度对于IP4 是4字节32位*/ char * h_addr_list; /* 主机的IP地址列表 */ #defineh_addrh_addr_list0 /*

25、 主机的第一个IP地址*/,3 套接字地址,UNIX系统为获得主机信息的相关函数,#include struct hostent* gethostbyname( char *name); struct hostent* gethostbyaddr( void *addr, size_t length, int type );,gethostbyname可以将机器名(如 )转换为一个结构指针.在这个结构里面储存了域名的信息 gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针. 这两个函数失败时返回NULL.,3 套接字地址,2.服务与端口,套接字地址 机器的I

26、P地址 端口号,标识一台计算机,区别同一台计算机上的不同服务程序,3 套接字地址,2 服务与端口,UNIX系统中的端口分配,0, 不使用,11023, 知名端口(保留端口),限定为标准服务使用,如ftp 的21端口,http的80端口等,10245000,可以被任意的客户端程序使用,500165535,为其他服务程序预留,3 套接字地址,UNIX系统为处理服务信息,定义了 servent 结构描述服务相关信息,include struct servent char * s_name; /服务的正式名称 char* s_aliases; /服务的可选别名 int s_port; /服务使用的端口

27、号 char * s_proto; /与该服务一起使用的协议名 ,3 套接字地址,UNIX系统为获得服务信息的相关函数,#include struct servent* getservbyname(char *name,char* proto); Struct servent* getservbyport(int port, char* proto);,3 套接字地址,不同的计算机系统采用不同的字节序存储数据,Little-Endian字节序:将低字节存储在起始地址,由Intel体系采用,Big-Endian字节序:将高字节存储在起始地址,由Macintosh体系采用,3 套接字地址,主机字节

28、序和网络字节序,主机字节序:将某给定系统所用的字节序称为主机字节序,网络字节序:网络上传输的数据使用规定的字节序,即网络字节序,采用big-endian,网络字节次序,不同的计算机厂商在计算机内部存储整数的方法会有些不同。 例如:一个4B的整数,占用内存的连续的4个存储单元,有的厂商将这个整数的低位字节放在最低地址处,x86系列CPU就是这种安排,这种安排叫Little Endian;而有的厂商正好相反,高位字节放在4B内存的低地址处,如PowerPC和Sparc,它们的字节顺序安排叫Big Endian。 网络通信时,总是从内存的低地址开始传输连续的若干字节,因此,网络软件为了保证各计算机之

29、间的互联性,要求所有的数据按统一的字节顺序传输,这就是规定的网络字节顺序。网络字节顺序的规定与x86的主机字节顺序相反。,44,网络字节次序,在UNIX中提供了htons,htonl两个库函数,分别将短整数和长整数从主机字节次序转换到网络字节次序。 相应地,ntohs()和ntohl()把网络字节顺序转换到主机字节顺序。在socket的网络系统调用和库函数的结构体参数中的整数,一般也要求按网络字节顺序排列。 为了源程序的可移植性,即使所用的UNIX中主机字节顺序与网络字节顺序正好吻合,也不要省略掉所必须的htons()、htonl()、ntohs()和ntohl()。,45,read/writ

30、e与TCP通信的时序,当调用read时,如果TCP连接上事先已经有到达了的数据,提前到达的数据会被放在内核里这个TCP连接对应的接收缓冲区内,那么read立即返回,返回值为实际的字节数;否则,如果TCP连接上没有数据到达,那么read就被阻塞,进程处于睡眠状态,直到收到数据为止。 当调用write时,如果TCP连接上发送忙,那么write就被阻塞,进程处于睡眠状态,直到能够把数据传给发送缓冲区为止。write调用没有返回1,系统调用正确返回后,并不能保证数据已经正确地发送到了对方。 下面以一个具体的TCP通信实例描述write和read在什么时刻返回。,46,read/write与TCP通信的

31、时序,主机A通过TCP连接向主机B发送数据,用write();主机B接收数据用read()。最左侧为时间编号。 t0: 主机B开始read(),read处于阻塞状态。 t1: 主机A调用write(),主机A发送缓冲区有空闲,将数据复制至发送缓冲区。 t2: 主机A将数据发往主机B。 t3: 主机B收到数据后,验证校验和正确,则进程被叫醒,read()返回读到的数据。 t4: 主机B向主机A发ACK,ACK途中丢失。 t5: 主机A超时自动重发数据。 t6: 主机B收到重复的数据后扔掉,回送ACK。 t7: 主机A收到ACK,将发送缓冲区的数据清除。,47,read/write与TCP通信的时

32、序,对于read()调用来说,在调用参数中指明了期望读到的字节数。那么,当系统收不到数据时,read()会等待,而不是立刻返回0,只要收到了数据,read()就立即返回,返回值为实际收到的字节数。 即使实际收到的字节数远远小于它所期望读到的字节数nbyte,read()也不再等下去。 系统实际已收到的字节数大于nbyte时,read()读取nbyte字节,剩下的数据,下次read()时读取。如果等待过程中发生了通信意外,那么read()会以1返回。 如果在read/write之前,sockfd上就已经出现了错误,例如,通信中断,那么,read()和write()都会马上以1立刻返回。,48,6

33、 套接字通信模式,流套接字通信模式,7 流套接字操作,send 和 recv,#include int send( int sockfd,void* buffer, size_t length, int flag ); int recv( int sockfd, void* buffer, size_t length, int flag );,sockfd:本地套接字描述符. buffer:保存有发送数据的缓冲区的指针,长度有length指定 flags:. 传输控制方式(0,MSG_DONTROUTE/MSG_OOB/MSG_PEEK/MSG_WAITALL),7 流套接字操作,流套接字操作

34、示例:客户程序向服务程序请求得到日期和时间信息,/客户端程序client.c ,将用到子函数socket_connect #include void main() int sockfd, n; char recvbuffer128; sockfd = socket_connect ( “”, “13”); while( (n= recv(sockfd,recvbuffer, 127, 0) 0 ) recvbuffern = 0; printf(“%s”, recvbuffer); close( sockfd ); ,7 流套接字操作,流套接字操作示例:客户程序向服务程

35、序请求得到日期和时间信息,/服务端程序server.c , 将用到子函数make_socket #include #define MAX_LISTEN 30 void main() int listen_fd, connected_fd; char buffer128; time_t ticks; struct sockaddr_in client_addr; listen_fd = make_socket(13); listen( listen_fd, MAX_LISTEN );,read/write与TCP通信故障,TCP在成功地建立好了连接后,在两台主机之间保持这条连接。 在连接保持期间

36、,如果用户没有数据在这条连接上发送,那么系统会周期性地发送探测数据,这就是所谓的keepalive。 由于TCP连接是“端到端”的,中间需要穿越网络,有很多转发节点,这和直接用电路连接的“点到点”情况不同。“点到点”之间如果没有数据传输,线路就空闲,但是,TCP的“端到端”之间没有数据传输,“端到端”的网络沿途所有节点的线路不见得空闲。所以,这种周期性的探测数据不应当太频繁,以免无谓地增加整个网络的流量。一般默认设置为两个小时。,53,read/write与TCP通信故障,TCP连接建立好之后,一段时间内没有数据传送。这时,如果网络已经发生了故障,TCP两小时一次的keepalive不能迅速察

37、觉,TCP两端的进程也无法知道。这时,如果调用write(),write()在将数据放到发送缓冲区后就成功地返回,返回码不等于1。随后立即还有write(),只要发送缓冲区有空闲,就能成功地返回。 内核中的TCP协议软件负责将数据发送到对方主机,由于网络发生了永久性故障,那么将不可能收到对方的ACK。TCP就试着重发,经多次重发仍没有对方回送的证实后,才断定故障发生。这样,随后再对这一连接进行的read()和write()才会失败,立刻返回1。,54,read/write与TCP通信故障,从write第一次试着发送数据到内核的TCP协议软件多次重发后认定出了故障,这段时间间隔常常为十几分钟。有

38、时在主机向网络发送数据时,网络中的设备会主动以ICMP报文向主机报告“目的不可达”,这时TCP会放弃重试,立即断定网络出了故障,那么,从write()第一次试着发送到断定出了故障的时间间隔值就小得多。 在断定出了故障后,随后的write/read或正在进行中的write/read都会失败,以1返回。 其他原因也有可能导致TCP传输失败,例如:两台主机之间建立好了一条TCP连接之后,其中一台来不及关闭所有TCP连接就突然掉电,不再启动。如果它正在以read()等待已停止运行的主机送来数据,那么它就会执著地等下去,直到keepalive发现了连接断开。,55,read/write与TCP通信故障,

39、在无法确认数据正确到达对方的前提下,write调用就返回,返回码不等于1,但是,数据可能仅仅复制到了内核中的缓冲区。这似乎与TCP承诺的无差错可靠的数据流传输相违背,其实不然,因为只要是从TCP接收到的,就一定是正确的,中间绝没有丢失数据。 在UNIX中的TCP连接上使用write()和read()能带给程序员的服务质量是“发出去的数据不能保证一定会到达对方,但是收到的数据就一定是正确的”。如果发送方需要知道对方是否确实收到了数据,那么,接收方就该通过TCP给一个回执。由于TCP是可靠传输,完全可以一批数据传输完成后给一个回执。 类似的问题是,对方计算机一样也不知道它给出的回执是否确实能到达受信方。,56,read/write与TCP通信故障,在TCP连接一侧的一次write可能会导致对方的多次read,也有可能多次write,只能导致对方的一次read,因此,靠发送端的一次write导致接收端的一次read这样一个对应关系来保持数据记录的边界是靠不住的。 TCP仅仅承诺在连接的两端提供了一个透明的可靠的字节流传输,为了提高传输或者重传的效率,TCP可能会把应用程序多次write的一段段数据,粘接在一起作为一个数据包,

温馨提示

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

评论

0/150

提交评论