Windows平臺網絡編程

windows socket分模式、模型兩種不同的描述方式:
阻塞模式:用於當一個套接字被調用時,決定調用函數的阻塞行爲(阻塞、非阻塞)
I/O模型:描述了一個應用程序如何對套接字上的I/O進行管理(select模型、WSAAsyncSelect模型、WSAEventSelect模型、重疊I/O模型(事件通知、完成例程)、完成端口模型)

模式和模型是兩個不同概念,且相互無關。

阻塞模式:
當使用socket()函數和WSASocket()函數創建套接字時,默認的套接字都是阻塞的.
對於阻塞模式套接字,只有部分socket api會發生阻塞,如:accept,connect,read,send等;bind,listen則會立即完成.
阻塞模式用來開發比較簡單,容易實現,發送數據量較少的網絡開發.
不足是難以管理大量已經連接的套接字.

非阻塞模式:
可以通過ioctlsocket()將阻塞套接字設置爲非阻塞模式.

unsigned long ul = 1;
SOCKET s=socket(AF_INET, SOCK_STREAM, 0);
int nRet = ioctrlsocket(s, FIONBIO, &ul);
if(nRet == SOCKET_ERROR)
{
     // 設置套接字非阻塞模式,失敗處理
}

除了ioctrlsocket外,當在調用WSAAsyncSelect或者WSAEventSelect函數時,會自動將套接字設置爲非阻塞模式.
非阻塞模式需要編寫更多的代碼,以便處理可能出現的失敗返回.相對來說,更難使用.
但是,非阻塞套接字在控制建立的多個連接,在數據的收發量不均,時間不定等方面明顯具有優勢.通常情況下非阻塞套接字的使用需要藉助套接字的I/O模型.

阻塞模式的套接字執行I/O操作時,如果執行操作的條件沒有得到滿足,線程就會被阻塞在該調用的函數上。程序不得不處於等待狀態。該調用函數什麼時候返回,不得而知。
非阻塞模式套接字執行I/O操作時,在任何情況下,調用函數都會立刻返回。軟件開發人員必須編寫更多的代碼,對該函數返回的錯誤進行處理,這無疑增加了開發windows sockes應用程序的難度。另外,應用程序中往往需要在一個循環內反覆調用該函數,直到返回成功指示爲止,這不是一種很好的做法。


---------------------------------------------------------------------------------------------------------

select I/O模型
使用select()函數檢測多個套接字中準備就緒的套接字。其中,需要使用fd_set存放多個套接字,傳入select函數。可以傳入三組,分別檢測是否可讀、是否可寫、是否connect失敗或者有外帶數據:
int select(int nfds, fd_set FAR * readfds, fd_ser FAR * writefds, fd_ser FAR * exceptfds, const struct timeval FAR * timeout);
nfds:忽略,只是爲了兼容早起版本
readfds:具有可讀性套接字集合的指針。符合下列條件的將被保留在fd_set內:
--有數據可以讀入
--連接已經關閉、重置、中止
--已經調用listen()函數的套接字,有客戶端正在連接
writefds:具有可寫性套接字集合的指針。符合下列條件的將被保留在fd_ser內:
--可以發送數據
--一個非阻塞套接字執行connect成功
exceptfds:檢查錯誤套接字集合的指針。滿足下列條件的將被保留在fd_set內:
--一個非阻塞套接字執行connect操作失敗
--有外帶數據可供讀取
timeout:用於設置調用select()函數時的等待時間。當此值爲NULL時,select將等到有滿足條件的套接字之後才返回。如果此值指向的結構體中的值爲0,則此函數無論是否含有滿足條件的套機子,都會立刻返回。

// select等待時間
struct timeval{
     long tv_sec;           // 秒
     long tv_usecl          // 微妙(不足1秒的部分)
}

與fd_set相關的幾個宏有:
FD_ZERO:清空fd_set數組
FD_SET:將一個套接字設置到fd_set中
FD_ISSET:檢測一個套接字是否包含在fd_set中

select函數對套接字的阻塞非阻塞模式沒有要求和影響。兩種模式的套接字都可以使用select I/O模型。

select模型的優勢在於可以同時對多個建立起來的套接字進行有序的管理。可以防止應用程序在一次I/O調用的過程中,使阻塞模式套接字被迫進入阻塞狀態;使非阻塞套接字產生WSAWOULDBLOCK錯誤。
是用select函數的windows sockets程序,其效率可能受損。在CPU的使用率不是關鍵因素時,這種效率可以接受。但是,當需要高效率時,肯定會產生問題。


WSAAsyncSelect
WSAAsyncSelect模型是windows sockets的一個異步模型。利用該模型應用程序可以再一個套接字上,接收以windows消息爲基礎的網絡事件。
WSAAsyncSelect模型是異步的。通過調用WSAAsyncSelect()函數,通知系統對指定套接字感興趣的網絡時間,該函數立即返回,應用程序繼續運行。
發生網絡事件後,系統將向套接字綁定的窗口發送消息。
調用WSAAsyncSelect()後,套接字自動被設置爲非阻塞模式。
int WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
-- s:目的套接字
-- hWnd:接收網絡事件的窗口
-- wMsg:發送給hWnd的消息(相當於自定義窗口消息,要比WM_USER大)
-- lEvent:感興趣的網絡事件(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_OOB、FD_CLOSE、FD_QOS、FD_GROUP_QOS、FD_ROUTING_INTERFACE_CHANGE、FD_ADDRESS_LIST_CHANGE)
開發人員註冊哪種網絡事件,取決於實際的需要。如果應用程序同時對多個網絡事件感興趣,需要對網絡事件類型執行按位或運算,然後將運算結果作爲lEvent參數傳給函數。
注意事項:
--多次調用WSAAsyncSelect函數,最後一次函數調用取消了前面註冊的網絡事件。
--如果要取消所有請求的網絡事件通知,要以參數lEvent值爲0調用WSAASycSelect函數。
--改變或者取消所有請求的網絡事件請求之後,可能在消息隊列中已經排隊了網絡消息,仍然會觸發消息處理函數。
--通過accept返回的套接字與監聽套接字擁有同樣的屬性,通常情況下,通過accept返回的套接字需要立刻再次調用WSAAsyncSelect,重新註冊感興趣的消息。
--每次收到FD_READ事件,不必recv所有的數據。recv後,如果還有未讀數據,還會收到FD_READ事件。
--不要在一個FD_READ事件中調用多次recv函數。否則會導致收到多個FD_READ事件。
--如果在一個FD_READ事件中需要調用多次recv函數,則應該在recv之前先關閉FD_READ事件。
--通過FD_CLOSE事件判斷套接字是否已經關閉。
--如果套接字從容關閉,且數據已經全部讀出,則會只收到FD_CLOSE消息。
--調用closesocket函數後不會投遞FD_CLOSE事件。
--收到FD_WRITE事件後可以在套接字上發送數據。如果發送數據返回WSAEWOULDBLOCK錯誤,則應該停止發送數據,直到再次收到FD_WRITE事件才能發送數據。


WSAEventSelect
WSAEventSelect是Windows Sockets提供的另外一個異步I/O模型。WSAEventSelect與WSAAsyncSelect很相似。不同的地方是兩者網絡事件發生時,通知應用程序的方式不同。WSAASyncSelect以窗口消息的形式通知,WSAEventSelect以事件的形式通知。
調用WSAEvnetSelect之後,套接字自動被設置爲非阻塞模式。如果應用程序要將套接字設置爲阻塞模式,必須設置lNetworkEvents參數爲0,再次調用WSAEventSelect。如果已經調用WSAEventSelect註冊了網絡事件,再通過ioctlsocket函數改變套接字爲阻塞模式,將會失敗,並返回WSAEINAL錯誤。
相關函數:WSAEventSelect,WSACreateEvent,WSAResetEvent,WSAWaitForMultipleEvents,WSAEnumNetworkEvents。
--WSAEventSelect:對指定套接字註冊要處理的網絡事件
----網絡事件:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_OOB,FD_CLOSE,FD_QOS,FD_GROUP_QOS,FD_ROUTING_INTERFACE_CHANGE,FD_ADDRESS_LIST_CHANGE。
--WSACreateEvent:創建要與套接字綁定,接收網絡事件的事件對象
--WSAResetEvent:將事件對象置爲無信號狀態
--WSAWaitForMultipleEvents:監控一個或多個網絡套接字的網絡事件
--WSAEnumNetworkEvents:獲取套接字發生的網絡事件。該函數第二個參數爲可選,可以指定與套接字綁定的事件對象(由WSACreateEvent創建)。如果這裏不指定事件對象,用戶需要調用WSAResetEvent來使事件對象置爲無信號。如果這裏指定了,則該函數將被重置。除非該函數出錯,返回SOCKET_ERROR,則對象不會被重置,網絡事件也不會被清除。


重疊I/O模型
相比select、WSAAsyncSelect、WSAEventSelect,重疊I/O模型是真正意義上的異步I/O模型。前面的幾種I/O模型當發送數據或接收數據時,都是要把數據的I/O完成之後才返回(用戶數據copy到套接字緩衝區或者數據從套接字緩衝區中copy到用戶內存)。但重疊I/O模型,是在應用程序中調用輸入或者輸出函數後,立即返回。當I/O操作完成後,系統通知應用程序。系統通知應用程序的方式有兩種:事件通知、完成例程。
相關的函數主要包括:WSASocket()、WSASend()、WSASendTo()、WSARecv()、WSARecvFrom()、WSAIoctl()、AcceptEx()
--創建
     要使用重疊I/O模型功能,必須使用WSA_FLAG_OVERLAPPED標誌創建套接字。在WSASocket()中需要顯示指定,但當使用socket()函數創建套接字時,會默認設置WSA_FLAG_OVERLAPPED標誌。
--接收數據
     調用WSARecv()或者WSARecvFrom()函數接收數據。其中可以指定指向WSAOVERLAPPED結構體指針或者完成例程。
     一次調用可以指定一個或者多個緩衝區接收數據。如果接收操作立即完成,改函數返回0,lpNumberOfBytesRecvd參數指明接收數據的字節數。如果重疊操作未能立即完成,函數返回SOCKET_ERROR值,錯誤代碼爲WSA_IO_PENDING。在這種情況下lpNumberOfBytesRecvd值不被更新。應用程序可以利用完成例程或者事件通知來獲知操作的最終結果。
     如果lpCompletionRoutine和lpOverLapped參數都爲NULL,則該套接字作爲非重疊套接字使用。
     如果lpCompleRoute參數不爲NULL,則lpOverLapped參數的事件對象將被忽略。應用程序使用完成例程傳遞重疊操作結果。
     如果lpCompleRoute參數爲NULL,lpOverLapped參數不爲NULL,當接收數據完成時,lpOverLapped參數中的事件對象變爲 有信號 狀態。可以通過WSAWaitForMutipleEvents或者WSAGetOverlappedResult函數等待該事件。
--發送數據
     調用WSASend()或者WSASendTo()函數發送數據。
     一次調用可以指定一個或者多個發送緩衝區。
     如果發送立即完成,函數返回0,lpNumberOfBytesSend參數指明被髮送數據的字節數。如果操作未能完成........(同上)

套接字的重疊屬性,不會對套接字的當前工作模式產生影響。創建具有重疊屬性的套接字被用於執行重疊I/O操作,這種操作不會改變套接字的阻塞模式。套接字的阻塞模式與重疊I/O操作不相關。
接收數據時,如果應用程序調用輸入函數的時機上先於數據到達的時間,則當數據到達時,該數據被立即放入用戶的接收數據緩衝區。這就減少了數據的複製過程。如果時機晚於數據到達的時間,則數據被立即複製到用戶緩衝區。


完成端口
完成端口是Win32一種核心對象。使用它,能在一個程序內管理數百個甚至上千個套接字。它往往可以達到最好的系統性能。它是一種真正意義上的異步模型。解決了“one-thread-per-client”的問題。當應用程序需要管理成千上百個套接字,並且希望隨着系統安裝的CPU數量的增加,應用程序的性能得到提升時,I/O完成端口模型是最好的選擇。
--創建完成端口對象
     使用CreateIoCompletionPort()函數創建完成端口對象。
     HANDLE hIoCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
     最後一個參數0表示併發線程數量爲CPU的數量。
--創建服務線程
     CreateThread(NULL, 0, ThreadFun, CreateIoCompletionPort, 0, &threadId);
--套接字與完成端口關聯
     還是通過調用CreateIoCompletionPort實現,不過,參數設置如下:
     FileHandle:套接字句柄
     ExitingCompletionPort:已經被創建好的完成端口句柄
     CompletionKey:完成鍵。通常應用程序自定義一個數據結構,保存與套接字相關的信息。將該結構地址作爲完成鍵傳進去
     NumerOfConcurrentThreads:設置爲0
--發起重疊I/O操作
     通過下列函數發起重疊I/O操作:WSASend、WSASendTO、WSARecv、WSARecvFrom
--等待重疊I/O操作結果
     在服務線程中,調用GetQueuedCompletionStatus函數等待重疊I/O操作的完成結果。當重疊I/O操作完成時,I/O操作完成通知包被髮送到完成端口上,此時該函數返回。完成通知包包含的信息有傳輸的字節數、完成鍵、和重疊結構。
--取消異步操作
     當關閉套接字應用程序時,如果此時系統中還有未完成的異步操作,那麼應用程序可以調用Cancello函數取消等待執行的異步操作。
--投遞完成通知包
     當服務線程退出,應用程序可以調用PostQueuedCompletionStatus函數向服務線程發送一個特殊的完成通知包。在服務線程中,收到這個通知包,線程退出。每個線程,都要調用一次該函數。當要多個線程退出時,需要調用該函數多次。

(完畢)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章