《網絡工程設計與實踐》課件第12章_第1頁
《網絡工程設計與實踐》課件第12章_第2頁
《網絡工程設計與實踐》課件第12章_第3頁
《網絡工程設計與實踐》課件第12章_第4頁
《網絡工程設計與實踐》課件第12章_第5頁
已閱讀5頁,還剩180頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第12章WinSock網絡程序設計12.1WinSock的基本情況

12.2套接字編程基本概念

12.3Sockets庫函數

12.4典型套接字系統調用時序圖

12.5WindowsSockets對BerkeleySockets的擴充

12.6基于WinSock函數的網絡應用程序開發12.7使用面向對象的開發語言提供的與網絡通信相關的類開發網絡應用程序12.8使用可視化開發工具的網絡控件開發網絡應用程序

12.1WinSock的基本情況

在Windows下的各種網絡編程接口中,WindowsSockets脫穎而出,越來越得到大家的重視,這是因為WindowsSockets規范是一套開放的、支持多協議的Windows下的網絡編程接口。從1991年的1.0版本到1995年的2.0.8版本,WindowsSockets經過不斷完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已經成為Windows網絡編程事實上的標準。

WindowsSocketsAPI是MicrosoftWindows的網絡程序接口,它包括一個標準的BerkeleySockets功能調用的集合,以及為Windows所做的重要擴充。擴充的功能調用都以WSA(WindowsSocketsAsynchronous)作前綴,表明它們允許異步的I/O操作,并采用了符合Windows消息機制的網絡事件異步選擇機制。這些擴充有利于應用程序開發者更好地利用Windows的消息驅動特性,設計出高性能的網絡程序。

WindowsSockets的實現一般都由兩部分組成:開發組件和運行組件。開發組件是供程序員開發WindowsSockets應用程序使用的,包括介紹WindowsSockets實現的文檔、WindowsSockets應用程序接口(API)引入庫和一些頭文件。頭文件WINSOCK.H是WindowsSockets最重要的頭文件,包括了WindowsSockets實現所定義的宏、常數值、數據結構和函數調用接口類型。運行組件是WindowsSockets應用程序接口的動態連接庫(DLL),文件名為WINSOCK.DLL,應用程序在執行時通過裝入它實現網絡通信功能。

12.2套接字編程基本概念

12.2.1套接字

套接字(Socket)是網絡通信的基本構件,是可以被命名和尋址的通信端點,使用中的每一個套接字都有其類型和一個與之相連的進程。

套接字存在于通信區域中。通信區域也稱為地址族,是一個抽象概念,主要用于把通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一區域中的套接字交換數據(也可跨區域通信,但要執行某種轉換進程之后才能實現)。WindowsSockets只支持一個通信區域:網際域(AF-INET),這個域被使用網際協議族通信的進程使用。套接字都具有類型,是根據用戶可見的通信特征進行分類的。應用程序被假定為只在同一類型的套接字間通信,不過只要通信協議支持,也可在不同類型的套接字間通信。

TCP/IP的Socket提供三種類型的套接字:

(1)流式套接字(SOCK_STREAM):提供一個面向連接的、可靠的數據傳輸服務,數據無差錯、無重復地發送,且按發送順序接收。內設流量控制,避免數據流超限;數據被看作是字節流,無長度限制。文件傳送協議(FTP)即使用流式套接字。

(2)數據報式套接字(SOCK_DGRAM):提供一個無連接服務。數據包以獨立包形式被發送,不提供無錯保證,數據可能丟失、重復或接收順序混亂。網絡文件系統(NFS)使用數據報式套接字。

(3)原始式套接字(SOCK_RAW):該接口允許對較低層協議(如IP、ICMP)直接訪問。常用于檢驗新的協議實現或訪問現有服務中配置的新設備。12.2.2網間進程通信

1.端口

網絡中可以被命名和尋址的通信端口是操作系統可分配的一種資源。按照OSI協議的描述,傳輸層與網絡層在功能上的最大區別是傳輸層提供進程通信能力。從這個意義上講,網絡通信的最終地址不僅僅是主機地址,還包括可以描述進程的某種標識符。為此,TCP/IP協議提出了協議端口(protocolport,簡稱端口)的概念,用于標識通信的進程。端口是一種抽象的軟件結構(包括一些數據結構和I/O緩沖區)。應用程序(進程)通過系統調用與某端口建立連接(binding)后,傳輸層傳給該端口的數據都被相應進程所接收,相應進程發給傳輸層的數據都通過該端口輸出。在TCP/IP協議的實現中,端口操作類似一般的I/O操作,進程獲取一個端口,相當于獲取本地惟一的I/O文件,可以用一般的讀寫原語訪問。

類似于文件描述符,每個端口都擁有一個叫端口號(portnumber)的整數型標識符,用于區別不同的端口。由于TCP/IP傳輸層的兩個協議TCP和UDP是完全獨立的兩個軟件模塊,因此各自的端口號也相互獨立。端口號的分配是個重要問題,有兩種基本分配方式:全局分配和本地分配。全局分配是一種集中控制方式,由一個公認的中央機構根據用戶需要進行統一分配,并將結果公布于眾。本地分配又稱動態鏈接,即進程需要訪問傳輸層服務時,向本地操作系統提出申請,操作系統返回一個本地惟一的端口號,進程再通過合適的系統調用,將自己與該端口號聯系起來。TCP/IP中端口號的分配綜合了上述兩種方式。TCP/IP將端口號分為兩部分,少量的作為保留端口,以全局方式分配給服務進程,因此每個標準服務器都擁有一個全局公認的端口即周知端口(well-knownport),即使在不同機器上,其端口號也相同。剩余的為自由端口,以本地方式進行分配。TCP和UDP均規定,小于256的端口號才能作為保留端口。

2.地址

網絡通信中通信的兩個進程在不同的機器上。這兩個機器可能位于不同的網絡,這些網絡通過網絡互聯設備(網關、網橋、路由器等)連接。因此需要三級尋址:

(1)某一主機與多個網絡相連,必須指定一特定網絡地址。

(2)網絡上每一主機應有惟一的地址。

(3)每一主機上的每一進程應有在該主機上的惟一標識符。

通常主機地址由網絡ID和主機ID組成,在TCP/IP協議中用32位整數值表示;TCP和UDP均使用16位端口號標識用戶進程。

3.網絡字節順序

不同計算機存放多字節值的順序不同。為保證數據的正確性,在網絡協議中需要指定網絡字節順序。TCP/IP協議使用16位整數和32位整數的高價先存(高位字節在前,低位字節在后)格式,它們均含在協議頭文件中。

4.連接

兩個進程間的通信鏈路稱為連接。連接在內部表現為一些緩沖區和一組協議機制,在外部表現出比無連接高的可靠性。

5.半相關

網絡中用協議、本地地址、本地端口號三者構成的一個三元組在全局中惟一標志一個進程。這個三元組叫半相關(half-association),它指定連接一邊的進程部分。

6.全相關

一個完整的網間進程通信需要兩個進程組成,且只能使用同一種高層協議。即不可能通信的一端用TCP協議通信,另一端用UDP協議通信。因此一個完整的網間通信需要一個五元組來標識(協議,本地地址,本地端口號,遠端地址,遠端端口號),這個五元組叫相關或全相關(association),即兩個協議相同的半相關才能組成一個合適的相關,或完全指定組成一個連接。12.2.3服務方式

1.面向連接(虛電路)或無連接

面向連接服務是電話系統服務模式的抽象,每一次完整的數據傳輸都要經過建立連接、使用連接、中止連接的過程。在數據傳輸過程中,各數據分組,但不攜帶目的地址,而是使用連接號(connectID)。本質上,連接是一個管道,收發數據不但順序一致,而且內容相同。TCP協議提供面向連接的虛電路。

無連接服務是郵政系統服務模式的抽象,每個分組都攜帶完整的目的地址,各分組在系統中獨立傳送。無連接服務不能保證分組的先后順序,不進行分組出錯的恢復和重傳,不保證傳輸的可靠性。UDP協議提供無連接的數據報服務。

2.順序

在網絡傳輸中,兩個連續報文在端到端通信中可能經過不同的路徑,這樣到達目的地址時的順序可能會與發送時不同。“順序”是指接收數據順序與發送數據順序相同。TCP協議提供這項服務。

3.差錯控制

差錯控制是保證應用程序接收的數據無差錯的一種機制。檢查差錯的方法一般是采用檢驗“檢查和(Checksum)”的方法。而保證傳輸無差錯的方法是雙方采用確認應答技術。TCP協議提供這項服務。

4.流控制

流控制是在數據傳輸過程中控制數據傳輸速率的一種機制,以保證數據不丟失。TCP協議提供這項服務。

5.字節流

字節流方式是指僅把傳輸中的報文看作是一個字節序列,不提供數據流的任何邊界。TCP協議提供這項服務。

6.報文

接收方要保存發送方的報文邊界。UDP協議提供這項服務。

7.全雙工/半雙工

端與端之間的數據同時以兩個方向或一個方向傳送。

8.緩存/帶外數據

在字節流服務中,由于沒有報文邊界,用戶進程在某一時刻可以讀寫任意數量的字節。采用有流控制的協議時或為保證傳輸正確時,要進行緩存。但對某些特殊的需求,如交互式應用程序,又會要求取消這種緩存。

在數據傳輸過程中,希望不通過常規傳輸方式傳送給用戶以便用戶及時處理的一類信息稱為帶外數據。為了將互操作中的問題減到最小,應用程序編寫者除非與現有服務互操作時要求帶外數據,否則最好不要使用它。12.2.4客戶機/服務器模式

客戶機/服務器模式在操作過程中采取主動請求方式。首先是服務器方要先啟動,并根據請求提供相應的服務,具體操作如下:

(1)打開一通信通道并告知本地主機,它愿意在某公認地址上接收客戶請求。

(2)等待客戶請求到達該端口。

(3)接收到重復服務請求。處理該請求并發送應答信號。

(4)返回第二步,等待另一客戶請求。

(5)關閉服務器。客戶方的具體操作如下:

(1)打開一通信通道,并連接到服務器所在地主機的特定端口。

(2)向服務器發送服務請求報文,等待并接收應答。

(3)請求結束后關閉通信通道并中止。

從上面描述的過程可知:

(1)客戶與服務器進程的作用是非對稱的,因此編碼不同。

(2)服務進程一般是先于客戶請求而啟動的,只要系統運行,該服務進程一直存在,直到正常中止或強迫中止。

12.3Sockets庫函數

12.3.1庫函數綜述

1.套接字函數

WindowsSockets1.1版本支持的Sockets函數如表12.1所示。表12.1WindowsSockets1.1版本中的Sockets函數這些函數根據功能的不同可以分為如下幾類:

(1)套接字函數。此類函數包括sockets()、bind()、getpeername()、getsockname()和closesocket(),它們主要完成創建、關閉套接字功能,以及對套接字命名與名字獲取。

(2)網絡連接函數。此類函數包括listen()、accept()、connect()和shutdown(),它們完成網絡連接(如虛電路)的建立與關閉。此類函數中有部分可能阻塞。

(3)數據傳輸函數。此類函數包括send()、recv()、sendto()和recvfrom(),它們完成網絡數據的發送與接收,全部是可以阻塞的函數。

(4)字節定序函數。此類函數包括htonl()、htons()、ntohl()和ntohs(),它們完成主機和網絡之間數據字節順序的轉換。

(5)地址轉換函數。此類函數包括inet_addr()、inet_ntoa(),它們完成網絡字符串地址和Internet地址之間的轉換。

(6)套接字控制函數。此類函數包括getsockopt()、setsockopt()、ioctlsocket()和select(),它們設置/獲取套接字的選項,控制/檢測套接字的工作狀態。其中select()函數在必要時可能阻塞。套接字默認的工作模式是處于阻塞方式,一個阻塞操作可能阻塞整個Windows環境。在非搶占式優先的Windows環境中,建議使用非阻塞(異步)操作。也就是說,推薦使用WindowsSockets提供的異步選擇函數代替可能阻塞的select()函數,并且用網絡事件消息來驅動可能阻塞的網絡連接函數(accept()和connect())數據傳輸函數。這樣設計的Windows程序能更好地工作。

2.數據庫函數

WindowsSockets定義了如表12.2所示的數據庫函數。表12.2WindowsSockets1.1版本定義的數據庫函數提供這類函數的目的是為了獲取網絡特定的信息。在WindowsSockets實現中,可能使用了不依賴于本地數據庫文件的方法(如域名服務),但是對應用程序來說,請求這些信息的格式是一致的,并且對應用程序來說是透明的。

調用這些例程所獲得的信息存放在由WindowsSockets實現分配的一個結構中,函數返回此結構的地址。因此,應用程序可以通過此結構指針獲取所需要的信息,但它不能修改此結構,也不能釋放結構的任一部分。另外,對一個線程來說,WindowsSockets實現只分配了結構的一個備份,任何WindowsSocketsAPI調用都可以修改此結構。也就是說,結構指針指向的數據只在此線程的下一次WindowsSocketsAPI調用之前才是正確的,應用程序應該在發布任何其他WindowsSocketsAPI調用之前將任何需要的信息拷貝出來。

數據庫函數除了gethostname()之外都是阻塞的。在設計實現WindowsSockets應用程序時,建議使用WindowsSockets提供的數據庫函數的異步版本(見下節)。

3.?WindowsSockets專用的增設函數

WindowsSockets針對BerkeleySockets標準函數集提供了一些擴充,這些擴充主要用于允許對網絡事件基于消息的異步存取。除了WSAStartup()和WSACleanup()函數之外,其他擴充的函數對于基于套接字的程序設計來說不是強制的,但為了和Windows程序設計風格一致,WindowsSockets推薦使用它們。

WindowsSockets提供的Windows專用增設函數在表12.3中列出。表12.3WindowsSockets1.1版本提供的Windows專用增設函數這些函數可以按功能分為如下幾類:

(1)啟動與中止函數。此類函數包括WSAStartup()和WSACleanup(),它們是WindowsSockets網絡程序必須使用的函數。

(2)異步服務函數。此類函數包括WSAAsyncGetXByY()類的6個函數和WSACancelAsyncRequest()。WSAAsyncGetXByY()請求異步服務,WSACancelAsyncRequest()取消一個未完成的異步操作。

(3)異步選擇函數。?異步選擇函數WSAAsyncSelect()允許應用程序注冊一個或幾個感興趣的網絡事件。

(4)阻塞處理函數。此類函數包括WSAIsBlocking()、WSACancelBlockingCall()、WSASetBlockingHook()和WSAUnhookBlockingHook()。WSAIsBlocking()函數用來檢測阻塞調用是否正在進行,而WSACancelBlockingCall()取消一個正在進行的阻塞調用。它們經常是聯合起來使用的,參看下面的程序段:

if(WSAIsBlocking())WSACancelBlockingCall();

函數WSASetBlockingHook()為應用程序設置自己的阻塞處理例程,WindowsSockets推薦下面的編程風格:在安裝自己的阻塞處理例程時,保存先前安裝的阻塞處理例程的程序實例指針,并在處理結束后恢復,這樣的程序段在執行之后不會影響原來環境中的阻塞處理例程。函數WSAUnhookBlockingHook()恢復默認的阻塞處理例程。在多線程版本Windows中,沒有默認的阻塞處理例程,調用此函數將關閉應用程序所有的阻塞處理例程,并使任何阻塞調用產生阻塞該調用的線程。

(5)錯誤處理函數。此類函數包括WSAGetLastError()和WSASetLastError()。上面介紹的WindowsSocketsAPI函數是WindowsSockets1.1版本規范定義的函數標準集,任何WindowsSockets實現都支持這些函數。另外,特定的WindowsSockets實現可能提供了更為豐富的功能。在WindowsSockets規范中規定,從WindowsSocketsDLL引出的附加接口的序數必須在1000以上,1000以內的序數保留給將來的WindowsSockets使用。那些使用了特定的WindowsSockets實現所提供的附加接口的應用程序,可能不會在其他軟件商提供的WindowsSockets上正常工作。建議在設計應用程序時,不要靜態地把DLL連接到應用程序,而要使用Windows例程LoadLibary()和ProcAddress()動態連接,這樣在不支持應用程序所使用的某些API函數時能夠給出一個出錯消息。12.3.2標準Sockets函數

1.?accept()

accept()用于面向連接服務器。參數addr和addrlen存放客戶方的地址信息。調用前addr指向一個初始值為空的地址結構,而addrlen的初始值為0;調用accept()后,服務器等待從編號為s的套接字上接受客戶連接請求,連接請求是客戶方的connect()調用發出的。當有連接請求到達時,accept()調用將請求連接隊列上的第一個客戶方套接字地址及長度放入addr和addrlen,并創建一個與s有相同特性的新套接字號。新套接字號可用于處理服務器并發請求。

2.?bind()

此函數用于未連接的數據報或流套接字,將一個本地地址與套接字相連接,即建立半相關。當用socket()創建一個套接字后,它存在于一名字空間(地址族),但沒有賦予名字。bind()通過將一本地名字賦予一未命名的套接字,建立起套接字的本地連接(主機地址/端口號)。

3.?closesocket()

closesocket()關閉套接字s,并釋放分配給該套接字的資源。如果s涉及一個打開的TCP連接,則該連接被釋放。

語法:

intWSAAPIclosesocket(SOCKETs);

參數:

s是待關閉的套接字的描述符。

返回值:

如果沒有錯誤產生,closesocket()返回0,否則;返回SOCKET_ERROR。

4.?connect()

此函數用于與對等方建立一個連接。如果套接字s沒有綁定,則系統賦予本地相關的惟一值,并且將套接字表示為已綁定的。

語法:

intWSAAPIconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);

參數:

s是欲建立的本地套接字的描述符(套接字號)。

name指出對方套接字地址結構的指針。

namelen是name指向的socketaddress結構的字節數。返回值:

如果沒有錯誤產生,connect()返回0;否則,返回SOCKET_ERROR。

5.?getpeername()

此函數用于獲取與套接字連接的對等方的地址,它檢索與套接字s連接的對等方的名字,并存在sockaddr結構的name域中。此函數只能用于已連接的數據報或流套接字。對于數據報套接字,只有在先前的connect()調用中指定的對等方的名字被返回,而不會返回先前的sendto()調用指定的名字。語法:

intWSAAPIgetpeername(SOCKETs,structsockaddrFAR*name,intFAR*namelen);

參數:

s是標識已連接的套接字的描述符。

name是指向連接的套接字網際地址的指針,該結構由getpeername()在返回前填寫,name的格式由通信發生的區域決定。

namelen是指向name所指向的結構的大小的指針,返回時含有名字的實際字節數。

返回值:

如果沒有錯誤產生,getpeername()返回0;否則,返回SOCKET_ERROR。

6.?getsockname()

此函數用于獲取套接字的本地名。它檢索指定套接字描述符的當前名字,并存放于sockaddr結構的name域中。它用參數s指定一個已綁定或已連接的套接字,返回與該套接字相連的本地地址。當調用connect()函數之前沒有調用bind()函數時,該調用特別有用,它提供了惟一一種用來確定系統設置的本地連接的方法。

語法:

intWSAAPIgetsockname(SOCKETs,structsockaddrFAR*name,intFAR*namelen);參數:

s是已綁定(bind())的套接字的描述符。

name是接收套接字的地址(名字)的緩沖區指針。

namelen是指向name緩沖區大小的指針,返回名字的實際字節數。

返回值:

如果沒有錯誤產生,getsockname()返回0;否則,返回SOCKET_ERROR。

7.?getsockopt()

此函數用來獲取套接字選項。它檢索與任何類型、任何狀態的套接字相連的套接字選項的當前值,并把結果存入optval。選項可能在多個協議層存在,但它們總是表現在最高的“socket”層。選項影響套接字操作,如一個操作是否阻塞、包的路由選擇、帶外數據傳輸等。

語法:

intWSAAPIgetsockopt(SOCKETs,intlevel,intoptname,charFAR*optval,intFAR*optlen);參數:

s為套接字描述符。

level為設置選項的層,只支持SOL_SOCKET和IPPROTO_TCP。

optname是指定要檢索的套接字選項的名字。

optval是指向查詢選項返回值的緩沖區的指針。

optlen是指向optval緩沖區大小的指針。

返回值:

如果沒有錯誤產生,getsockopt()返回0;否則,返回SOCKET_ERROR。

8.?htonl()

此函數將一個u_long類型數(32位無符號整數)從主機字節順序轉換成TCP/IP網絡字節順序。

語法:

u_longWSAAPIhtonl(u_longhostlong);

參數:

hostlong是指主機字節順序表示的32位無符號整數。

返回值:

返回一個TCP/IP網絡字節順序表示的32位值。

9.?htons()

此函數將一個u_short類型數(16位無符號整數)從主機字節順序轉換成TCP/IP網絡字節順序。

語法:

u_shortWSAAPIhtons(u_shorthostshort);

參數:

hostshort是按主機字節順序表示的16位無符號整數。

返回值:

返回一個TCP/IP網絡字節順序表示的16位值。

10.?inet_addr()

此函數將一個用點分表示法表示地址的字符串地址轉換成網際地址in_addr形式。所有網際地址都以網絡字節順序(字節順序從左到右)返回。

語法:

unsignedlongWSAAPIinet_addr(constcharFAR*cp);

參數:

cp是含有用網際標準點分表示法表示地址的字符串。

返回值:

如果沒有錯誤產生,inet_addr()返回一個無符號長整數,用適合網際地址的二進制表示;否則,返回INADDR_NONE。

11.?inet_ntoa()

此函數將一個網際地址轉換成點分十進制表示法表示的字符串。它接收由參數in指定的網際地址結構,返回以點分表示法表示的地址的ASCII字符串。

語法:

charFAR*WSAAPIinet_ntoa(structin_addrin);

參數:

in表示主機網際地址結構。

返回值:

如果沒有錯誤產生,inet_ntoa()返回一個字符指針,該指針含有以點分十進制表示法表示的正文地址靜態緩沖區,否則,返回NULL。

12.?ioctlsocket()

此函數控制套接字模式,它用于處在任何狀態的任何套接字,用來獲取或檢索與套接字相關的、獨立于協議和通信子系統的操作參數。

語法:

intWSAAPIioctlsocket(SOCKETs,longcmd,u_longFAR*argp);

參數:

s是套接字的描述符。

cmd是在套接字s上執行的命令。

argp是指向cmd命令的參數的指針。返回值:

成功結束則ioctlsocket()返回0;否則,返回SOCKET_ERROR。

13.?listen()

此函數只用于流套接字,執行兩個操作:若沒有為s調用過bind(),則listen()完成套接字s所必需的連接;建立長度為backlog的請求連接隊列來存放即將到來的連接請求。

語法:

intWSAAPIlisten(SOCKETs,intbacklog);參數:

s標識一個本地已建立、尚未連接的套接字號,服務器愿意從它上面接收請求。

backlog表示請求連接隊列的最大長度,用于限制排隊請求的個數,目前允許的最大值為5。

返回值:

如果沒有錯誤產生,listen()返回0;否則,返回SOCKET_ERROR。

14.?ntohl()

此函數將一個u_long類型數(32位無符號整數)從TCP/IP網絡字節順序轉換成主機字節順序。

語法:

u_longWSAAPIntohl(u_longnetlong);

參數:

netlong是TCP/IP網絡字節順序表示的32位無符號整數。

返回值:

返回一個主機字節順序表示的32位值。

15.?ntohs()

此函數將一個u_short類型數(16位無符號整數)從TCP/IP網絡字節順序轉換成主機字節順序。

語法:

u_shortWSAAPIntohs(u_shortnetshort);

參數:

netshort是TCP/IP網絡字節順序表示的16位無符號整數。

返回值:

返回一個主機字節順序表示的16位值。

16.?recv()

此函數用于在參數s指定的已連接的數據報或流套接字上讀取輸入數據。

語法:

intWSAAPIrecv(SOCKETs,charFAR*buf,intlen,intflags);

參數:

s為已連接的套接字描述符。

buf是指向接收輸入數據緩沖區的指針。

len為buf所指向的緩沖區的長度。

flags為指定調用方式,可用來與套接字相關的選項一起影響函數的功能。返回值:

如果沒有錯誤產生,recv()返回總共接收的字節數;如果連接被關閉,返回0,否則,返回SOCKET_ERROR。

17.?recvfrom()

此函數用于在參數s指定的套接字(可能已連接)上讀取輸入數據,并捕獲發送數據的地址,存入源地址緩沖區。

語法:

intWSAAPIrecvfrom(SOCKETs,charFAR*buf,intlen,intflags,structsockaddrFAR*from,intFAR*fromlen);參數:

s為已連接的套接字的描述符。

buf是指向接收輸入數據緩沖區的指針。

len為buf所指向的緩沖區的長度。

flags是指定調用方式,可用來與套接字相關的選項一起影響函數的功能。

from是任選指針,指示源地址緩沖區。

fromlen是任選指針,指示源地址緩沖區的長度。

返回值:

如果沒有錯誤產生,recvfrom()返回收到的字節數;如果連接被關閉,返回0,否則,返回SOCKET_ERROR。

18.?select()

此函數用來檢測一個或多個套接字的狀態。對每個套接字來說,這個調用可以請求讀、寫或錯誤狀態的信息。請求給定狀態的套接字集合由一個fd_set結構指示。在返回時,此結構被更新,以反映那些特定條件的套接字的子集,同時,select()返回滿足條件的套接字的數目。

語法:

intWSAAPIselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,fd_setFAR*exceptfds,conststructtimevalFAR*timeout);參數:

nfds指明被檢查的套接字描述符的值域,一般忽略該參數。

readfds是指向要做讀檢測的套接字描述符集合的指針。

writefds是指向要做寫檢測的套接字描述符集合的指針。

exceptfds是指向要檢測是否出錯的套接字描述符集合的指針。

timeout是指向select()等待的最大時間。如設為NULL,則為阻塞操作。返回值:

如果沒有錯誤產生,select()返回包含在fd_set結構中已準備好的套接字描述符的總數目;否則,返回SOCKET_ERROR。

19.?send()

此函數用于在參數s指定的已連接的數據報或流套接字上發送輸出數據。

語法:

intWSAAPIsend(SOCKETs,constcharFAR*buf,intlen,intflags);參數:

s為已建立的本地套接字的描述符。

buf是指向存有發送數據的緩沖區的指針。

len是buf指向緩沖區中數據的長度。

flags是指定調用方式,可用來與套接字相關的選項一起影響函數的功能。

返回值:

如果沒有錯誤產生,send()返回總共發送的字節數;否則,返回SOCKET_ERROR。

20.?sendto()

此函數用于在參數s指定的數據報或流套接字上向指定的目的地址發送輸出數據。

語法:

intWSAAPIsendto(SOCKETs,constcharFAR*buf,intlen,intflags,conststructsockaddrFAR*to,intFAR*tolen);

參數:

s為已建立的本地套接字的描述符。

buf是指向存有發送數據的緩沖區的指針。

len是buf指向緩沖區中數據的長度。

flags是指定調用方式,可用來與套接字相關的選項一起影響函數的功能。

to是指向目的套接字地址的任選指針。

tolen是to所指向地址的長度。

返回值:

如果沒有錯誤產生,send()返回總共發送的字節數;否則,返回SOCKET_ERROR。

21.?setsockopt()

此函數為套接字相關的選項設置當前值,套接字可以是任何類型,可以處于任何狀態。盡管選項可存在于多個協議層,但它們總是表現在最高的“socket”層。選項影響套接字操作,如加急數據是否在正常數據流中接收、廣播消息是否在套接字上發送等。

語法:

intWSAAPIsetsockopt(SOCKETs,intlevel,intoptname,constcharFAR*optval,intFAR*optlen);參數:

s為套接字描述符。

level為選項定義的層次,只支持SOL_SOCKET和IPPROTO_TCP。

optname是指定套接字選項的名字。

optval是指向請求選項數據緩沖區的指針。

optlen是選項數據optval緩沖區的長度。

返回值:

如果沒有錯誤產生,setsockopt()返回0;否則,返回SOCKET_ERROR。

22.?shutdown()

此函數用來切斷一個雙向連接的接收、發送部分或全部連接。

語法:

intWSAAPIshutdown(SOCKETs,inthow);

參數:

s是套接字描述符。

how是斷路方式,有0,1,2三種不同情況,分別是:如how為0(SD_RECEIVE),套接字上后續的接收將被禁止,這對低層的協議沒有影響;如how為1(SD_SEND),后續的發送數據被禁止;如how為2(SD_BOTH),將同時禁止后繼數據的發送和接收。返回值:

如果沒有錯誤產生,shutdown()返回0;否則,返回SOCKET_ERROR。

23.?socket()

此函數建立一個套接字,它給指定的地址族、數據類型和協議分配一個套接字描述符以及相關的資源。根據這三個參數建立一個套接字,并將相應的資源分配給它,同時返回一個整型套接字號。因此socket()系統調用實際上指定了相關五元組中的“協議”這一元。

語法:

SOCKETWSAAPIsocket(intaf,inttype,intprotocol);參數:

af是一種地址格式描述。現在支持的格式只有PF_INET,為ARPA網際地址格式。

type描述要建立的套接字的類型。

protocol說明該套接字使用的特定協議,如果調用者不希望特別指定使用的協議,則置為0,使用默認的連接模式。

返回值:

如果沒有錯誤產生,socket()返回一個與建立的套接字相關的描述符;否則,返回INVALID_SOCKET。12.3.3數據庫函數

1.?gethostbyaddr()

此函數用來通過地址獲取主機信息。對于數據庫函數中的getXbyY()類型的函數來說,它們返回的指針指向的結構是由WindowsSockets實現分配的。應用程序不能修改此結構或釋放它的任一部件。

語法:

structhostentFAR*WSAAPIgethostbyaddr(constcharFAR*addr,intlen,inttype);參數:

addr是指向一個網絡字節順序表示的32位網際地址的指針。

len表示addr的字節數,對PF_INET地址,必須為4。

type是地址類型,必須為PF_INET。

返回值:

如果沒有錯誤產生,gethostbyaddr()返回一個hostent結構指針;否則,返回NULL指針。

2.?gethostbyname()

此函數用來通過主機名獲取主機信息,它返回hostent結構的指針,此結構包含與給定的主機名相對應的名字和地址。gethostbyname()函數不能理解傳遞給它的IP地址字符串,這個請求只能當作傳送了一個未知的主機名來處理。要分析IP地址字符串,應該使用inet_addr()來將字符串轉換成IP地址,然后調用gethostbyaddr()來獲得hostent結構。

語法:

structhostentFAR*WSAAPIgethostbyname(constcharFAR*name);參數:

name為所查詢主機的名字。

返回值:

如果沒有錯誤產生,gethostbyname()返回一個hostent結構的指針;否則,返回NULL指針。

3.?gethostname()

此函數在參數指定的緩沖區中返回本地主機的名字,是一個以NULL結束的字符串。

語法:

intWSAAPIgethostname(charFAR*name,intnamelen);

參數:

name是指向接受主機名的緩沖區的指針。

namelen是緩沖區的長度。

返回值:

如果沒有錯誤產生,gethostname()返回0;否則,返回SOCKET_ERROR。

4.?getprotobyname()

此函數用來獲取協議信息。

語法:

structprotoentFAR*WSAAPIgetprotobyname(constcharFAR*name);

參數:

name是指向協議名的指針。

返回值:

如果沒有錯誤產生,getprotobyname()返回一個protoent結構指針;否則,返回NULL指針。

5.?getprotobynumber()

此函數通過協議號獲取協議信息,返回protoent結構指針,此結構包含與給定的協議號相對應的名字和協議號。

語法:

structprotoentFAR*WSAAPIgetprotobynumber(intnumber);

參數:

number是用主機字節順序表示的協議號。

返回值:

如果沒有錯誤產生,getprotobynumber()返回一個protoent結構指針;否則,返回NULL指針。

6.?getservbyname()

此函數用來獲取服務信息,返回servent結構指針,此結構內容通過給定服務名name和協議名proto獲得,包含服務名和服務端口號。

語法:

structserventFAR*WSAAPIgetservbyname(constcharFAR*name,constcharFAR*proto);參數:

name是指向服務名的指針。

proto是指向協議名的指針,該參數可選。如為NULL,getservbyname()返回服務名s_name或服務別名s_aliases之中與name匹配的第一個服務入口,否則,getservbyname()同時匹配服務名name和協議名proto。

返回值:

如果沒有錯誤產生,getservbyname()返回一個servent結構的指針;否則,返回NULL指針。

7.?getservbyport()

此函數用來獲取服務信息,返回servent結構的指針,此結構的內容通過給定端口號port和協議名proto獲得,包含服務名和服務端口號。

語法:

structserventFAR*WSAAPIgetservbyport(intport,constcharFAR*proto);參數:

port是服務使用的端口號,用網絡字節順序表示。

proto是指向協議名的指針,該參數可選。如為NULL,getservbyport()返回服務端口號s_port與port匹配的第一個服務入口,否則,getservbyport()同時匹配服務端口號port和協議名proto。

返回值:

如果沒有錯誤產生,getservbyport()返回一個servent結構指針;否則,返回NULL指針。12.3.4增設函數

1.?WSAAsyncGetHostByAddr()

此函數是gethostbyaddr()的異步版本,用來通過網絡地址獲取主機的名字和地址信息。

語法:

HANDLEWSAAPIWSAAsyncGetHostByAddr(HWNDhWnd,unsignedintwMsg,constcharFAR*addr,intlen,inttype,charFAR*buf,intbuflen);參數:

hWnd為異步請求完成時,接收消息的窗口句柄。

wMsg為異步請求完成時,接收到的消息。

addr為指向主機網絡地址的指針,主機網絡地址用網絡字節順序存儲。

len為地址長度,對PF_INET族,必須為4。

type為地址類型,必須為PF_INET。

buf為接收主機信息的數據緩沖區的指針,它的長度必須大于hostent結構的長度。

buflen為buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

2.?WSAAsyncGetHostByName()

此函數是gethostbyname()的異步版本,用來通過主機名字獲取主機的名字和地址信息。

語法:

HANDLEWSAAPIWSAAsyncGetHostByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,charFAR*buf,intbuflen);

參數:

hWnd為異步請求完成時,接收消息的窗口的句柄。

wMsg為異步請求完成時,接收到的消息。

name是指向主機名字的指針。

buf是接收主機信息的數據緩沖區的指針,它的長度必須大于hostent結構的長度。

buflen是buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

3.?WSAAsyncGetProtoByName()

此函數是getprotobyname()的異步版本,用來通過協議名獲取協議的名字和協議號信息。

語法:

HANDLEWSAAPIWSAAsyncGetProtoByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,charFAR*buf,intbuflen);參數:

hWnd為異步請求完成時,接收消息的窗口的句柄。

wMsg為異步請求完成時,接收到的消息。

name是指向要查詢的協議名字的指針。

buf是接收協議信息的數據緩沖區的指針,它的長度必須大于protoent結構的長度。

buflen是buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

4.?WSAAsyncGetProtoByNumber()

此函數是getprotobynumber()的異步版本,用來通過協議號獲取協議的名字和協議號信息。

語法:

HANDLEWSAAPIWSAAsyncGetProtoByNumber(HWNDhWnd,unsignedintwMsg,intnumber,charFAR*buf,intbuflen);參數:

hWnd為異步請求完成時,接收消息的窗口的句柄。

wMsg為異步請求完成時,接收到的消息。

name是指向要查詢的協議名字的指針,用主機字節順序表示。

buf為接收協議信息的數據緩沖區的指針,它的長度必須大于protoent結構的長度。

buflen為buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

5.?WSAAsyncGetServByName()

此函數是getservbyname()的異步版本,用來通過服務名字和協議名獲取服務信息。

語法:

HANDLEWSAAPIWSAAsyncGetServByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,constcharFAR*proto,charFAR*buf,intbuflen);

參數:

hWnd為異步請求完成時,接收消息的窗口的句柄。

wMsg為異步請求完成時,接收到的消息。

name是指向服務名字的指針。

proto是指向協議名的指針,如為NULL,函數尋找服務名s_name或服務別名s_aliases之一與給定的name相匹配的第一個服務入口;否則,函數同時匹配服務名

name和協議名proto。

buf為接收主機信息的數據緩沖區的指針,它的長度必須大于servent結構的長度。

buflen為buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

6.?WSAAsyncGetServByPort()

此函數是getservbyport()的異步版本,用來通過端口號和協議名獲取服務信息。

語法:

HANDLEWSAAPIWSAAsyncGetServByPort(HWNDhWnd,unsignedintwMsg,intport,constcharFAR*proto,charFAR*buf,intbuflen);

參數:

hWnd為異步請求完成時,接收消息的窗口的句柄。

wMsg為異步請求完成時,接收到的消息。

port為服務端口號,用網絡字節順序表示。

proto是指向協議名的指針,如為NULL,函數尋找服務端口號s_port與給定的端口port相匹配的第一個服務入口;否則,函數同時匹配port和proto。

buf為接收主機信息的數據緩沖區的指針,它的長度必須大于servent結構的長度。

buflen為buf指向緩沖區的長度。

返回值:

如果操作被成功初始化,返回一個HANDLE類型的非零值,它是此請求的異步任務句柄;否則,返回0。

7.?WSAAsyncSelect()

此函數為套接字請求基于Windows消息的事件通知,用于請求WindowsSocketsDLL在檢測到由參數lEvent指定的網絡事件時,向窗口hWnd發送一個消息。發送的消息由參數wMsg指定,要求接到通知的套接字由參數s標識。此函數自動地設置套接字s處于非阻塞方式。

語法:

intWSAAPIWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longlEvent);參數:

s是標識請求事件通知的套接字的描述符。

hWnd是標識網絡事件發生時,接收消息的窗口的句柄。

wMsg為網絡事件發生時,窗口接收到的消息。

lEvent為指定應用程序感興趣的網絡事件組合的位屏蔽。

返回值:

如果應用程序對感興趣的網絡事件集的注冊登記成功,返回0;否則,返回SOCKET_ERROR。

8.?WSACancelAsyncRequest()

此函數用來取消由WSAAsyncGetXByY()類函數引起的異步操作。被取消的操作由參數hAsyncTaskHandle標識,此參數由原始調用函數返回,是此異步任務的句柄。

語法:

intWSAAPIWSACancelAsyncRequest(HANDLEhAsyncTaskHandle);

參數:

hAsyncTaskHandle是指示將被取消的異步操作的異步任務句柄。

返回值:

如果操作被成功取消,返回0;否則,返回SOCKET_ERROR。

9.?WSACancelBlockingCall()

此函數為當前任務取消任何尚未完成的阻塞調用。

語法:

intWSAAPIWSACancelBlockingCall(void);

參數:

無。

返回值:

如果操作被成功取消,返回0;否則,返回SOCKET_ERROR。

10.?WSACleanup()

此函數用來結束對WindowsSocketsDLL的使用。

語法:

intWSAAPIWSACleanup(void);

參數:

無。

返回值:

如果操作成功,返回0;否則,返回SOCKET_ERROR。

11.?WSAGetLastError()

此函數用來獲取最近失敗操作的錯誤碼,返回最近產生的網絡錯誤。

語法:

intWSAAPIWSAGetLastError(void);

參數:

無。

返回值:

返回值指示當前線程最近執行的WindowsSocketsAPI函數產生的錯誤。

12.?WSAIsBlocking()

此函數用來檢查阻塞調用是否正在進行,允許任務在等待一個先前的阻塞調用完成時,檢測此調用是否正在進行。

語法:

BOOLWSAAPIWSAIsBlocking(void);

參數:

無。

返回值:

如果當前線程存在未完成的阻塞函數,返回TRUE;否則,返回FALSE。

13.?WSASetBlockingHook()

此函數用來建立特定應用程序的阻塞處理例程。

語法:

FARPROCWSAAPIWSASetBlockingHook(FARPROClpBlockFunc);

參數:

lpBlockFunc是指向要安裝的阻塞處理例程的過程實例地址的指針。

返回值:

返回值為先前安裝的阻塞處理例程的程序實例指針,調用者應該保存這個返回值以便在必要時恢復;如操作失敗,返回NULL指針。

14.?WSASetLastError()

此函數允許應用程序為當前線程設置錯誤碼,此代碼可由其后的WSAGetLastError()調用獲得。

語法:

voidWSAAPIWSASetLastError(intiError);

參數:

iError指示由其后的WSAGetLastError()調用返回的錯誤碼。

返回值:

無。

15.?WSAStartup()

此函數初始化WindowsSocketsDLL,必須是應用程序或DLL第一個調用的WindowsSockets函數。它允許應用程序或DLL指定WindowsSocketsAPI要求的版本,以獲取指定的WindowsSockets實現的細節。應用程序或DLL只有在一次成功的WSAStartup()執行后才能發布后續的WinSock函數。

語法:

intWSAAPIWSAStartup(WORDwVersionRequested,LPWSADATAlpWSAData);參數:

wVersionRequested為調用者可以使用的WindowsSocketsAPI支持的最高版本號。

lpWSAData是指向接收WindowsSockets實現細節的數據結構WSADATA的指針。

返回值:

如果調用成功,返回0;否則,返回錯誤碼。

16.?WSAUnhookBlockingHook()

此函數刪除先前安裝的任何阻塞處理例程,并重新安裝默認的阻塞機制。在多線程版本Windows中,沒有默認的阻塞處理例程,調用此函數將關閉應用程序安裝的所有阻塞處理例程,并使任何阻塞調用產生阻塞該調用的線程。

語法:

intWSAAPIWSAUnhookBlockingHook(void);

參數:

無。

返回值:

如果操作成功,返回0;否則,返回SOCKET_ERROR。

12.4典型套接字系統調用時序圖

TCP/IP協議的應用一般采用客戶機/服務器模式。因此,在實際應用中,必須有客戶和服務器兩個進程,并要先啟動服務器。面向連接協議(如TCP)的套接字系統調用時序圖如圖12.1所示。

服務器必須首先啟動,直到執行完accept()調用,進入等待狀態后,方能接收客戶請求。如客戶在此之前啟動,則connect()將返回出錯代碼,連接不成功。圖12.1面向連接的協議(如TCP)的套接字系統調用時序圖無連接協議的套接字系統調用時序圖如圖12.2所示。

無連接服務器也必須先啟動,否則客戶請求傳不到服務進程。無連接客戶不調用connect(),因此,在數據發送之前,客戶與服務器之間尚未建立全相關,但各自通過socket()和bind()建立了半相關。發送數據時,發送方除指定本地套接字號外,還需指定接收方套接字號,從而在數據收發過程中動態的建立全相關。圖12.2無連接協議的套接字系統調用時序圖 12.5WindowsSockets對Berkeley Sockets的擴充

1.異步選擇機制

WindowsSockets的異步選擇函數提供了消息機制的網絡事件選擇。當使用它登記的網絡事件發生時,Windows應用程序相應的窗口函數將收到一個消息,消息中指示了發生的網絡事件以及與事件相關的一些信息。

2.異步請求函數

異步請求函數允許應用程序用異步方式獲得請求的信息,且在請求的服務完成時給應用程序相應的窗口函數發送一個消息。

3.阻塞處理方法

為了實現當應用程序的套接字調用處于阻塞時,能夠放棄CPU讓其他應用程序運行,WindowsSockets在調用處于阻塞時便進入一個叫“HOOK”的例程。HOOK例程負責接收和分配Windows消息,這使得其他應用程序仍然能夠接收到自己的消息并取得控制權。WindowsSockets還提供了兩個函數(WSASetBlockingHook()和WSAUnhookBlockingHook()),讓用戶設置和取消自己的阻塞處理例程,用以支持要求復雜消息處理的應用程序(如多文檔界面)。函數WSAIsBlocking()可以檢測是否阻塞,函數WSACancelBlockingCall()可以取消一個阻塞的調用。

4.出錯處理

WindowsSockets為了和以后多線程環境(如WindowsNT)兼容,提供了兩個出錯處理函數WSAGetLastError()和WSASetLastError()來獲取和設置當前線程的最近錯誤號。

5.啟動與中止

由于WindowsSockets的服務是以動態連接庫WINSOCK.DLL形式實現的,所以必須要先調用WSAStartup()函數對WindowsSocketsDLL進行初始化,協商版本的支持,并分配必要的資源。在應用程序完成了對WindowsSockets的使用之后,還應調用函數WSACleanup(),中止對WindowsSocketsDLL的使用,并釋放資源,以備下一次使用。12.6基于WinSock函數的網絡應用程序開發

在介紹了WindowsSocketsAPI的基本情況后,將介紹基于WindowsSocketsAPI的網絡程序設計方法。在Windows環境下,有三種基本的網絡應用程序設計方法,分別如下:

(1)用基本的WindowsSocketsAPI函數來進行網絡應用程序開發。

(2)用面向對象的開發語言提供的與網絡通信有關的類來進行網絡應用程序開發。

(3)用可視化開發工具中的網絡控件來進行網絡應用程序開發。這三種方法中所使用的網絡應用編程接口,由低級到高級,即使是高一級的編程接口(類和網絡控件)也是以基本的WindowsSocketsAPI為基礎,只不過是對低級接口在不同程度上的封裝而已。

因為Windows環境下程序設計工具的多樣性,所以有很多可視化程序開發工具和程序設計語言(如Java,C++等)。比較著名的可視化程序設計工具有:Delphi、PowerBuilder、C++Builder、Jbuilder、VisualC++、VisualBasic等。在這些可視化開發工具中,既提供了基本的套接字API函數,也提供了更高一級的編程接口:網絡控件或網絡通信類。盡管不同工具中提供的有關網絡程序設計的類或控件的數量和名稱有所不同,但是其基本思想和原理卻是一樣的。因此,我們將以MFC為例介紹使用類來進行網絡程序設計,以VisualBasic為例介紹基于網絡控件的網絡程序設計。

另外,Windows環境下的程序設計語言和工具發展很快,類和控件的數量也是處于不斷變化之中。因此,需要掌握基本的方法和思想。下面我們以一個例子介紹Winsock網絡程序最典型的模式:客戶機/服務器工作模式(摘自DouglasE.Comer與DavidL.Stevens合著的InternetWorkingwithTCP/IPVolumeⅢ:ClientServerProgrammingandApplications)。本例說明回顯服務(ECHO)是如何實現的,服務器采用的是并發模式,即對每個客戶的請求都用一個獨立的線程來處理,這樣能極大地提高系統效率,減少連接隊列中連接請求的大小。整個程序分為兩部分:客戶程序和服務器程序。

12.6.1服務器程序

下面介紹服務器程序,內容如下。函數passivesock是根據不同的傳輸協議、要求提供的服務,以及連接隊列的大小來創建服務器的監聽套接字。函數首先調用getprotobyname取得協議的信息,從而決定使用何種地址類型來創建套接字,調用函數getservbyname取得相關服務信息,以設置相應的端口;其次根據得到的信息來創建適當的套接字;再次,調用bind函數將創建的套接字與本地地址關聯起來,形成連接三元組;最后調用listen監聽創建的套接字,監聽客戶端的請求。

函數passiveTCP將傳輸協議“tcp”傳遞給passivesock并直接調用它。函數passiveUDP與passiveTCP基本相同,傳遞“udp”給passivesock。

main函數通用passiveTCP創建一個監聽套接字,然后進入主循環,處理客戶端的連接請求,每次收到一個連接請求后程序都調用_beginthread函數為之創建一個

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論