Windows Socket知識總結


 目錄

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是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在建立連接打開后,可以向自己文件寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉文件。
套接字是通信的基石,可看作不同主機的進程進行雙向通信的端點。
套接字有兩種不同的類型:流式套接字和數據報套接字。
套接字可處於阻塞模式或非阻塞模式。

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操作,而且應用程序很難同時通過多個建好連接的套接字進行通信。
可見,在默認情況下套接字為阻塞模式。
這種情況下一般采用多線程方式,在不同的線程中進行不同的連接處理來避免阻塞,但是多線程會增加系統開銷,而且線程同步會增加復雜度。

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.

 

 代碼片段(基於TCP)
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錯誤表示請求的操作在調用期間沒有時間完成。由於會不斷地返回這個錯誤,所以程序員需要通過不斷地檢查函數返回碼以判斷一個套接字何時可供讀寫。

 

 C++ Code 
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進行管理,它們包括:

  • select(選擇)
  • WSAAsyncSelect(異步選擇)
  • WSAEventSelect(事件選擇)
  • Overlaped(重疊)
  • Completion port(完成端口)

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模型。
優點:
1、可以運行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系統。
2、比起阻塞、非阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,Overlapped I/O模型使應用程序能達到更佳的系統性能。
因為它和這5種模型不同的是:使用重疊模型的應用程序通知緩沖區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩沖區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩沖區。
而這5種模型種,數據到達並拷貝到單套接字接收緩沖區中,此時應用程序會被告知可以讀入的容量。當應用程序調用接收函數之后,數據才從單套接字緩沖區拷貝到應用程序的緩沖區,差別就體現出來了。
3、從《Windows網絡編程》中提供的試驗結果中可以看到,在使用了P4 1.7G Xero處理器(CPU很強啊)以及768MB的回應服務器中,最大可以處理4萬多個SOCKET連接,在處理1萬2千個連接的時候CPU占用率才40% 左右(非常好的性能,已經直逼完成端口了^_^),再也不被限制在64個客戶端連接數了,而且性能杠杠的!
原理:
概括一點說,重疊模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個Winsock I/O請求。針對這些提交的請求,在它們完成之后,應用程序會收到通知,於是就可以通過自己另外的代碼來處理這些數據了。
需要注意的是,有兩個方法可以用來管理重疊IO請求的完成情況(就是說接到重疊操作完成的通知):
1、事件對象通知(event object notification)
2、完成例程(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
);

 

 C++ Code 
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請求提供服務。要注意的是,所謂"完成端口",實際是Win32、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(原始套接字)工作原理與規則
https://blog.csdn.net/bcbobo21cn/article/details/51330174


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM