第3章-Socket編程基礎_第1頁
第3章-Socket編程基礎_第2頁
第3章-Socket編程基礎_第3頁
第3章-Socket編程基礎_第4頁
第3章-Socket編程基礎_第5頁
已閱讀5頁,還剩101頁未讀, 繼續免費閱讀

下載本文檔

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

文檔簡介

第三章套接字編程基礎NetworkAdvancedProgramming主要內容網絡編程基礎Socket網絡編程接口的產生與發展Socket工作原理WinSocket編程基礎WinSock編程模型SocketI/O模型Winsock基本APINetworkAdvancedProgramming網絡程序在網絡體系結構中的位置從功能上,網絡程序分為兩部分:通信模塊—分布式應用基礎用戶交互或后臺處理模塊NetworkAdvancedProgramming實現網間進程通信必須解決的問題進程間的標識如何與網絡協議棧連接應用程序需要一個簡單的方式與協議棧連接多重協議棧的識別多重協議棧:TCP/IP,IPX/SPX(NetWareOS),AppleTalk等不同的通信服務不同的網絡應用有不同的通信服務要求NetworkAdvancedProgramming網間進程的標識IP地址--在網絡中標識主機IP地址(網絡號+主機號)傳輸層端口--標識進程端口是TCP/IP協議族中,應用層進程與傳輸層協議實體間的通信接口;從實現的角度講,端口是一種抽象的軟件機制,包括一些數據結構和I/O緩沖區;應用程序需要與端口建立綁定關系;每個端口都擁有一個叫作端口號(portnumber)的整數型標識符;NetworkAdvancedProgramming客戶與服務器第一次通信NetworkAdvancedProgramming使用端口號來判斷服務Webserver(port80)客戶端服務端42FTPserver(port21)Servicerequestfor42:80(i.e.,theWebserver)Webserver(port80)FTPserver(port21)Servicerequestfor42:21(i.e.,theFTPserver)TCP/IP協議棧TCP/IP協議棧ClientClientNetworkAdvancedProgramming標識網間進程TCP和UDP端口號分配端口0:不使用,或者作為特殊的使用;端口1-254:保留給特定的服務,TCP和UDP均規定,小于256的端口號才能分配給網上著名的服務;端口255-1023:保留給其他的服務,如路由;端口1024-4999:可以用作任意客戶的端口;端口5000-65535:可以用作用戶的服務器端口進程的網絡地址的概念在因特網絡中,用一個三元組可以在全局中唯一地標識一個應用層進程:

應用層進程地址=(傳輸層協議,主機的IP地址,傳輸層的端口號)這樣一個三元組,叫做一個半相關(halfassociation),它標識了因特網中,進程間通信的一個端點,也把它稱為進程的網絡地址。

NetworkAdvancedProgramming網絡中進程通信的標識一個完整的網間通信需要一個五元組在全局中唯一地來標識:(傳輸層協議,本地機IP地址,本地機傳輸層端口,遠地機IP地址,遠地機傳輸層端口)這個五元組稱為一個全相關(association)。即兩個協議相同的半相關才能組合成一個合適的全相關,或完全指定一對網間通信的進程。NetworkAdvancedProgrammingNetworkAdvancedProgramming網絡協議的特征面向消息的協議vs.基于流的協議面向連接的服務vs.無連接的服務面向連接服務是電話系統服務模式的抽象,即每一次完整的數據傳輸都要經過建立連接,使用連接,終止連接的過程。無連接服務是郵政系統服務的抽象,每個分組都攜帶完整的目的地址,各分組在系統中獨立傳送可靠性vs.次序性NetworkAdvancedProgramming三類網絡編程基于TCP/IP協議棧的網絡編程基于TCP/IP協議棧的網絡編程是最經典的網絡編程方式,主要是使用各種編程語言,利用操作系統提供的套接字網絡編程接口,直接開發各種網絡應用程序。本書主要講解這種網絡編程的相關技術?;赪WW應用的網絡編程JAVA,HTML,ASP,PHP基于.NET框架的WebServices網絡編程NetworkAdvancedProgrammingC/S模式Clientasks(request)–serverprovides(response)Typically:singleserver-multipleclientsTheserverdoesnotneedtoknowanythingabouttheclienteventhatitexistsTheclientshouldalwaysknowsomethingabouttheserveratleastwhereitislocatedClientprocessServerprocess1.Clientsendsrequest2.Serverhandlesrequest3.Serversendsresponse4.ClienthandlesresponseResourceNetworkAdvancedProgrammingC/S模式C/S模式,因特網上應用程序最常用的通信模式。客戶方采取的是主動請求方式,其工作過程是:(1)打開一通信通道,并連接到服務器所在主機的特定監聽端口。(2)向服務器發送請求報文,等待并接收應答;繼續提出請求,與服務器的會話按照應用協議進行。(3)請求結束后,關閉通信通道并終止。NetworkAdvancedProgrammingC/S交互模式服務器的工作過程:(1)打開一通信通道,并告知服務器所在的主機,它愿意在某一公認的地址上(熟知端口,如FTP為21)接收客戶請求。(2)等待客戶的請求到達該端口。(3)服務器接收到服務請求,處理該請求并發送應答信號。(4)返回第二步,等待并處理另一客戶請求。(5)在特定的情況下,關閉服務器。NetworkAdvancedProgramming服務器的工作方式若有多個客戶端同時請求,服務器如何處理?循環方式(iterativemode)在計算機中一次只運行一個服務器進程。當有多個客戶進程請求服務時,服務器進程就按請求的先后順序依次做出響應。并發方式(concurrentmode)在計算機中同時運行多個服務器進程,而每一個服務器進程都對某個特定的客戶進程做出響應。NetworkAdvancedProgramming服務端的并發性并發性是C/S模式的基礎,并發允許多個客戶獲得同一種服務,而不必等待服務器完成對上一個請求的處理。這樣才能很好地同時為多個客戶提供服務。操作系統支持并發性程序開發Unix系統:fork(),創建新進程Windows系統:CreateThread(),創建新線程NetworkAdvancedProgramming服務器的設計方式服務器設計方式由采用的傳輸層協議和工作方式決定;(TCP/UDP)+(循環/并發)四種設計方式:面向連接的并發面向連接的循環無連接的并發無連接的循環NetworkAdvancedProgrammingTCPTCP客戶臨時端口臨時端口TCP客戶臨時端口TCP客戶臨時端口主服務器TCP連接熟知端口僅用于接受服務請求創建從屬服務器面向連接的并發服務器主服務器主要流程在周知端口上設置監聽模式While(true){

若在周知端口上有客戶端的TCP連接請求;

進行三次握手建立TCP連接;

創建從屬服務器(用臨時端口與客戶端進行通信);}NetworkAdvancedProgramming無連接循環服務器UDP服務器UDP客戶臨時端口熟知端口UDP客戶臨時端口UDP客戶臨時端口一次一個客戶服務器只使用一個熟知端口。每一個客戶則使用自己創建的臨時端口(端口號自己設定)。TCP/IP網絡程序框架面向連接的C/S程序工作流程無連接的C/S程序工作流程通俗解釋socket

socket是網絡編程的基礎,用打電話來類比socket通信中建立TCP連接的過程。

socket函數,表示你買了或者借了一部手機。

bind函數,告訴別人你的手機號碼,讓他們給你打電話。

listen函數,打開手機的鈴聲,而不是靜音,這樣有電話時可以立馬反應。listen函數的第二個參數,最大連接數,表示最多有幾個人可以同時撥打你的號碼。不過我們的手機,最多只能有一個人打進來,要不然就提示占線。

NetworkAdvancedProgramming通俗解釋socketconnect函數,你的朋友知道了你的號碼,通過這個號碼來聯系你。在他等待你回應的時候,不能做其他事情,所以connect函數是阻塞的。

accept函數,你聽到了電話鈴聲,接電話,acceptit!然后“喂”一聲,你的朋友聽到你的回應,知道電話已經打進去了。至此,一個TCP連接建立了。

read/write函數,連接建立后,TCP的兩端可以互相收發消息,這時候的連接是全雙工的。對應打電話中的電話煲。

NetworkAdvancedProgramming通俗解釋socketclose函數,通話完畢,一方說“我掛了”,另一方回應"你掛吧",然后將連接終止。實際的close(sockfd)有些不同,它不止是終止連接,還把手機也歸還,不在占有這部手機,就當是公用電話吧。

NetworkProgramming通俗解釋socket注意到,上述連接是阻塞的,你一次只能響應一個用戶的連接請求,但在實際網絡編程中,一個服務器服務于多個客戶,上述方案也就行不通了,怎么辦?想一想1860,移動的聲訊服務臺,也是只有一個號碼,它怎么能同時服務那么多人呢?可以這樣理解,在你打電話到1860時,總服務臺會讓一個接線員來為你服務,而它自己卻繼續監聽有沒有新的電話接入。在網絡編程中,這個過程類似于fork一個子進程,建立實際的通信連接,而主進程繼續監聽。1860的接線員是有限的,所以當連接的人數達到上線時,它會放首歌給你聽,忙等待,直到有新的空閑接線員為止。

NetworkProgramming面向連接的C/S程序工作流程(TCP)服務器端工作流程使用WSAStartup()函數檢查系統協議棧安裝情況使用socket()函數創建服務器端通信套接口使用bind()函數將創建的套接口與服務器地址綁定使用listen()函數使服務器套接口做好接收連接請求準備使用accept()接收來自客戶端由connect()函數發出的連接請求根據連接請求建立連接后,使用send()函數發送數據,或者使用recv()函數接收數據使用closesocket()函數關閉套接口(可以先用shutdown()函數先關閉讀寫通道)最后調用WSACleanup()函數結束WinsockSocketsAPI面向連接的C/S程序工作流程(TCP)客戶端程序工作流程使用WSAStartup()函數檢查系統協議棧安裝情況使用socket()函數創建客戶端套接口使用connect()函數發出也服務器建立連接的請求(調用前可以不用bind()端口號,由系統自動完成)連接建立后使用send()函數發送數據,或使用recv()函數接收數據使用closesocet()函數關閉套接口最后調用WSACleanup()函數,結束WinsockSocketsAPI面向連接的C/S程序工作流程(TCP)服務器與客戶端五元組的建立五元組<協議><本地IP地址,本地端口號><遠程IP地址,遠程端口號>服務器端五元組由socket()確定由服務器端調用bind()時確定由accept()確定客戶端五元組由socket()確定由客戶端的bind()調用確定。如果客戶端沒有進行bind()調用,或調用了bind()但沒有指定具體地址或端口號,則由系統內核自動確定地址和端口由connect()確定面向連接的C/S程序工作流程圖(TCP)無連接的C/S程序工作流程(UDP)無連接的數據報傳輸服務通信時,客戶端與服務器端所使用的函數是類似的,其工作流程如下:使用WSAStartup()函數檢查系統協議棧的安裝情況使用socket()函數創建套接口,以確定協議類型調用bind()函數將創建的套接口與本地地址綁定,確定本地地址和本地端口號使用sendto()函數發送數據,或者使用recvfrom()函數接收數據使用closesocket()函數關閉套接口調用WSACleanup()函數,結束WindowsSocketsAPI無連接的C/S程序工作流程(UDP)注意事項:通信的一方可以不用bind()綁定地址和端口,由系統分配不綁定IP地址和端口號的一方必須首先向綁定地址的一方發送數據無連接的應用程序也可以調用connect()函數,但是它并不向對方發出建立連接的請求,而是在本地返回,由內核將connect()中指定的目標IP地址和端口號記錄下來,在以后的通信中就可以使用面向連接的數據發送函數send()和數據接收函數recv()無連接的數據報傳輸過程中,作為服務器的一方必須先啟動無連接客戶端一般不調用connect(),在數據發送前客戶與服務器各自通過socket()和bind()建立了半相關,發送數據時除指定本地套接口的地址外,還需要指定接收方套接口地址,從而在數據收發過程中動態建立全連接無連接的C/S程序工作流程圖(UDP)阻塞通信與非阻塞通信阻塞方式:套接字進行I/O操作時,函數要等待到相關的操作完成以后才能返回,對提高處理機的利用率不利,但編程簡單。非阻塞方式:套接字進行I/O操作時,無論操作成功與否,調用都會立即返回。阻塞方式編程簡單,一個套接口的默認操作模式為阻塞,可以調用函數ioctlsocket()進行設置。并發服務器NetworkAdvancedProgramming套接字與套接字編程接口套接字接口定義了應用程序與協議棧軟件進行交互時可以使用的一組操作,決定了應用程序使用協議棧的方式、應用程序所能實現的功能、以及開發具有這些功能的程序的難度。applicationlayertransportlayer(TCP/UDP)networklayer(IP)linklayer(e.g.ethernet)physicallayerapplicationlayertransportlayer(TCP/UDP)networklayer(IP)linklayer(e.g.ethernet)physicallayerOSnetworkstackClientProcessServerProcessSocketAPIOSnetworkstackSocketAPIInternetInternetInternetNetworkAdvancedProgramming什么是套接字(1/3)從系統內核角度,套接字是對網絡中不同主機上應用進程之間進行雙向通信的端點的抽象,表示通信的端點。從應用程序角度,套接字是使得應用程序能夠對網絡進行讀寫操作的文件描述符;對Unix系統,對任何I/O設備,“Everythingisfile”文件I/O的基本操作為:“打開-讀-寫-關閉”(open-read-write-close)網絡I/O與文件I/O的不同主要在于應用程序如何創建和控制套接字Socket:電插口NetworkAdvancedProgramming什么是套接字(2/3)OS將文件描述符實現為一個指針數組,指向一個內部的數據結構:進程描述符表的下標;套接字和文件類似,每個活動套接字使用一個整數標識進程的文件描述符和套接字描述符值不能相同;用于文件0的內部DS0:1:2:3:4:family:PF_INETservice:SOCK_STREAMLocalIP:RemoteIP:Localport:Remoteport::用于文件1的內部DS用于文件2的內部DS用于文件3的內部DS進程的文件描述符表套接字的文件描述符表什么是套接字(3/3)小結在TCP/IP協議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程,“IP地址+端口號”就稱為socket。在TCP協議中,建立連接的兩個進程各自有一個socket來標識,那么這兩個socket組成的socketpair就唯一標識一個連接。socket本身有“插座”的意思,因此用來描述網絡連接的一對一關系。TCP/IP協議最早在BSDUNIX上實現,為TCP/IP協議設計的應用層編程接口稱為socketAPINetworkAdvancedProgrammingNetworkAdvancedProgramming套接字編程接口的起源加州大學伯克利(Berkley)分校開發并推廣了一個包括TCP/IP互聯協議的UNIX,稱為BSDUNIX(BerkeleySoftwareDistributionUNIX)操作系統,套接字編程接口是這個操作系統的一個部分。后來的許多操作系統并沒有另外搞一套其它的編程接口,而是選擇了對于套接字編程接口的支持。由于這個套接字規范最早是由Berkeley大學開發的,一般將它稱為BerkeleySockets規范。NetworkAdvancedProgramming套接字的服務方式和類型數據報套接字(SOCK_DGRAM)提供無連接服務。數據包以獨立數據包的形式被發送,不提供無差錯保證,數據可能丟失或重復,順序發送,可能亂序接收。原始套接字(SOCK_RAW)可以對較低層次協議,如IP、ICMP直接訪問。流式套接字(SOCK_STREAM)提供了一個面向連接、可靠的數據傳輸服務,數據無差錯、無重復的發送且按發送順序接收。內設置流量控制,避免數據流淹沒慢的接收方。數據被看作是字節流,無長度限制。3.2WinSock編程原理

WinSock的啟動和終止

WinSock的啟動是通過調用WSAStartup函數,實現WindowsSocketsDLL的初始化,協商WinSock的版本支持,并分配必要的資源。如果在調用WinSock函數之前,沒有加載WinSock庫,則返回SOCKET_ERROR錯誤,錯誤信息是WSANOTINITIALISED。WSAStartup函數如下:

intWSAStartup(WORDwVersionRequested,LPWSADATAlpWSAData);其中:參數wVersionRequested為調用者可以使用的WindowsSocketsAPI支持的最高版本號;參數lpWSAData指向接收WindowsSockets實現細節的數據結構WSADATA的指針。返回值:如果調用成功,返回0;否則,返回錯誤碼。此函數初始化WindowsSocketsDLL,必須是應用程序或DLL第一個調用的WindowsSockets函數允許。應用程序或DLL指定WindowsSocketsAPI要求的版本,以獲取指定的WindowsSockets實現的細節。應用程序或DLL只有在一次成功的WSAStartup()執行后才能發布后續的WinSock函數。

此外,在應用程序關閉套接字后,還應調用WSACleanup函數終止對WindowsSocketsDLL的使用,并釋放資源,以備下一次使用。WSACleanup函數如下:

intWSACleanup(void);該函數不帶任何參數,若調用成功則返回0,否則返回錯誤碼。

錯誤檢查和控制錯誤檢查和控制對于WinSock應用程序是至關重要的。事實上,對WindowsSocketsAPI函數來說,返回錯誤是很常見的,但多數情況下,通信仍可在套接字上進行。盡管返回的值并非一成不變,但不成功的WinSock調用返回的最常見的值是SOCKET_ERROR。SOCKET_ERROR是值為-1的常量。如果返回了錯誤,可用WSAGetLastError函數來獲得一段代碼,這段代碼明確地表明了產生錯誤的原因。WSAGetLastError函數如下:

intWSAAPIWSAGetLastError(void);該函數的返回值指示當前線程最近執行的WindowsSocketsAPI函數產生的錯誤。

3.2.3WinSock編程模型不論是流套接字還是數據報套接字編程,一般都采用客戶機/服務器方式,它們的運作過程相似。下面介紹這兩種套接字的編程模型。

1.流套接字編程模型流套接字的服務進程和客戶進程在通信前必須創建各自的套接字并建立連接,然后才能對相應的套接字進行讀/寫操作,實現數據的傳輸。流套接字編程時序如圖3.1所示。

圖3.1流套接字編程時序圖

具體步驟如下:

(1)建立套接字。此過程通過socket函數完成。

(2)要將指定協議的套接字綁定到它已知的名字上,這個名字就是本地的IP地址端口號。此過程通過bind函數完成。

(3)服務進程要處于監聽狀態,等待任意數量的客戶端連接,以便為它們的請求提供服務。此服務進程必須在所綁定的名字上進行監聽,所以要把套接字置為監聽模式。此過程通過listen函數來實現。

(4)服務進程調用函數accept或WSAAccept準備接收來自客戶端的連接,如果一個客戶端用connect函數試圖建立連接,服務進程就可以接受連接。

(5)建立連接后,服務器和客戶端之間就可以使用send和recv函數進行通信。注意默認情況recv函數處于阻塞模式,在接收到數據前,程序不向下執行。

(6)通信結束后,調用closesocket函數關閉套接字。

2.數據報套接字編程模型數據報套接字是無連接的,其編程過程比流套接字簡單。數據報套接字編程時序如圖3.2所示。

圖3.2數據報套接字編程時序圖

具體步驟如下:

(1)服務器和客戶端都建立一個數據報套接字。

(2)服務器調用bind函數給套接字分配一個公認的端口,在開發應用程序時,這個公認端口通常是指定的??蛻舳送瑯有枰獙μ捉幼诌M行綁定。

(3)客戶端和服務器都可以使用sendto函數發送數據,使用recvfrom函數接收數據,完成數據報傳遞。默認情況recvfrom函數處于阻塞模式,在接收到數據前,程序不向下執行。

(4)通信結束后,調用closesocket函數關閉套接字。

NetworkProgrammingWinsockI/O模型Windows套接字在阻塞和非阻塞模式下會執行I/O操作。默認情況下,套接字為阻塞模式。阻塞模式下,I/O操作完成前,執行操作的Winsock函數會一直等下去,不會將控制權交回給程序,也就意味著任何一個線程在某一時刻只能執行一個I/O操作,而且應用程序很難同時通過多個建好連接的套接字進行通信。在阻塞模式下,為了解決Winsock函數在未執行完時不交出控制權的情況,但同時還想建立多個套接字通信的問題,Winsock編程通常使用多線程的方法。即使程序每建立一個Winsock連接即建立一個新線程來處理,但這樣會大大增加系統開銷。在非阻塞模式下,Winsock函數了無論如何都會返回并交出程序的控制權,即WinsockAPI調用會立即返回,在大多數情況下這些調用會“失敗”,并返回WSAWOULDBOLCK錯誤信息表示請求的操作在調用期間沒時間完成。阻塞模式下在調用完成后,將會頻繁返回WSAWOULDBLOCK錯誤信息,所以在程序設計時需要通過不斷地檢查函數返回代碼,以判斷一個套接字是否可以供讀寫。NetworkProgrammingWinsockI/O模型為了解決以上問題,Winsock提供了幾種不同的套接字I/O模型進行管理:Select(選擇模型)WSAAsyncSelect(異步選擇模型)WSAEventSelect(事件選擇模型)Overlapped(重疊模型)Completionport(完成端口模型)3.3WinSockI/O模型

3.3.1Select模型

Select模型是。通過調用Select函數可以確定一個或多個套接字的狀態,判斷套接字上是否存在數據,或者能否向一個套接字寫入數據。它既能防止應用程序在套接字處于阻塞模式時,在一次I/O操作后被阻塞,同時也能防止在套接字處于非鎖定模式時,產生WSAEWOULDBLOCK錯誤。Select函數如下:

select模型select原型如下:

intselect( intnfds,//用于保持兼容性,可忽略

fd_setFAR*readfds,//用于檢查可讀性

fd_setFAR*writefds,//用于檢查可寫性

fd_setFAR*exceptfds,//用于例外數據

conststructtimevalFAR*timeout//指向timeval結構指針 );其中:參數nfds指明被檢查的套接字描述符的值域,一般被忽略;參數readfds指向要做讀檢測的套接字描述符集合的指針,調用者希望從中讀取數據;參數writefds指向要做寫檢測的套接字描述符集合的指針;參數exceptfds指向要檢測是否出錯的套接字描述符集合的指針;參數timeout指向select()等待的最大時間,如設為NULL,則為阻塞時間。參數timeout為timeval結構數據,timeval結構的格式為:

select模型readfds集合包括符合下述任何一個條件的套接字:有數據可以讀入。連接已經關閉、重設或中止。假如已調用了listen而且一個連接正在建立,那么accept函數調用會成功。writefds集合包括符合下述任何一個條件的套接字:有數據可以發出。如果已完成了對一個非鎖定連接調用的處理,連接就會成功。exceptfds集合包括符合下述任何一個條件的套接字:假如已完成了對一個非鎖定連接調用的處理,連接嘗試就會失敗。有帶外(Out-of-band,OOB)數據可供讀取。select模型timeout指向timeval結構。用于決定最多等待I/O操作完成的時間

structtimeval{ longtv_sec; longtv_usec; };

如timeout是一個空指針,那么select調用會無限期地“鎖定”或停頓下去,直到至少有一個描述符符合指定的條件后結束。

若將超時值設置為0,表明立即返回,允許應用程序對select操作進行“輪詢”。出于對性能方面的考慮,應避免這樣的設置。select模型用select套接字進行監視之前,在自己的應用程序中,必須將套接字句柄分配給一個集合,設置好一個或全部讀、寫以及例外fd_set結構。之后再來調用select,便可知道一個套接字上是否正在發生上述的I/O活動。Winsock提供了下列宏操作,可用來針對I/O活動,對fd_set進行處理與檢查:FD_CLR(s,*set):從set中刪除套接字s。FD_ISSET(s,*set):檢查s是否set集合的一名成員;如答案是肯定的是,則返回TRUE。FD_SET(s,*set):將套接字s加入集合set。FD_ZERO(*set):將set初始化成空集合。select模型用select操作一個或多個套接字句柄的全過程:1)使用FD_ZERO宏,初始化自己感興趣的每一個fd_set。2)使用FD_SET宏,將套接字句柄分配給自己感興趣的每個fd_set。3)調用select函數,然后等待在指定的fd_set集合中,I/O活動設置好一個或多個套接字句柄。select完成后,會返回在所有fd_set集合中設置的套接字句柄總數,并對每個集合進行相應的更新。4)select返回后,它會修改每個fd_set結構,刪除那些不存在待決I/O操作的套接字句柄。根據select的返回值,我們的應用程序便可判斷出哪些套接字存在著尚未完成的I/O操作。具體的方法是使用FD_ISSET宏,對每個fd_set集合進行檢查。5)知道了每個集合中“待決”的I/O操作之后,對I/O進行處理,然后返回步驟1),繼續進行select處理。3.3.2WSAAsyncSelect模型

WSAAsyncSelect模型是WinSock中另一個常用的異步I/O模型。利用這個模型可在一個套接字上接收以Windows消息為基礎的網絡事件通知。該模型的實現方法是通過調用WSAAsyncSelect函數自動將套接字設置為阻塞模式,并向WinSockDLL注冊一個或多個感興趣的網絡事件,同時提供一個通知時使用的窗口句柄,當注冊的網絡事件發生時,對應的窗口將收到一個基于消息的通知。WSAAsyncSelect模型具體的做法是在建好一個套接字后,調用WSAAsyncSelect函數。消息通知要想使用WSAAsyncSelect模型,在應用程序中,首先必須用CreateWindow函數創建一個窗口,再為該窗口提供一個窗口例程支持函數(Winproc)。亦可使用一個對話框,為其提供一個對話例程,而非窗口例程,因為對話框本質也是“窗口”??紤]到我們的目的,我們打算用一個簡單的窗口來演示這種模型,采用的是一個支持窗口例程。設置好窗口的框架后,便可開始創建套接字,并調用WSAAsyncSelect函數,打開窗口消息通知。WSAAsyncSelect模型WSAAsyncSelect函數的定義:s參數指定的是我們感興趣的那個套接字。hWnd參數指定的是一個窗口句柄,它對應于網絡事件發生之后,想要收到通知消息的那個窗口或對話框。wMsg參數指定在發生網絡事件時,打算接收的消息。該消息會投遞到由hWnd窗口句柄指定的那個窗口。通常,應用程序需要將這個消息設為比Windows的WM_USER大的一個值,避免網絡窗口消息與預定義的標準窗口消息發生混淆與沖突。最后一個參數是lEvent,它指定的是一個位掩碼,對應于一系列網絡事件的組合,應用程序感興趣的便是這一系列事件。表3.1IEvent可用事件

NetworkProgramming到底使用FD_ACCEPT,還是使用FD_CONNECT類型,要取決于應用程序的身份到底是一個客戶機呢,還是一個服務器。如應用程序同時對多個網絡事件有興趣,只需對各種類型執行一次簡單的按位OR(或)運算,然后將它們分配給lEvent就可以了。WSAAsyncSelect模型舉個例子來說:應用程序以后便可在套接字s上,接收到有關連接、發送、接收以及套接字關閉這一系列網絡事件的通知。特別要注意的是,多個事件務必在套接字上一次注冊!另外還要注意的是,一旦在某個套接字上允許了事件通知,那么以后除非明確調用closesocket命令,或者由應用程序針對那個套接字調用了WSAAsyncSelect,從而更改了注冊的網絡事件類型,否則的話,事件通知會永遠有效!若將lEvent參數設為0,效果相當于停止在套接字上進行的所有網絡事件通知。WSAAsyncSelect模型若應用程序針對一個套接字調用了WSAAsyncSelect,那么套接字的模式會從“鎖定”自動變成“非鎖定”,我們在前面已提到過這一點。這樣一來,假如調用了像WSARecv這樣的WinsockI/O函數,但當時卻并沒有數據可用,那么必然會造成調用的失敗,并返回WSAEWOULDBLOCK錯誤。為防止這一點,應用程序應依賴于由WSAAsyncSelect的uMsg參數指定的用戶自定義窗口消息,來判斷網絡事件類型何時在套接字上發生;而不應盲目地進行調用。WSAAsyncSelect模型用于WSAAsyncSelect函數的網絡事件類型FD_READ 應用程序想要接收有關是否可讀的通知,以便讀入數據FD_WRITE 應用程序想要接收有關是否可寫的通知,以便寫入數據FD_OOB 應用程序想接收是否有帶外(OOB)數據抵達的通知FD_ACCEPT 應用程序想接收與進入連接有關的通知FD_CONNECT 應用程序想接收與一次連接或者多點join操作完成的通知FD_CLOSE 應用程序想接收與套接字關閉有關的通知FD_QOS 應用程序想接收套接字“服務質量”(QoS)發生更改的通知FD_GROUP_QOS 應用程序想接收套接字組“服務質量”發生更改的通知(現在沒什么用處,為未來套接字組的使用保留)FD_ROUTING_INTERFACE_CHANGE 應用程序想接收在指定的方向上,與路由接口發生變化的通知FD_ADDRESS_LIST_CHANGE 應用程序想接收針對套接字的協議家族,本地地址列表發生變化的通知WSAAsyncSelect模型應用程序在一個套接字上成功調用了WSAAsyncSelect之后,應用程序會在與hWnd窗口句柄參數對應的窗口例程中,以Windows消息的形式,接收網絡事件通知。窗口例程通常定義如下:hWnd參數指定一個窗口的句柄,對窗口例程的調用正是由那個窗口發出的。uMsg參數指定需要對哪些消息進行處理。就我們的情況來說,感興趣的是WSAAsyncSelect調用中定義的消息。wParam參數指定在其上面發生了一個網絡事件的套接字。假若同時為這個窗口例程分配了多個套接字,這個參數的重要性便顯示出來了。在lParam參數中,包含了兩方面重要的信息。其中,lParam的低字(低位字)指定了已經發生的網絡事件,而lParam的高字(高位字)包含了可能出現的任何錯誤代碼。WSAAsyncSelect模型網絡事件消息抵達一個窗口例程后,應用程序首先應檢查lParam的高字位,以判斷是否在套接字上發生了一個網絡錯誤。這里有一個特殊的宏:WSAGETSELECTERROR,可用它返回高字位包含的錯誤信息。若應用程序發現套接字上沒有產生任何錯誤,接著便應調查到底是哪個網絡事件類型,造成了這條Windows消息的觸發—具體的做法便是讀取lParam之低字位的內容。此時可使用另一個特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。3.3.3WSAEventSelect模型

WSAEventSelect模型是WinSock提供的另一個有用的異步I/O模型,與WSAAsyncSelect模型類似的是,它也允許應用程序在一個或多個套接字上,接收以事件為基礎的網絡事件通知,并且它支持的網絡事件與WSAAsyncSelect模型一樣。它與WSAAsyncSelect模型的主要區別在于網絡事件會被發送到一個事件對象句柄,而不是發送到一個窗口。首先,調用函數WSACreateEvent()創建事件對象來接收網絡事件,其函數原型為

WSAEVENTWSACreateEvent(void);

其返回值是一個事件對象句柄,該事件有兩種工作狀態“已傳信(signaled)”和“未傳信(nonsignaled)”及兩種工作模式“人工重設(manualreset)”和“自動重設(autoreset)”。默認狀態下事件處于“未傳信”的工作狀態和“人工重設”模式。其次,要調用WSAEventSelect()函數將創建的事件對象與某個套接字關聯在一起,同時注冊感興趣的網絡事件類型,使事件對象的工作狀態從“未傳信”轉變成“已傳信”。WSAEventSelect()函數原型為

intWSAEventSelect(SOCKETs,WSAEVENThEventObject,longlNetworkEvents);其中:參數s為一個標識套接字的描述字;參數hEventObject用于指定與所提供的FD_XXX網絡事件集合相關的一個事件對象句柄;參數lNetworkEvents是一個屏蔽位,用于指定感興趣的FD_XXX網絡事件組合。由于事件對象創建后默認處于人工重設模式,因此在完成一個I/O請求的處理之后,應用程序需要調用WSAResetEvent函數將事件對象的工作狀態從“已傳信”更改為“未傳信”。WSAResetEvent()函數原型為

BOOLWSAResetEvent(WSAEVENThEvent);其中:參數hEvent是一個事件句柄,成功則返回TRUE,失敗則返FALSE。

一個套接字與一個事件對象句柄關聯在一起后,應用程序就可以通過WSAWaitForMultipleEvents()函數等待網絡事件來觸發事件句柄的工作狀態,進行I/O處理,其原型為DWORDWSAWaitForMultipleEvents(DWORD cEvents,constWSAEVENTFAR*lphEvents,BOOL fWaitAll,DWORD dwTimeout,BOOL fAlertable );其中:參數lphEvents為指向一個事件對象句柄數組的指針。參數cEvents指出lphEvents所指向的數組中事件對象句柄的數目,事件對象句柄數組的最大值為WSA_MAXIMUM_WAIT_EVENTS。參數fWaitAll指定等待類型,若為TRUE,則當lphEvents數組中的所有事件對象同時有信號時,函數返回;若為FALSE,則當任意一個事件對象有信號時函數就返回。后一種情況返回值指出是哪一個事件對象造成函數返回。通常把該參數設置為FALSE,一次只為一個套接字事件提供服務。

參數dwTimeout指定超時等待時間(以毫秒計),若超時間隔到,不論fWaitAll參數所指定的條件是否滿足,函數即返回。如果dwTimeout為零,則函數測試指定事件對象的狀態,并立即返回。如果dwTimeout為WSA_INFINITE,則函數的超時間隔永遠不會到。參數fAlertable指定當系統將一個I/O完成例程放入隊列以供執行時,函數是否返回,若為TRUE,則函數返回并執行完成例程;若為FALSE,則函數不返回且不執行完成例程。在Win16中應忽略該參數。返回值:如果函數調用成功,則指出造成函數返回的事件對象;如果函數調用失敗,則返回值為WSA_WAIT_FAILED。這樣,應用程序就可引用事件數組中已傳信的事件,并檢索與那個事件對應的套接字,判斷到底是在哪個套接字上,發生了什么網絡事件類型。對事件數組中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去預聲明值WSA_WAIT_EVENT_0,得到具體的引用值(即索引位置)。例如:

Index=WSAWaitForMultipleEvents(...);

MyEvent=EventArray[Index-WSA_WAIT_EVENT_0];知道了產生網絡事件的套接字之后,可調用WSAEnumNetworkEvents函數了解發生的網絡事件的類型。該函數原型為:

intWSAEnumNetworkEvents( SOCKETs,WSAEVENThEventObject,LPWSANETWORKEVENTSlpNetworkEents );其中:參數s為標識套接字的描述字。參數hEventObject是可選參數,用于標識需要重設的相應事件對象,由于事件對象處在一個“已傳信”狀態,所以可將它傳入,令其自動成為“未傳信”狀態。如果不想用hEventObject參數來重設事件,可使用WSAResetEvent()函數。參數lpNetworkEents是一個WSANETWORKEVENTS結構的數組,每一個元素記錄了一個網絡事件和相應的錯誤代碼。WSANETWORKEVENTS結構的格式為:

typedefstruct_WSANETWORKEVENTS

{

long lNetworkEvents;

int iErrorCodes[FD_MAX_EVENTS];

};其中:參數lNetworkEvents用于指定套接字上發生的所有網絡事件類型。

參數iErrorCodes指定的是一個錯誤代碼數組,同lNetworkEvents中的事件關聯在一起。針對每個網絡事件類型,都存在著一個特殊的事件索引,名字與事件類型的名字類似,只是要在事件名字后添加一個“_BIT”后綴字串即可。如對FD_READ事件類型來說,iErrorCodes數組的索引標識符便是FD_READ_BIT。完成了對WSANETWORKEVENTS結構中事件的處理之后,應用程序可在所有可用的套接字上繼續等待網絡事件。

NetworkAdvancedProgrammingWinSock基本APISOCKET:創建套接字SOCKETsocket(intaf,inttype,intprotocol);舉例://創建一個流式套接字。SOCKETsockfd=SOCKET(AF_INET,SOCK_STREAM,IPPROTO_TCP);//創建一個數據報套接字。

SOCKETsockfd=SOCKET(AF_INET,SOCK_DGRAM,IPPROTO_UDP;

BIND:將套接字綁定到指定的網絡地址intbind(SOCKETs,conststructsockaddr*name,intnamelen);NetworkAdvancedProgrammingWinSock中地址信息(1/2)有許多函數都需要套接字的地址信息,像UNIX套接字一樣,Winsock也定義了三種關于地址的結構,經常使用。①通用的Winsock地址結構,針對各種通信域的套接字,存儲它們的地址信息,通常不直接使用。structsockaddr{u_shortsa_family;/*地址家族*/charsa_data[14];/*協議地址*/}②專門針對Internet通信域的Winsock地址結構structsockaddr_in{short.sin_family;/*指定地址家族,一定是AF_INET*/u_shortsin_port;/*指定將要分配給套接字的傳輸層端口號*/structin_addrsin_addr;/*指定套接字的主機IP地址*/charsin_zero[8];/*全置為0,是一個填充數。*/}NetworkAdvancedProgrammingWinSock中地址信息(2/2)在使用Internet域的套接字時,這三個數據結構的一般用法是:首先,定義一個Sockaddr_in的結構實例變量,并將它清零。然后,為這個結構的各成員變量賦值,在調用Bind()綁定函數時,將指向這個結構的指針強制轉換為sockaddr*類型。③專用于存儲IP地址的結構Structin_addr{Union{ Struct{u_chars_b1,s_b2,s_b3,s_b4;}S_un_b;Struct{u_shorts_w1,s_w2;}S_un_w;U_longS_addr;}}NetworkAdvancedProgramming主機字節順序vs.網絡字節順序字節順序是指占內存多于一個字節類型的數據在內存中的存放順序;小端字節序(Little-Endian)指低字節數據存放在內存低地址處,高字節數據存放在內存高地址處eg.X86架構等大端字節序(Big-Endian)是高字節數據存放在低地址處,低字節數據存放在高地址處。eg.Moto系列架構整數0X12345678在小端和大端系統上的存放方式?NetworkAdvancedProgramming主機字節順序vs.網絡字節順序網絡應用程序要在不同的計算機中運行,本機字節順序是不同的,但是,網絡字節順序是一定的。轉換函數:htons()ntohs()htonl()ntohl()inet_addr()將點分十進制IP地址轉換為in_addr地址(網絡字節順序)inet_ntoa()將in_addr地址轉化為點分十進制舉例SOCKETserSock;sockaddr_inmy_addr;interr;//出錯碼。intslen=sizeof(sockaddr);//sockaddr結構的長度。serSock=socket(AF_INET,SOCK_STREAM,0);memset(my_addr,0,sizeof(my_addr));my_addr.sin_family=AF_INET;my_addr.sin_port=htons(21);my_addr.sin_addr.s_addr=htonl(INADDR_ANY);//將套接字綁定到指定的網絡地址,對&my_addr進行了強制類型轉換。if(bind(serSock,(LPSOCKADDR)&my_addr,slen)==SOCKET_ERROR){ err=WSAGetLastError();……}通配地址,由內核選擇IPinet_addr(“”)NetworkAdvancedProgrammingWinSock基本APILISTEN:啟動服務器監聽intlisten(SOCKETs,intbacklog);ACCEPT:接收連接請求SOCKETaccept(SOCKETs,structsockaddr*addr,int*addrlen);返回值為新連接套接字ServerClientlistensocket連接請求listensocketnewsocket建立連接NetworkAdvancedProgrammingWinSock基本APICONNECT:請求連接intconnect(SOCKETs,structsockaddr*name,intnamelen);不需要進行顯示bind地址;舉例client代碼片段,連接133,197.22.4的8888端口struct

sockaddr_in

daddr;memset((void

*)&daddr,0,sizeof(daddr));daddr.sin_family=AF_INET;daddr.sin_port=htons(8888);daddr.sin_addr.s_addr=inet_addr("");iRet=connect(ClientSocket,(struct

sockaddr

*)&daddr,sizeof(daddr));if(iRet==SOCKET_ERROR){//錯誤處理}NetworkAdvancedProgrammingWinSock基本APISEND

intsend(SOCKETs,char*buf,intlen,intflags);

返回實際發送的數據量,以字節為單位nLeft=nBytes;//發送數據長度idx=0;While(nLeft>0){ret=send(s,&sendbuffer[idx],nLeft,0);if(ret==SOCKET_ERROR){

//error}nLeft-=ret;idx+=ret;}NetworkAdvancedProgrammingWinSock基本APIRECVintrecv(SOCKETs,char*buf,intlen,intflags);返回實際接收的數據量,以字節為單位nLeft=512;//接收數據長度idx=0;While(nLeft>0){ret=recv(s,&recvbuffer[idx],nLeft,0);if(ret==SOCKET_ERROR){

//error}nLeft-=ret;idx+=ret;}NetworkAdvancedProgrammingWinSock基本API

Shutdown:關閉連接intshutdown(SOCKETs,inthow);CLOSE:關閉套接字intclosesocket(SOCKETs);SD_RECEIVE、SD_SEND或SD_BOTHClient+server:connection-orientedConcurrentserverSOCKETBINDLISTENCONNECTACCEPTRECEIVERECEIVESENDSENDCLOSETCPthree-wayhandshakeC/S通信實例Client:建立與server的TCP連接;接收服務器的消息;退出Server:等待客戶端的連接,并打印出到來客戶端的IP和端口客戶端主要代碼:#include<winsock2.h>#include<stdio.h>#pragmacomment(lib,"wsock32.lib")Main():WSADATAwsaData;intiResult;

iResult=WSAStartup(MAKEWORD(2,2),&wsaData);

SOCKETConnectSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(ConnectSocket==INVALID_SOCKET){ printf("erroratsocket():%d\n",WSAGetLastError()); WSACleanup(); return-1;}sockaddr_inserviceAddr;char*sendbuf="thisisatest";serviceAddr.sin_family=AF_INET;serviceAddr.sin_port=htons(20000);serviceAddr.sin_addr.s_addr=inet_addr(“");iResult=connect(ConnectSocket,(sockaddr*)&serviceAddr,sizeof(serviceAddr));if(iResult==SOCKET_ERROR){printf("erroratconnect():%d\n",WSAGetLastError());closesocket(ConnectSocket);WSACleanup();return-1;}do{ iResult=recv(ConnectSocket,recvbuf,DEFAULT_BUFLEN,0); if(iResult>0){ printf("Bytesreceived:%d\n",iResult); printf("%s\n",recvbuf);

} elseif(iResult==0) printf("Connectionclosed\n"); else printf("recvfailed:%d\n",WSAGetLastError());

}while(iResult>0);Shutdown(ConnectSocket,SD_BOTH);closesocket(ConnectSocket);WSACleanup();服務器主要代碼:WSADATAwsaData;intiResult;iResult=WSAStartup(MAKEWORD(2,2)

溫馨提示

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

評論

0/150

提交評論