目錄程序員
0 理解Socket
1 WinSock API
2 阻塞socket
3 非阻塞Socket
4 套接字IO模型
4.1 套接字IO模型:select(選擇)
4.2 套接字IO模型:WSAAsyncSelect(異步選擇)
4.3 套接字IO模型:WSAEventSelect(事件選擇)
4.4 套接字IO模型:Overlaped(重疊)
4.4.1 基於事件通知的重疊I/O模型
4.4.2 基於完成例程的重疊I/O模型
4.5 套接字IO模型:Completion port(完成端口)
5 原始套接字 編程
0 理解Socket
什麼是Socket呢?
咱們常常把Socket翻譯爲套接字,Socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操做抽象爲幾個簡單的接口供應用層調用已實現進程在網絡中通訊。
socket起源於UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在創建鏈接打開後,能夠向本身文件寫入內容供對方讀取或者讀取對方內容,通信結束時關閉文件。
套接字是通訊的基石,可看做不一樣主機的進程進行雙向通訊的端點。
套接字有兩種不一樣的類型:流式套接字和數據報套接字。
套接字可處於阻塞模式或非阻塞模式。windows
1 WinSock API
什麼是WinSock呢?
WinSock是一套開放的、支持多種協議的Windows下網絡編程的接口,是Windows網絡編程實時上的標準。
Winsock版本:目前Winsock有兩個版本,分別是WinSock1.1和WinSock2.0,使用方法以下:
WinSock1.1:
#include <winsock.h>
#pragma comment(lib, "wsock32.lib")
WinSock2.0:
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
WinSock服務是以動態連接庫Winsock DLL形式實現的。服務器
通用API函數列表
WinSock API 描述
WSAStartup Winsock啓動
WSACleanup Winsock中止
WSASetLastError 錯誤的檢查和控制網絡
針對WinSock1.1存在的某些侷限,WinSock2提供了許多方面的擴展(如支持多個傳輸協議的原始套接字、重疊IO模型、服務質量控制等)以支持功能更強大的應用,考慮兼容性,WinSock1.1的API都在WinSock2中保留了下來。數據結構
2 阻塞socket
基於TCP的套接字:
// 建立套接字
SOCKET socket(int af, int type, int protocol);
// 綁定地址端口
int bind(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);
// 監聽客戶端
int listen(SOCKET s, int backlog);
// 接收客戶端套接字請求
int accept(SOCKET s, struct sockaddr *addr, socklen_t *addrlen);
// 鏈接服務端套接字
int connect(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);
// 發送數據
int send(SOCKET s, const char FAR * buf, int len, int flags);
// 接收數據(阻塞)
int recv(SOCKET s, char* buf, int len, int flags);
// 關閉套接字
int closesocket(SOCKET s);多線程
基於UDP的套接字:
// 建立套接字
SOCKET socket(int af, int type, int protocol);
// 綁定地址端口
int bind(SOCKET s, const struct sockaddr *addr, socklen_t addrlen);
// 發送數據
int sendto (SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);
// 接收數據(阻塞)
int recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen);
// 關閉套接字
int closesocket(SOCKET s);併發
總結:
在阻塞模式下,在IO操做完成以前,執行操做的WinSock函數會一直等待下去,不會當即返回,這就意味着任意一個線程在某一時刻只能進行一個IO操做,並且應用程序很難同時經過多個建好鏈接的套接字進行通訊。
可見,在默認狀況下套接字爲阻塞模式。
這種狀況下通常採用多線程方式,在不一樣的線程中進行不一樣的鏈接處理來避免阻塞,可是多線程會增長系統開銷,並且線程同步會增長複雜度。app
3 非阻塞Socket
WinSock API默認爲阻塞模式,可是其提供了非阻塞模式套接字,非阻塞模式套接字使用上不如阻塞模式套接字簡單,存在一點的難度,可是隻要排除了這些困難,它在功能上仍是很強大的。
可使用ioctlsocket將套接字設置爲非阻塞模式套接字:
int PASCAL FAR ioctlsocket (
IN SOCKET s,
IN long cmd,
IN OUT u_long FAR *argp);
// If *argp = 0, blocking is enabled;
// If *argp != 0, non-blocking mode is enabled.異步
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sock) { AfxMessageBox(_T( "Create socket failed" )); WSACleanup(); return false ; } unsigned long lMode = 1 ; int retMode = ioctlsocket(sock, FIONBIO, ( unsigned long *)& lMode); if (retMode == SOCKET_ERROR) { return false ; } |
將一個套接字設置爲非阻塞模式後,WinSock API調用會當即返回。大多數狀況下,這些調用都會"失敗",並返回一個WSAEWOULDBLOCK錯誤表示請求的操做在調用期間沒有時間完成。因爲會不斷地返回這個錯誤,因此程序員須要經過不斷地檢查函數返回碼以判斷一個套接字什麼時候可供讀寫。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
While(
true
) { nRec = recv(sock, ( char *)pbuf, 1000 , 0 ); if (nRec == SOCKET_ERROR) { int r = WSAGetLastError(); if (r == WSAEWOULDBLOCK) { continue ; } else if (nRec == WSAETIMEDOUT || nRec == WSAENETDOWN) { printf( "recv failed!\n" ); closesocket(sServer); closesocket(sClient); WSACleanup(); return - 1 ; } } } |
4 套接字IO模型
套接字的阻塞模式和非阻塞模式都存在必定的缺點,會給編程帶來必定的麻煩。爲了免去這樣的麻煩,WinSock提供了集中不一樣的套接字IO模型對IO進行管理,它們包括:
4.1 套接字IO模型:select(選擇)
select模式是WinSock中最多見的IO模型。經過調用select函數能夠肯定一個或多個套接字的狀態,判斷套接字上是否存在數據,或者可否向一個套接字寫入數據。有以下好處:
1)、防止應用程序在套接字處於阻塞模式時,在一次IO操做後被阻塞;
2)、防止在套接字處於非阻塞模式中時產生WSAEWOULDBLOCK錯誤。
函數原型:
The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
int PASCAL FAR select (
IN int nfds,
IN OUT fd_set FAR *readfds,
IN OUT fd_set FAR *writefds,
IN OUT fd_set FAR *exceptfds,
IN const struct timeval FAR *timeout);
nfds - Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
readfds -Optional pointer to a set of sockets to be checked for readability.
writefds -Optional pointer to a set of sockets to be checked for writability.
exceptfds -Optional pointer to a set of sockets to be checked for errors.
timeout -Maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to null for blocking operations.
使用該模型時,在服務端(select主要用在服務端處理多個客戶請求上)咱們能夠開闢兩個線程,一個線程用來監聽客戶端的鏈接請求,另外一個用來處理客戶端的請求。就這樣不須要一個一個客戶請求對應一個服務器處理線程,減小了線程的開銷。
select容許進程指示內核等待多個事件中的任何一個發生,並僅在有一個或多個時間發生或經歷一段指定時間後才喚醒它。select告訴內核對哪些描述子感興趣以及等待多長時間。這就是所謂的非阻塞模型,就是進程或線程執行此函數時沒必要非要等待事件的發生,一旦執行確定返回,以返回值的不一樣來反映函數的執行狀況,若是事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,因此效率較高。
select自己是會阻塞的,咱們可使用select實現阻塞式套接字(如上),也能夠實現異步套接字。我我的對實現異步套接字的理解是:你能夠單獨使用一個線程來進行你select,也就是說select阻塞你單獨的線程,說白了就是讓線程來完成異步。
該模型有個最大的缺點就是,它須要一個死循環不停的去遍歷全部的客戶端套接字集合,詢問是否有數據到來,這樣,若是鏈接的客戶端不少,勢必會影響處理客戶端請求的效率,但它的優勢就是解決了每個客戶端都去開闢新的線程與其通訊的問題。
4.2 套接字IO模型:WSAAsyncSelect(異步選擇)
若是有一個模型,能夠不用去輪詢客戶端套接字集合,而是等待系統通知,當有客戶端數據到來時,系統自動的通知咱們的程序,這就解決了select模型帶來的問題了。
因而WSAAsyncSelect模型登場了,WSAAsyncSelect模型就是這樣一個解決了普通select模型問題的socket編程模型。它是在有客戶端數據到來時,系統發送消息給咱們的程序,咱們的程序只要定義好消息的處理方法就能夠了,用到的函數只要是WSAAsyncSelect。
The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.
int WSAAsyncSelect(
__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
s -A descriptor that identifies the socket for which event notification is required.
hWnd -A handle that identifies the window that will receive a message when a network event occurs.
wMsg -A message to be received when a network event occurs.
lEvent -A bitmask that specifies a combination of network events in which the application is interested.
WSAAsyncSelect模型將套接字和Windows消息機制很好地粘合在一塊兒,爲用戶異步SOCKET應用提供了一種較優雅的解決方案。
WSAAsyncSelect模型是很是簡單的模型,它解決了普通select模型的問題,可是它最大的缺點就是它只能用在Windows程序上,由於它須要一個接收系統消息的窗口句柄,那麼有沒有一個模型既能夠解決select模型的問題,又不限定只能是Windows程序才能用呢?請看下節。
4.3 套接字IO模型:WSAEventSelect(事件選擇)
WSAEventSelect模型是一個不用主動去輪詢全部客戶端套接字是否有數據到來的模型,它也是在客戶端有數據到來時,系統發送通知給咱們的程序,可是,它不是發送消息,而是經過事件的方式來通知咱們的程序,這就解決了WSAEventSelect模型只能用在Windows程序的問題。
該模型的實現,咱們也能夠開闢兩個線程來進行處理,一個用來接收客戶端的鏈接請求,一個用來與客戶端進行通訊,用到的主要函數有:WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents。
The WSACreateEvent function creates a new event object.
WSAEVENT WSACreateEvent(void);
The WSAEventSelect function specifies an event object to be associated with the specified set of FD_XXX network events.
int WSAEventSelect(
__in SOCKET s,
__in WSAEVENT hEventObject,
__in long lNetworkEvents
);
The WSAWaitForMultipleEvents function returns when one or all of the specified event objects are in the signaled state, when the time-out interval expires, or when an I/O completion routine has executed.
DWORD WSAWaitForMultipleEvents(
__in DWORD cEvents,
__in const WSAEVENT* lphEvents,
__in BOOL fWaitAll,
__in DWORD dwTimeout,
__in BOOL fAlertable
);
The WSAEnumNetworkEvents function discovers occurrences of network events for the indicated socket, clear internal network event records, and reset event objects (optional).
int WSAEnumNetworkEvents(
__in SOCKET s,
__in WSAEVENT hEventObject,
__out LPWSANETWORKEVENTS lpNetworkEvents
);
The WSACloseEvent function closes an open event object handle.
BOOL WSACloseEvent(
__in WSAEVENT hEvent
);
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 全局變量 int g_nTotalConn = 0 ; SOCKET g_ClientSocket[MAXIMUM_WAIT_OBJECTS]; WSAEVENT g_ClientEvent[MAXIMUM_WAIT_OBJECTS]; //…… while (TRUE) { // Accept a connection sClient = accept(sListen, ( struct sockaddr *)&client, &nAddrSize); printf( "Accepted client:%s:%d\n" , inet_ntoa(client.sin_addr), ntohs(client.sin_port)); // Associate socket with network event g_ClientSocket[g_nTotalConn] = sClient; g_ClientEvent[g_nTotalConn] = WSACreateEvent(); WSAEventSelect(g_ClientSocket[g_nTotalConn], g_ClientEvent[g_nTotalConn], FD_READ | FD_CLOSE); g_nTotalConn++; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
DWORD WINAPI WorkerThread(LPVOID lpParam)
{ int nRet = 0 ; int nIndex = 0 ; WSANETWORKEVENTS NetworkEvents; char szMessage[MSG_SIZE] = { 0 }; while (TRUE) { nRet = WSAWaitForMultipleEvents(g_nTotalConn, g_ClientEvent, FALSE, 1000 , FALSE); //注意這裏應該有相應的修正的地方,WSAWaitForMultipleEvents函數在fWaitAll設置成FALSE //的時候只能指定一個事件對象受信,解決方法使用for循環進行循環檢測 if (nRet == WSA_WAIT_FAILED || nRet == WSA_WAIT_TIMEOUT) { continue ; } nIndex = nRet - WSA_WAIT_EVENT_0; //查看發生了什麼網絡事件 WSAEnumNetworkEvents(g_ClientSocket[nIndex], g_ClientEvent[nIndex], &NetworkEvents); if (NetworkEvents.lNetworkEvents & FD_READ) { // Receive message from client nRet = recv(g_ClientSocket[nIndex], szMessage, MSG_SIZE, 0 ); if (nRet == 0 || (nRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) { Cleanup(nIndex); } else { szMessage[nRet] = '\0' ; send(g_ClientSocket[nIndex], szMessage, strlen(szMessage), 0 ); } } if (NetworkEvents.lNetworkEvents & FD_CLOSE) { Cleanup(nIndex); } } return 0 ; } |
該模型經過一個死循環裏面調用WSAWaitForMultipleEvents函數來等待客戶端套接字對應的Event的到來,一旦事件通知到達,就經過該套接字去接收數據。雖然WsaEventSelect模型的實現較前兩種方法複雜,但它在效率和兼容性方面是最好的。
4.4 套接字IO模型:Overlaped(重疊)
以上三種模型雖然在效率方面有了很多的提高,但它們都存在一個問題,就是都預設了只能接收64個客戶端鏈接,雖然咱們在實現時能夠不受這個限制,可是那樣,它們所帶來的效率提高又將打折扣,那又有沒有什麼模型能夠解決這個問題呢?
固然有,它就是Overlaped模型。
優勢:
一、能夠運行在支持Winsock2的全部Windows平臺 ,而不像完成端口只是支持NT系統。
二、比起阻塞、非阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,Overlapped I/O模型使應用程序能達到更佳的系統性能。
由於它和這5種模型不一樣的是:使用重疊模型的應用程序通知緩衝區收發系統直接使用數據,也就是說,若是應用程序投遞了一個10KB大小的緩衝區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩衝區。
而這5種模型種,數據到達並拷貝到單套接字接收緩衝區中,此時應用程序會被告知能夠讀入的容量。當應用程序調用接收函數以後,數據才從單套接字緩衝區拷貝到應用程序的緩衝區,差異就體現出來了。
三、從《Windows網絡編程》中提供的試驗結果中能夠看到,在使用了P4 1.7G Xero處理器(CPU很強啊)以及768MB的迴應服務器中,最大能夠處理4萬多個SOCKET鏈接,在處理1萬2千個鏈接的時候CPU佔用率才40% 左右(很是好的性能,已經直逼完成端口了^_^),不再被限制在64個客戶端鏈接數了,並且性能槓槓的!
原理:
歸納一點說,重疊模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個Winsock I/O請求。針對這些提交的請求,在它們完成以後,應用程序會收到通知,因而就能夠經過本身另外的代碼來處理這些數據了。
須要注意的是,有兩個方法能夠用來管理重疊IO請求的完成狀況(就是說接到重疊操做完成的通知):
一、事件對象通知(event object notification)
二、完成例程(completion routines),注意,這裏並非完成端口
咱們知道WinSock2擴展中支持重疊IO模型,既然要使用重疊結構,咱們經常使用的send、sendto、recv、recvfrom也都要被WSASend、WSASendto、WSARecv、WSARecvFrom替換掉了,這裏只須要注意一點,它們的參數中都有一個Overlapped參數,咱們能夠假設是把咱們的WSARecv這樣的操做操做"綁定"到這個重疊結構上,提交一個請求,其餘的事情就交給重疊結構去操心,而其中重疊結構又要與Windows的事件對象"綁定"在一塊兒,這樣咱們調用完WSARecv之後就能夠"不勞而獲",等到重疊操做完成之後,天然會有與之對應的事件來通知咱們操做完成,而後咱們就能夠來根據重疊操做的結果取得咱們想要德數據了。
WinSock重疊IO的基礎是Windows的重疊IO機制。
BOOL WINAPI ReadFile(
__in HANDLE hFile,
__out LPVOID lpBuffer,
__in DWORD nNumberOfBytesToRead,
__out LPDWORD lpNumberOfBytesRead,
__in LPOVERLAPPED lpOverlapped
);
若是咱們在CreateFile的時候沒有使用FILE_FLAG_OVERLAPPED標誌,同時在調用ReadFile的時候把lpOverlapped這個參數設置的是null,那麼ReadFile這個函數的調用一直要到讀取完數據指定的數據後纔會返回,若是沒讀取完,就會阻塞在這裏。一樣 ,writefile和ReadFile都是這樣的。這樣在讀寫大文件的時候,咱們不少時間都浪費在等待ReadFile和writefile的返回上面。若是ReadFile和WriteFile是往管道里讀寫數據,那麼有可能阻塞得更久,致使程序性能降低。爲了解決這個問題,windows引進了重疊io的概念,一樣是上面的ReadFile和WriteFile,若是在CreateFile的時候設置了file_flag_overlapped ,那麼在調用ReadFile和WriteFile的時候就能夠給他們最後一個參數傳遞一個overlapped結構。這樣ReadFile或者WriteFile的調用立刻就會返回,這時候你能夠去作你要作的事,系統會自動替你完成ReadFile或者WriteFile,在你調用了ReadFile或者WriteFile後,你繼續作你的事,系統同時也幫你完成ReadFile或WriteFile的操做,這就是所謂的重疊。使用重疊io還有一個好處,就是你能夠同時發出幾個ReadFile或者WriteFile的調用,而後用WaitForSingleObject或者WaitForMultipleObjects來等待操做系統的操做完成通知,在獲得通知信號後,就能夠用GetOverlappedResult來查詢IO調用的結果。
舉個例子:
你想當你有這樣一個請求,就是
readfile(...) //1
writefile(...) //2
readfile(...) //3
你在程序中若是使用同步的話,那只有當你完成1之後2纔會繼續執行,2執行完之後3纔會繼續執行,這就是同步。
當若是使用異步的話,當系統遇到1時,ok,開一線程給它去完成該io請求,而後系統繼續運行2,3,分別開兩線程。 1-2-3若是是比較耗時的操做,尤爲是運用在網絡上,那麼1-2-3這三個io請求是並行的,也就是重疊的。
4.4.1 基於事件通知的重疊I/O模型
The WSARecv function receives data from a connected socket.
int WSARecv(
__in SOCKET s,
__in_out LPWSABUF lpBuffers,
__in DWORD dwBufferCount,
__out LPDWORD lpNumberOfBytesRecvd,
__in_out LPDWORD lpFlags,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
while
(
1
) { Flags = 0 ; rc = WSARecv(ConnSocket, &DataBuf, 1 , &RecvBytes, &Flags, &RecvOverlapped, NULL ); if ( (rc == SOCKET_ERROR) && (WSA_IO_PENDING != (err = WSAGetLastError()))) { fprintf(stderr, "WSARecv failed: %d\n" , err); break ; } rc = WSAWaitForMultipleEvents( 1 , &RecvOverlapped.hEvent, TRUE, INFINITE, TRUE); if (rc == WSA_WAIT_FAILED) { fprintf(stderr, "WSAWaitForMultipleEvents failed: %d\n" , WSAGetLastError()); break ; } rc = WSAGetOverlappedResult(ConnSocket, &RecvOverlapped, &RecvBytes, FALSE, &Flags); if (rc == FALSE) { fprintf(stderr, "WSARecv operation failed: %d\n" , WSAGetLastError()); break ; } printf( "Read %d bytes\n" , RecvBytes); WSAResetEvent(RecvOverlapped.hEvent); // If 0 bytes are received, the connection was closed if (RecvBytes == 0 ) break ; } |
4.4.2 基於完成例程的重疊I/O模型
完成例程(Completion Routine)並不是是你們所常聽到的"完成端口"(Completion Port),而是另一種管理重疊I/O請求的方式。
若是你想要使用重疊I/O機制帶來的高性能模型,又懊惱於基於事件通知的重疊模型要收到64個等待事件的限制,還有點畏懼完成端口稍顯複雜的初始化過程,那麼"完成例程"無疑是你最好的選擇!^_^由於完成例程擺脫了事件通知的限制,能夠連入任意數量客戶端而不用另開線程,也就是說只用很簡單的一些代碼就能夠利用Windows內部的I/O機制來得到網絡服務器的高性能。
並且我的感受"完成例程"的方式比重疊I/O更好理解,由於就和咱們傳統的"回調函數"是同樣的,也更容易使用一些,推薦!
基於事件通知的重疊I/O模型,在你投遞了一個請求之後(好比WSARecv),系統在完成之後是用事件來通知你的,而在完成例程中,系統在網絡操做完成之後會自動調用你提供的回調函數,區別僅此而已,是否是很簡單呢?
採用完成例程的服務端,通訊流程是這樣的:
從圖中能夠看到,服務器端存在一個明顯的異步過程,也就是說咱們把客戶端連入的SOCKET與一個重疊結構綁定以後,即可以將通信過程全權交給系統內部本身去幫咱們調度處理了(該過程見途中灰色部分),咱們在主線程中就能夠去作其餘的事情,邊等候系統完成的通知(調用事前註冊的完成例程回調函數)就OK,這也就是完成例程高性能的緣由所在。
有趣的比方:完成例程的處理過程,也就像咱們告訴系統,說"我想要在網絡上接收網絡數據,你去幫我辦一下"(投遞WSARecv操做),"不過我並不知道網絡數據合適到達,總之在接收到網絡數據以後,你直接就調用我給你的這個函數(好比_CompletionProess),把他們保存到內存中或是顯示到界面中等等,全權交給你處理了",因而乎,系統在接收到網絡數據以後,一方面系統會給咱們一個通知,另外同時系統也會自動調用咱們事先準備好的回調函數,就不須要咱們本身操心了。
完成例程回調函數原型及傳遞方式:
Void CALLBACK _CompletionRoutineFunc(
DWORD dwError, // 標誌我們投遞的重疊操做,好比WSARecv,完成的狀態是什麼
DWORD cbTransferred, // 指明瞭在重疊操做期間,實際傳輸的字節量是多大
LPWSAOVERLAPPED lpOverlapped, // 參數指明傳遞到最初的IO調用內的一個重疊結構
DWORD dwFlags // 返回操做結束時可能用的標誌(通常沒用)
);
由於咱們須要給系統提供一個如上面定義的那樣的回調函數,以便系統在完成了網絡操做後自動調用,這裏就須要提一下到底是如何把這個函數與系統內部綁定的呢?以下所示,在WSARecv函數中是這樣綁定的:最後一個參數
The WSARecv function receives data from a connected socket.
int WSARecv(
__in SOCKET s,
__in_out LPWSABUF lpBuffers,
__in DWORD dwBufferCount,
__out LPDWORD lpNumberOfBytesRecvd,
__in_out LPDWORD lpFlags,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
小結:
重疊模型的缺點:它爲每個IO請求都開了一個線程,當同時有1000個請求發生,那麼系統處理線程上下文[context]切換也是很是耗時的,因此這也就引起了完成端口模型iocp,用線程池來解決這個問題,這是下節要學習的內容。
4.5 套接字IO模型:Completion port(完成端口)
IOCP(I/O Completion Port,I/O完成端口)是性能最好的一種I/O模型。
它是應用程序使用線程池處理異步I/O請求的一種機制。在處理多個併發的異步I/O請求時,以往的模型都是在接收請求是建立一個線程來應答請求。這樣就有不少的線程並行地運行在系統中。而這些線程都是可運行的,Windows內核花費大量的時間在進行線程的上下文切換,並無多少時間花在線程運行上。再加上建立新線程的開銷比較大,因此形成了效率的低下。
Windows Sockets應用程序在調用WSARecv()函數後當即返回,線程繼續運行。當系統接收數據完成後,向完成端口發送通知包(這個過程對應用程序不可見)。
應用程序在發起接收數據操做後,在完成端口上等待操做結果。當接收到I/O操做完成的通知後,應用程序對數據進行處理。
完成端口其實就是上面兩項的聯合使用基礎上進行了必定的改進。
一個完成端口其實就是一個通知隊列,由操做系統把已經完成的重疊I/O請求的通知放入其中。當某項I/O操做一旦完成,某個能夠對該操做結果進行處理的工做者線程就會收到一則通知。而套接字在被建立後,能夠在任什麼時候候與某個完成端口進行關聯。
衆所皆知,完成端口是在Windows平臺下效率最高,擴展性最好的IO模型,特別針對於WinSock的海量鏈接時,更能顯示出其威力。其實創建一個完成端口的服務器也很簡單,只要注意幾個函數,瞭解一下關鍵的步驟也就好了。
從本質上說,完成端口模型要求咱們建立一個Win32完成端口對象(內核對象),經過指定數量的線程對重疊I/O請求進行管理,以便爲已經完成的重疊I/O請求提供服務。要注意的是,所謂"完成端口",實際是Win3二、Windows NT以及Windows 2000採用的一種I/O構造機制,除套接字句柄以外,實際上還可接受其餘東西。然而,本文只打算講述如何使用套接字句柄,來發揮完成端口模型的巨大威力。使用這種模型以前,首先要建立一個I/O完成端口對象,用它面向任意數量的套接字句柄。管理多個I/O請求。要作到這—點,須要調用CreateIoCompletionPort函數。該函數定義以下:
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);
5 原始套接字
通常狀況下程序設計人員主要接觸如下兩類套接字:
流式套接字(SOCK_STREAM): 面向鏈接的套接字,對應於 TCP 應用程序。
數據包套接字(SOCK_DGRAM): 無鏈接的套接字,對應於UDP 應用程序。
這一類套接字爲標準套接字。此外,還有一類原始套接字,它是一種對原始網絡報文進行處理的套接字。原始套接字的用途主要有:
發送自定義的IP 數據報
發送ICMP 數據報
網卡的偵聽模式,監聽網絡上的數據包。
假裝IP地址。
自定義協議的實現。
原始套接字主要應用在底層網絡編程上,同時也是網絡黑客的必備手段。eg:sniffer、拒絕服務(DoS)、IP 地址欺騙等都須要在原始套接字的基礎上實現。
原始套接字與標準套接字之間的關係以下圖所示。標準套接字與網絡協議棧的TCP、UDP 層打交道,而原始套接字則與IP層級網絡協議棧核心打交道。
網絡監聽技術很大程度上依賴於SOCKET_RAW。
要使用原始套接字,必須通過建立原始套接字、設置套接字選項和建立並填充相應協議頭這三個步驟,而後用send、WSASend函數將組裝好的數據發送出去。接收的過程也很類似,只是須要用recv或WSARecv函數接收數據。
SOCKET sock;
Sock=socket (AF_INET, SOCK_RAW, IPPROTO_UDP);
int setsocketopt (SOCKET s, int level, int optname, const char FAR *optval, int optlen);
struct TCP
{
unsigned short tcp_sport;
unsigned short tcp_dport;
unsigned int tcp_seq;
unsigned int tcp_ack;
unsigned char tcp_lenres;
unsigned char tcp_flag;
unsigned short tcp_win;
unsigned short tcp_sum;
unsigned short tcp_urp;
};
raw socket(原始套接字)工做原理與規則
http://www.javashuo.com/article/p-tpsfjohl-ch.html