C++socket编程.doc_第1页
C++socket编程.doc_第2页
C++socket编程.doc_第3页
C++socket编程.doc_第4页
C++socket编程.doc_第5页
免费预览已结束,剩余32页可下载查看

下载本文档

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

文档简介

第3章 网络应用3.1 网络编程基础多媒体技术与网络技术的结合,使得网络生活变得多姿多彩。从此,网络生活很迷人;网络改变了和改变着人们原本的生活方式。姑且认为DirectShow是单机的多媒体技术,一旦融合了网络技术,DirectShow更显现了它强大的生命力。本章将着重介绍DirectShow技术在网络方面的应用。网络编程,当然要用到Windows Socket(套接字)技术。Socket相关的操作由一系列API函数来完成,比如socket、bind、listen、connect、accept、send、sendto、recv、recvfrom等。调用这些API函数有一定的先后次序,有些函数的参数还比较复杂,对于开发者来说,不是很好用。于是,微软的MFC提供了两个类:CAsyncSocket和CSocket,极大地方便了Socket功能的使用。这两个类的继承关系如图3.1。图3.1 MFC Socket类的继承关系CAsyncSocket类在较低层次上封装了Windows Socket API,并且通过内建一个(隐藏的)窗口,实现了适合Windows应用的异步机制(Windows Socket API默认情况下工作在阻塞模式,不方便直接在消息驱动的Windows程序上使用)。CSocket类从CAsyncSocket类派生,进一步简化了Socket功能的应用。不过很遗憾,正因为这两个类都内建了一个窗口,它们并不是线程安全的(thread-safe);如果要在多线程环境下应用Socket功能,建议自行封装Socket API函数。使用Socket传输数据主要有两种方式:TCP传输和UDP传输。(OSI参考模型将网络通信分成7个层次,从低往上依次为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层;TCP和UDP均是传输层的协议。)下面,就分别来介绍这两种数据传输方式。提示:本章在介绍网络通信双方的时候,会使用两组关键词:服务器-客户机和本地端-远程端。其中,服务器-客户机是根据角色来界定的;而本地端-远程端是一个相对概念,依据不同的参照物,可以分别表示不同的角色。比如以服务器为参照物,可以称服务器为本地端,称客户机为远程端;而如果以客户机为参照物,可以称客户机为本地端,称服务器为远程端。3.1.1 TCP传输TCP,Transfer Control Protocol的缩写(传输控制协议),是一种面向连接的网络传输协议。TCP协议的特点是,支持多数据流操作,提供流控和错误控制,甚至能完成对乱序到达报文的重新排序等。因此,TCP提供了可靠的应用数据传输服务。通信双方使用TCP传输的一般过程参考如图3.2。图3.2 TCP通信的一般过程本节将要实现一个TCP传输的演示程序TCPDemo,它包括服务器和客户机两个部分。它们的程序界面如图3.3。图3.3 TCP传输演示程序界面TCPDemo的演示过程如下:(1)将服务器和客户机两部分程序都运行起来(此时服务器已经启动了侦听客户机连接请求的子线程,侦听端口号为10028)。(2)在客户机程序界面上输入服务器的IP地址(如果服务器和客户机运行在同一台机器上,IP地址可以指定为)、侦听端口号(因为服务器在10028端口上侦听,这里也应该指定为10028)。(3)点击客户机程序界面上的“Connect”按钮,向服务器发送Socket连接请求。(4)服务器侦听到有客户机的连接请求后便接受它(于是在两个程序之间就建立了一条可靠的Socket连接)。然后服务器会向客户机发送两次字符串数据。(5)客户机接收到数据后,弹出两次如图3.4的消息框。图3.4 TCP传输客户机接收到数据后显示的消息框提示:TCPDemo为什么使用10028作为TCP通信的端口号?因为TCP数据包的TCP头结构中,使用了16位的域来表示一个端口号。因此,有65536个可能的端口号。不过,0-1023是周知口(众所周知的端口,比如80是超文本传输协议http的端口,25是简单邮件传输协议smtp的端口,20和21是文件传输协议ftp的端口等),比1023大的端口号通常被称为高端口号。应用程序一般使用高端口号提供自己的通信服务。TCPDemo使用10028端口是偶然的,只要比1023大就可以了。TCPDemo在具体实现时,设计了一个CTCPListener类专门用于服务器对特定TCP端口的侦听。另外,设计了一个CStreamSocket类专门用于TCP数据的传输。CStreamSocket作为基类,服务器程序从它派生出另一个类CSocketSender专门用于数据的发送,客户机程序从它派生出CSocketReceiver类专门用于数据的接收。这些类的继承结构如图3.5。图3.5 TCPDemo的类继承结构提示:关于CMsgStation和CMsgReceiver两个类的功能介绍,请读者另行参考本书的“2.4.1 一种不错的设计模式”。/ CTCPListener.h/#ifndef _H_CTCPListener_#define _H_CTCPListener_#include CMsgStation.hclass CTCPListener : public CMsgStationprotected:SOCKETmListener; / 用于侦听的SocketSOCKETmAccepted; / 用于与远程端建立连接的SocketWORDmListenPort; / 侦听端口号BOOLmIsListening; / 是否正在侦听的标记HANDLEmLsnThread; / 侦听线程public:CTCPListener();virtual CTCPListener();public:/ 设置/得到侦听的端口号void SetListenPort(WORD inPort); WORD GetListenPort(void);/ 创建/销毁用于侦听的SocketBOOL Create(void);void DeleteListener(void);/ 销毁服务器与客户机建立连接的Socketvoid DeleteAccepted(void);/ 启动/停止侦听线程BOOL StartListening(void);void StopListening(void);/ 得到服务器与客户机建立连接的Socket(用于数据传输)SOCKET GetAccepted(void);private:BOOL Accept(void); / 接受远程端的连接请求static DWORD WINAPI ListeningThrd(void *pParam); / 侦听线程执行体;#endif / _H_CTCPListener_/ / CTCPListener.cpp/#include stdafx.h#include CTCPListener.h#include Netdefs.h#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE = _FILE_;#endif/CTCPListener:CTCPListener()/ 参数初始化mListener = INVALID_SOCKET;mAccepted = INVALID_SOCKET;/ 默认在10028端口上侦听mListenPort = 10028;mLsnThread = NULL;mIsListening = FALSE;CTCPListener:CTCPListener()/ 销毁SocketDeleteAccepted();DeleteListener();/ 停止侦听线程StopListening();/ 设置侦听端口号void CTCPListener:SetListenPort(WORD inPort)mListenPort = inPort;/ 得到侦听端口号WORD CTCPListener:GetListenPort(void)return mListenPort;/ 创建用于侦听的SocketBOOL CTCPListener:Create(void)DeleteListener(); / 销毁侦听Socketint val = 0;BOOL pass = FALSE;/ 创建一个TCP传输的SocketmListener = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);if (mListener != INVALID_SOCKET)/ 在Socket上进行参数设置BOOL sopt = TRUE;setsockopt(mListener, IPPROTO_TCP, TCP_NODELAY, (char *)&sopt, sizeof(BOOL);/ 在销毁Socket时不必等待未发送完的数据完全发送出去setsockopt(mListener, SOL_SOCKET, SO_DONTLINGER, (char *)&sopt, sizeof(BOOL);/ 绑定Socket到指定的侦听端口SOCKADDR_IN addr;memset(&addr, 0, sizeof(SOCKADDR_IN);addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(mListenPort);val = bind(mListener, (struct sockaddr*) &addr, sizeof(addr);pass = (val != SOCKET_ERROR);if (pass)/ 将Socket置于侦听状态val = listen(mListener, SOMAXCONN);pass = (val != SOCKET_ERROR);if (!pass)DeleteListener();return pass;/ 销毁用于侦听的Socketvoid CTCPListener:DeleteListener(void)if (mListener != INVALID_SOCKET)closesocket(mListener);mListener = INVALID_SOCKET;/ 销毁服务器与客户机建立连接的Socketvoid CTCPListener:DeleteAccepted(void)if (mAccepted != INVALID_SOCKET)closesocket(mAccepted);mAccepted = INVALID_SOCKET;/ 启动侦听线程(因为用于接受连接请求的accept函数调用时会阻塞)BOOL CTCPListener:StartListening(void)/ 如果侦听Socket没有创建,则创建它if (mListener = INVALID_SOCKET)Create();if (mListener != INVALID_SOCKET)if (mIsListening)return TRUE;/ 启动侦听线程DWORD threadID = 0;mLsnThread = CreateThread(NULL, 0, ListeningThrd, this, 0, &threadID);return (mLsnThread != NULL);return FALSE;/ 停止侦听线程void CTCPListener:StopListening(void)if (mListener != INVALID_SOCKET & mIsListening)/ 销毁侦听Socket,于是accept将脱离阻塞状态DeleteListener();/ 等待侦听线程完全退出 if (mLsnThread != NULL) WaitForSingleObject(mLsnThread, INFINITE);mLsnThread = NULL;/ 接受远程端的连接请求(创建一个新的Socket用于与远程端建立一条连接)BOOL CTCPListener:Accept(void)if (mListener != INVALID_SOCKET)SOCKADDR_IN saddr;int len = sizeof(SOCKADDR_IN);/ 侦听远程端的连接请求(如果没有连接请求,这个函数将阻塞)SOCKET accepted = accept(mListener, (SOCKADDR *)&saddr, &len);if (accepted = INVALID_SOCKET)return FALSE;/ 注意:目前仅支持建立一条Socket连接!/ 在建立新的连接之前将以前的连接断开DeleteAccepted();/ 保存与远程端建立连接的SocketmAccepted = accepted;/ 在Socket上设置一些参数BOOL sopt = TRUE;setsockopt(mAccepted, IPPROTO_TCP, TCP_NODELAY, (char *)&sopt, sizeof(BOOL);setsockopt(mAccepted, SOL_SOCKET, SO_DONTLINGER, (char *)&sopt, sizeof(BOOL);return TRUE;return FALSE;/ 当与远程端连接的Socket取出之后,保存该Socket的变量置为无效/ 取出的Socket由取出者负责销毁SOCKET CTCPListener:GetAccepted(void)SOCKET ret = mAccepted;mAccepted = INVALID_SOCKET;return ret;/ 侦听线程的函数执行体DWORD WINAPI CTCPListener:ListeningThrd(void *pParam)ASSERT(pParam);/ 获得侦听对象指针CTCPListener * pListen = (CTCPListener *) pParam;pListen-mIsListening = TRUE;while (pListen-mIsListening)/ 开始侦听(如果没有远程端发送连接请求,这个函数将阻塞)if (!pListen-Accept()pListen-mIsListening = FALSE;break;else/ const long cNewSocketAccepted = 6688;/ 发送给上层观察者一个自定义消息cNewSocketAccepted,/ 表示一条Socket连接已经建立(可以用它进行数据传输了!)pListen-Broadcast(cNewSocketAccepted);return 1;/ CStreamSocket.h/#ifndef _H_CStreamSocket_#define _H_CStreamSocket_class CStreamSocketprotected:SOCKETmSocket; / 用于数据发送或接收的SocketBOOLmIsConnected; / Socket是否已经建立了连接的标记BOOLmIsReceiving; / 使用独立的线程进行数据接收HANDLEmRcvThread;BOOLmIsSending; / 使用独立的线程进行数据发送HANDLEmSndThread;public:CStreamSocket();virtual CStreamSocket();public:BOOL Attach(SOCKET inSocket); / 关联一个Socketvoid Detach(void); / 销毁Socket/ 向指定IP地址、端口号的机器发送连接请求BOOL ConnectTo(const char * inTarget, WORD inPort);BOOL IsConnected(void) return mIsConnected; ;/ 用于数据接收的控制函数BOOL StartReceiving(void);void StopReceiving(void);BOOL IsReceiving(void) return mIsReceiving; ;/ 用于数据发送的控制函数BOOL StartSending(void);void StopSending(void);BOOL IsSending(void) return mIsSending; ;protected:static DWORD WINAPI ReceivingThrd(void * pParam); / 接收线程执行体static DWORD WINAPI SendingThrd(void * pParam); / 发送线程执行体/ 接收/发送数据循环过程(虚函数,供子类定制)virtual void ReceivingLoop(void); virtual void SendingLoop(void);#endif / _H_CStreamSocket_/ / CStreamSocket.cpp/ #include stdafx.h#include CStreamSocket.h#include UNetwork.h#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE = _FILE_;#endif/CStreamSocket:CStreamSocket()/ 参数初始化mSocket = INVALID_SOCKET;mIsConnected = FALSE;mIsReceiving = FALSE;mIsSending = FALSE;mRcvThread = NULL;mSndThread = NULL;/ 销毁Socket,停止发送/接收线程CStreamSocket:CStreamSocket()Detach();StopSending();StopReceiving();/ 关联一个Socket到本包装对象BOOL CStreamSocket:Attach(SOCKET inSocket)/ 如果已经包装了一个Socket,则返回一个错误值if (mSocket != INVALID_SOCKET)return FALSE;/ 保存Socket句柄mSocket = inSocket;mIsConnected = TRUE;return TRUE;/ 销毁Socketvoid CStreamSocket:Detach(void)if (mSocket != INVALID_SOCKET)closesocket(mSocket);mSocket = INVALID_SOCKET;mIsConnected = FALSE;/ 向指定IP地址、端口号的机器发送连接请求BOOL CStreamSocket:ConnectTo(const char * inTarget, WORD inPort)if (mIsConnected)return TRUE;/ 首先创建一个TCP传输的SocketmSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (mSocket != INVALID_SOCKET)/ 在成功创建的Socket上调整参数BOOL sopt = TRUE;setsockopt(mSocket, IPPROTO_TCP, TCP_NODELAY, (char *)&sopt, sizeof(BOOL);setsockopt(mSocket, SOL_SOCKET, SO_DONTLINGER, (char *)&sopt, sizeof(BOOL);/ 向服务器发送连接请求SOCKADDR_IN saddr;memset(&saddr, 0, sizeof(SOCKADDR_IN);saddr.sin_addr.S_un.S_addr = inet_addr(inTarget);saddr.sin_family = AF_INET;saddr.sin_port = htons(WORD)inPort);if (connect(mSocket, (SOCKADDR *)&saddr, sizeof(SOCKADDR_IN) != 0) / 跟踪Socket错误#ifdef _DEBUGUNetwork:DumpSocketError();#endif/ 如果连接失败,则销毁刚才创建的SocketDetach();return FALSE;mIsConnected = TRUE;return TRUE;return FALSE;/ 启动数据接收线程(因为Socket数据接收函数调用时会阻塞)BOOL CStreamSocket:StartReceiving(void)if (mSocket != INVALID_SOCKET)if (mIsReceiving)return TRUE;DWORD threadID = 0;mRcvThread = CreateThread(NULL, 0, ReceivingThrd, this, 0, &threadID);return (mRcvThread != NULL);return FALSE;/ 停止数据接收线程void CStreamSocket:StopReceiving(void)if (mIsReceiving)/ 销毁Socket,使接收函数失败或脱离阻塞Detach();/ 等待数据接收线程的完全退出if (mRcvThread != NULL) WaitForSingleObject(mRcvThread, INFINITE);mRcvThread = NULL;/ 启动数据发送线程(以提高数据发送的效率)BOOL CStreamSocket:StartSending(void)if (mSocket != INVALID_SOCKET)if (mIsSending)return TRUE;DWORD threadID = 0;mSndThread = CreateThread(NULL, 0, SendingThrd, this, 0, &threadID);return (mSndThread != NULL);return FALSE;/ 停止数据发送线程void CStreamSocket:StopSending(void)if (mIsSending)/ 销毁Socket,使发送函数失败或脱离阻塞Detach();if (mSndThread != NULL) / 等待数据发送线程的完全退出WaitForSingleObject(mSndThread, INFINITE);mSndThread = NULL;/ 数据接收线程的函数执行体DWORD WINAPI CStreamSocket:ReceivingThrd(void * pParam)CStreamSocket * pSock = (CStreamSocket *) pParam;if (pSock)pSock-mIsReceiving = TRUE;/ 执行接收循环pSock-ReceivingLoop();return 1;return 0;/ 数据发送线程的函数执行体DWORD WINAPI CStreamSocket:SendingThrd(void * pParam)CStreamSocket * pSock = (CStreamSocket *) pParam;if (pSock)pSock-mIsSending = TRUE;/ 执行发送循环pSock-SendingLoop();return 1;return 0;/ 虚函数,供子类定制实际的数据接收(循环)过程void CStreamSocket:ReceivingLoop(void)/ 虚函数,供子类定制实际的数据发送(循环)过程void CStreamSocket:SendingLoop(void)/ CSocketSender.h/#ifndef _H_CSocketSender_#define _H_CSocketSender_#include CStreamSocket.hclass CSocketSender : public CStreamSocketpublic:CSocketSender();virtual CSocketSender();protected:virtual void SendingLoop(void); / 定制数据发送过程;#endif / _H_CSocketSender_/ CSocketSender.cpp/ 服务器程序定制的数据发送过程void CSocketSender:SendingLoop(void)char buf1024; / 发送数据使用的缓存int bytes = 0;/ 定义一个字符串作为发送的数据内容char str = HQ Tech, Make Technology Easy!;/ 发送数据的总长度 = 字符串长度 + 头信息长度int len = strlen(str) + sizeof(Net_Header); / 在数据内容之前加上一个自定义头信息(用以说明数据内容的长度)Net_Header * pHeader = (Net_Header *) buf;pHeader-pack_size = strlen(str);pHeader-my_hton(); / 字节顺序转换!/ 将欲发送的数据内容和头信息整合strcpy(buf+sizeof(Net_Header), str);/ 作为演示,将上述定义的字符串数据发送两次int counter = 2;while (mIsSending)/ 使用Socket进行一次数据发送bytes = send(mSocket, buf, len, 0);if (bytes = SOCKET_ERROR)Detach();mIsSending = FALSE;break;/ 当完成两次发送后断开Socket连接,结束发送线程if (-counter = 0)Detach();mIsSending = FALSE;break;/ CSocketReceiver.h/#ifndef _H_CSocketReceiver_#define _H_CSocketReceiver_#include CStreamSocket.hclass CSocketReceiver : public CStreamSocketpublic:CSocketReceiver();virtual CSocketReceiver();protected:virtual void ReceivingLoop(void); / 定制数据接收过程;#endif / _H_CSocketReceiver_/ CSocketReceiver.cpp/ 客户机程序定制的数据接收过程void CSocketReceiver:ReceivingLoop(void)/ 接收数据使用的缓存char buf1024;int bytes = 0;Net_Header * pHeader = (Net_Header *) buf;while (mIsReceiving)/ 首先接收一个头信息(头信息内包含了随后的有效数据长度)bytes = recv(mSocket, buf, sizeof(Net_Header), 0);if (bytes = SOCKET_ERROR | bytes = 0)Detach();mIsReceiving = FALSE;break;pHeader-my_ntoh(); / 字节顺序转换!/ 继续读取后续的有效数据(即一个字符串内容)bytes = recv(mSocket, buf, pHeader-pack_size, 0);if (bytes = SOCKET_ERROR | bytes = 0)Detach();mIsReceiving = FALSE;break;bufbytes = 0;/ 弹出一个消息框显示接收到的字符串内容CString msg = Received content:n;AfxMessageBox(msg + buf);那么,TCPDemo是怎么来使用CTCPListener、CStreamSocket、CSocketSender、CSocketReceiver这几个类的呢?先来看服务器程序TCPServer。这是一个基于对话框的MFC程序。它在对话框类CTCPServerDlg中定义了两个成员:一个是CTCPListener类的实例,一个是CSocketSender类的实例。前者用于侦听客户机的连接请求,后者负责实际的Socket数据发送。然后,在主对话框的初始化函数中创建用于侦听的Socket,并启动侦听线程。当有客户机发出连接请求,并且服务器成功接受后,就启动数据发送线程真正开始数据的发送。/ TCPServerDlg.h : header file/class CTCPServerDlg : public CDialog, public CMsgReceiverpublic:CTCPServerDlg(CWnd* pParent = NULL);protected:HICON m_hIcon;CTCPListenermListener; / 用于侦听客户机的连接请求CSocketSendermNetSender; / 用于数据发送/ 自定义消息的处理函数virtual bool ReceiveMessage(MessageT inMessage, void * ioParam, void * ioParam2);/ 其它成员定义(省略)/ ;/ TCPServerDlg.cpp : implementation file/CTCPServerDlg:CTCPServerDlg(CWnd* pParent /*=NULL*/): CDialog(CTCPServerDlg:IDD, pParent)/AFX_DATA_INIT(CTCPServerDlg)mHostPort = 10028;/AFX_DATA_INIT/ Note that LoadIcon does not require a subsequent DestroyIcon in Win32m_hIcon = AfxGetApp()-LoadIcon(IDR_MAINFRAME);/ 主对话框的初始化函数BOOL CTCPServerDlg:OnInitDialog()CDialog:OnInitDialog();/ Set the icon for this dialog. The framework does this automatically/ when the applications main window is not a dialogSetIcon(m_hIcon, TRUE);/ Set big iconSetIcon(m_hIcon, FALSE);/ Set small icon/ 获取本地机器的IP地址、机器名,并在界面上显示char hostName100;char hostIP50;if (UNetwork:GetHostInfo(hostIP, hostName)mEditHostName.SetWindowText(hostName);mEditHostIP.SetWindowText(hostIP);/ 主界面对象是mListener对象的观察者(因为它想获得Socket连接建立的通知)mListener.AddMsgReceiver(this);/ 设置侦听端口号mListener.SetListenPort(mHostPort);/ 创建侦听Socket,成功后启动一个侦听线程if (mListener.Create()mListener.StartListening();return TRUE; / return TRUE unless you set the focus to a control/ 当接收到Socket连接已经建立的通知后,启动数据发送线程向客户机发送数据bool CTCPServerDlg:ReceiveMessage(MessageT inMessage, void * ioParam, void * ioParam2)if (inMessage = cNewSocketAccepted)/ 获取建立连接的SocketmNetSender.Attach(mListener.GetAccepted();/ 启动数据发送线程mNetSender.StartSending();return true;return CMsgReceiver:ReceiveMessage(inMessage, ioParam, ioParam2);提示:使用MFC开发Socket程序时,一般要包含afxsock.h头文件(可以加在stdafx.h文件中)。程序运行之前,还要调用AfxSocketInit函数进行Socket函数库的初始化,实现如下:/ TCPServer.cpp/BOOL CTCPServerApp:InitInstance()/ - Socket函数库的初始化 -/ AfxSocketInit内部调用WSAStartup函数,/ 并且能够保证在程序退出之前自动调用WSACleanup函数!if (!AfxSocketInit()AfxMessageBox(Socket initializing failded!);return FALSE;/ 创建主对话框CTCPServerDlg dlg;m_pMainWnd = &dlg;int nResponse = dlg.DoModal();if (nResponse = IDOK)else if (nResponse = IDCANCEL)return FALSE;再来看客户机程序TCPClient的实现。这也是一个基于对话框的MFC程序。它在对话框类CTCPClientDlg中定义了一个是CSocketReceiver类的实例,专门用于向服务器发出连接请求,以及接收服务器发送过来的数据。/ TCPClientDlg.h : header file/class CTCPClientDlg : public CDialogpublic:CTCPClientDlg(CWnd* pParent = NULL);protected:HICON m_hIcon;CSocketReceivermNetReceiver; / 用于数据接收/ 其它成员定义(省略)/ ;/ TCPClientDlg.cpp : implementation file/CTCPClientDlg:CTCPClientDlg(CWnd* pParent /*=NULL*/): CDialog(CTCPClientDlg:IDD, pParent)/AFX_DATA_INIT(CTCPClientDlg)mTargetIP = _T();mTargetPort = 10028;/AFX_DATA_INIT/ Note that LoadIcon does not require a subsequent DestroyIcon in Win32m_hIcon = AfxGetApp()

温馨提示

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

评论

0/150

提交评论