Windows完成端口編程


Windows完成端口編程
目錄
一 基本概念
二 OVERLAPPED數據結構
三 完成端口的內部機制
創建完成端口
完成端口線程的工作原理
線程間數據傳遞
線程的安全退出

一 基本概念
       設備---windows操作系統上允許通信的任何東西,比如文件、目錄、串行口、並行口、郵件槽、命名管道、無名管道、套接字、控制台、邏輯磁盤、物理 磁盤等。絕大多數與設備打交道的函數都是CreateFile/ReadFile/WriteFile等。所以我們不能看到**File函數就只想到文件 設備。

       與設備通信有兩種方式,同步方式和異步方式。同步方式下,當調用ReadFile函數時,函數會等待系統執行完所要求的工作,然后才返回;異步方式下,ReadFile這類函數會直接返回,系統自己去完成對設備的操作,然后以某種方式通知完成操作。

       重疊I/O----顧名思義,當你調用了某個函數(比如ReadFile)就立刻返回做自己的其他動作的時候,同時系統也在對I/0設備進行你要求的操 作,在這段時間內你的程序和系統的內部動作是重疊的,因此有更好的性能。所以,重疊I/O是用於異步方式下使用I/O設備的。

重疊I/O需要使用的一個非常重要的數據結構OVERLAPPED。

       完成端口---是一種WINDOWS內核對象。完成端口用於異步方式的重疊I/0情況下,當然重疊I/O不一定非使用完成端口不可,還有設備內核對象、事 件對象、告警I/0等。但是完成端口內部提供了線程池的管理,可以避免反復創建線程的開銷,同時可以根據CPU的個數靈活的決定線程個數,而且可以讓減少 線程調度的次數從而提高性能。

二 OVERLAPPED數據結構
typedef struct _OVERLAPPED 
{
    ULONG_PTR Internal;//被系統內部賦值,用來表示系統狀態
    ULONG_PTR InternalHigh;// 被系統內部賦值,傳輸的字節數 
    union {
        struct
             {
                DWORD Offset;//和OffsetHigh合成一個64位的整數,用來表示從文件頭部的多少字節開始
                DWORD OffsetHigh;//操作,如果不是對文件I/O來操作,則必須設定為0
             };
        PVOID Pointer;
    };
    HANDLE  hEvent;//如果不使用,就務必設為0,否則請賦一個有效的Event句柄
} OVERLAPPED, *LPOVERLAPPED;


下面是異步方式使用ReadFile的一個例子
OVERLAPPED Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;
//假定其他參數都已經被初始化
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
這樣就完成了異步方式讀文件的操作,然后ReadFile函數返回,由操作系統做自己的事情吧

下面介紹幾個與OVERLAPPED結構相關的函數
等待重疊I/0操作完成的函數
BOOL GetOverlappedResult (
ANDLE hFile,
LPOVERLAPPED lpOverlapped,//接受返回的重疊I/0結構
LPDWORD lpcbTransfer,//成功傳輸了多少字節數
BOOL fWait //TRUE只有當操作完成才返回,FALSE直接返回,如果操作沒有完成,通過調//用GetLastError ( )函數會返回ERROR_IO_INCOMPLETE
);

宏HasOverlappedIoCompleted可以幫助我們測試重疊I/0操作是否完成,該宏對OVERLAPPED結構的Internal成員進行了測試,查看是否等於STATUS_PENDING值。


三 完成端口的內部機制
創建完成端口

       完成端口是一個內核對象,使用時他總是要和至少一個有效的設備句柄進行關聯,完成端口是一個復雜的內核對象,創建它的函數是:
HANDLE CreateIoCompletionPort(
    IN HANDLE FileHandle,
    IN HANDLE ExistingCompletionPort,
    IN ULONG_PTR CompletionKey,
    IN DWORD NumberOfConcurrentThreads );

通常創建工作分兩步:

第一步,創建一個新的完成端口內核對象,可以使用下面的函數:

HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
{
 return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
};

第二步,將剛創建的完成端口和一個有效的設備句柄關聯起來,可以使用下面的函數:

 bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
{
          HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
          return h==hCompPort;
};

說明
1)  CreateIoCompletionPort函數也可以一次性的既創建完成端口對象,又關聯到一個有效的設備句柄
2)  CompletionKey是一個可以自己定義的參數,我們可以把一個結構的地址賦給它,然后在合適的時候取出來使用,最好要保證結構里面的內存不是分配在棧上,除非你有十分的把握內存會保留到你要使用的那一刻。
3)  NumberOfConcurrentThreads通常用來指定要允許同時運行的的線程的最大個數。通常我們指定為0,這樣系統會根據CPU的個數來自動確定。
創建和關聯的動作完成后,系統會將完成端口關聯的設備句柄、完成鍵作為一條紀錄加入到這個完成端口的設備列表中。如果你有多個完成端口,就會有多個對應的設備列表。如果設備句柄被關閉,則表中自動刪除該紀錄。

完成端口線程的工作原理
       完成端口可以幫助我們管理線程池,但是線程池中的線程需要我們使用_beginthreadex來創建,憑什么通知完成端口管理我們的新線程呢?答案在函數GetQueuedCompletionStatus。該函數原型:
BOOL GetQueuedCompletionStatus(
    IN  HANDLE CompletionPort,
    OUT LPDWORD lpNumberOfBytesTransferred,
    OUT PULONG_PTR lpCompletionKey,
    OUT LPOVERLAPPED *lpOverlapped,
    IN  DWORD dwMilliseconds
);

這個函數試圖從指定的完成端口的I/0完成隊列中抽取紀錄。只有當重疊I/O動作完成的時候,完成隊列中才有紀錄。凡是調用這個函數的線程將被放入到完成端口的等待線程隊列中,因此完成端口就可以在自己的線程池中幫助我們維護這個線程。
完 成端口的I/0完成隊列中存放了當重疊I/0完成的結果---- 一條紀錄,該紀錄擁有四個字段,前三項就對應GetQueuedCompletionStatus函數的2、3、4參數,最后一個字段是錯誤信息 dwError。我們也可以通過調用PostQueudCompletionStatus模擬完成了一個重疊I/0操作。
當I/0完成隊列中出現 了紀錄,完成端口將會檢查等待線程隊列,該隊列中的線程都是通過調用GetQueuedCompletionStatus函數使自己加入隊列的。等待線程 隊列很簡單,只是保存了這些線程的ID。完成端口會按照后進先出的原則將一個線程隊列的ID放入到釋放線程列表中,同時該線程將從等待 GetQueuedCompletionStatus函數返回的睡眠狀態中變為可調度狀態等待CPU的調度。
基本上情況就是如此,所以我們的線程要想成為完成端口管理的線程,就必須要調用
GetQueuedCompletionStatus函數。出於性能的優化,實際上完成端口還維護了一個暫停線程列表,具體細節可以參考《Windows高級編程指南》,我們現在知道的知識,已經足夠了。

線程間數據傳遞

       線程間傳遞數據最常用的辦法是在_beginthreadex函數中將參數傳遞給線程函數,或者使用全局變量。但是完成端口還有自己的傳遞數據的方法,答案就在於CompletionKey和OVERLAPPED參數。
CompletionKey 被保存在完成端口的設備表中,是和設備句柄一一對應的,我們可以將與設備句柄相關的數據保存到CompletionKey中,或者將 CompletionKey表示為結構指針,這樣就可以傳遞更加豐富的內容。這些內容只能在一開始關聯完成端口和設備句柄的時候做,因此不能在以后動態改 變。
OVERLAPPED參數是在每次調用ReadFile這樣的支持重疊I/0的函數時傳遞給完成端口的。我們可以看到,如果我們不是對文件設 備做操作,該結構的成員變量就對我們幾乎毫無作用。我們需要附加信息,可以創建自己的結構,然后將OVERLAPPED結構變量作為我們結構變量的第一個 成員,然后傳遞第一個成員變量的地址給ReadFile函數。因為類型匹配,當然可以通過編譯。當GetQueuedCompletionStatus函 數返回時,我們可以獲取到第一個成員變量的地址,然后一個簡單的強制轉換,我們就可以把它當作完整的自定義結構的指針使用,這樣就可以傳遞很多附加的數據 了。太好了!只有一點要注意,如果跨線程傳遞,請注意將數據分配到堆上,並且接收端應該將數據用完后釋放。我們通常需要將ReadFile這樣的異步函數 的所需要的緩沖區放到我們自定義的結構中,這樣當GetQueuedCompletionStatus被返回時,我們的自定義結構的緩沖區變量中就存放了 I/0操作的數據。
CompletionKey和OVERLAPPED參數,都可以通過GetQueuedCompletionStatus函數獲得。
線程的安全退出
      很多線程為了不止一次的執行異步數據處理,需要使用如下語句

while (true)
{

     ....
     GetQueuedCompletionStatus(...);       
}

那么如何退出呢,答案就在於上面曾提到的PostQueudCompletionStatus函數,我們可以用它發送一個自定義的包含了OVERLAPPED成員變量的結構地址,里面包含一個狀態變量,當狀態變量為退出標志時,線程就執行清除動作然后退出。

8.2.5完成端口模型
創建完成端口對象 CreateCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads);
CompletionKey:則指定要與某個特定套接字句柄關聯在一起的“單句柄數據”
1.工作者線程與完成端口
StartWinsock();
//step 1
//create an IO completion port.

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0);

//step 2;
//Determine how many processors are on the system

GetSystemInfo(&SystemInfo);

//Step3
//create worker threads based on the number of processors available on the system .For this simple case, we create one worker thread for each processor.
for(i=0; i < SystemInfo.dwNumberOfProcessors; i++)
{
   HANDLE ThreadHandle;
   //create a server worker thread and pass the completion port to the thread, Note:the ServerWorkerThread procedure is not defined in this listing.
   ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,0, &ThreadID);

   //close the thread handle
   CloseHandle(ThreadHandle);
}

//step 4
//create a listening socket
Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));

//prepare socket for listening
listen(Listen, 5);

while (TRUE)
{
 //step 5
 //Accept connections and assign to completion port.
 Accept = WSAAccept(Listen, NULL, NULL, NULL, 0);

 //step 6
 //create per-handle data information structure to associate with the socket
 PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));

 printf("Socket number %d connected/n",Accept);
 PerHandleData->Socket = Accept;

 //step 7
 //Associate the accepted socket with the completion port.
 CreateIoCompletionPort((HANDLE)Accept,CompletionPort, (DWORD)PerHandleData,0);

 //step 8
 //start processing IO on the accepted socket. post oen or more WSASend() or WSARecv() calls on the socket using overlapped IO
 WSARecv(...)''
}

2.完成端口和重疊IO
需要由我們的應用程序負責在以后的某個時間,通過一個OVERLAPPED結構,來接收調用的結果。
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);

3.單句柄數據和單IO操作數據
typedef struct 
{
 OVERLAPPED Overlapped;
 WSABUF     DataBuf;
 CHAR       Buffer[DATA_BUFSIZE];
 bool       OperationType;
} PER_IO_OPERATION_DATA;

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
  HANDLE CompletionPort = (HANDLE).CompletionPortID;
  DWORD  ByteTransferred;
  LPOVERLAPPED Overlapped;
  LPPER_HANDLE_DATA PerHandleData;
  LPPER_IO_OPERATION_DATA PerIoData;
  DWORD SendBytes, RecvBytes;
  DWORD Flags;

  while (TRUE)
  {
   //Wait for IO to complete on any socket associated with the completion port
   GetQueuedCompletionStatus(CompletionPort,&ByteTransferred, (LPDWORD)&PerHandleData,(LPOVERLAPPED*)&PerIoData, INFINITE);

   //socket and clean up the per-handle data and per-io opertion data associated with the socket
   if (BytesTransferred ==0&&(PerIoData->OperationType == RECV_POSTED||PerHandleData->OperationType == SEND_POSTED))
   {
    //A zero bytesTransferred indicates that the socket has been closed by the peer, so you should close the socket.
    //Note: Per-handle data was used to reference the socket associated withe IO operation.
    closesocket(PerHandleData->Socket);

    GlobalFree(PerHandleData);
    GlobalFree(PerIoData);
    continue;
   }

   //service the completed IO request, You can determine which IO request has just completed by looking at the OperationType field contained in the 
   //the per-io operation data.
     if (PerIoData->OperationType==RECV_POSTED)
  {
   //Do sth with the received data in perIoData->Buffer
     }

  //post another WSASend or WSARecv operation .
  //As an example , we will post another WSARecv() IO operation

  Flags = 0;

  //Set up the per-IO operation data for the next overlapped call
  ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));

  PerIoData->DataBuf.len = DATA_BUFSIZE;
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->OperationType = RECV_POSTED;

     WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf),1,&RecvBytes, &Flags, &(PerIoData->Overlapped),NULL);
  }
}

8.2.3WSAEventSelect
應用程序在一個或者多個套接字上,接收以事件為基礎的網絡事件通知。網絡事件投遞至一個事件對象句柄。
事件通知
WSAVENT WSACreateEvent(void);
BOOL    WSAResetEvent(WSAEVENT hEvent);
BOOL    WSACloseEvent(WSAEVENT hEvent);
int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);

DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);

Index = WSAWaitForMultipleEvents();
MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);

SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
SOCKET  Accept, Listen;
DWORD   EventTotal = 0;
DWORD   Index;

//Set up a TCP socket for listening on port 5150;
Listen = socket(PF_INET, SOCKET_STREAM, 0);

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5105);

bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));

NewEvent = WSACreateEvent();

WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);

listen(Listen, 5);

Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;

while(TRUE)
{
    //Wait for network events on all sockets
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
    
    WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);
    
    //check for FD_ACCEPT messages
    if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
     {
        if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
         {
            printf("FD_ACCEPT failed with error%d/n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
            break;
         }       
     
     //Accept a new connection and it to the socket and event lists
     Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);
     
     //We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close  the accepted socket
     if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
     {
       printf("Too many connecttions");
       closesocket(Accept);
       break;
     }
     
     NewEvent = WSACreateEvent();
    
     WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);
     
     Event[EventTotal] = NewEvent;
     Socket[EventTotal] = Accept;
     EventTotal++;
     
     printf("socket &d connected/n",ACCept);
   } 

    //Process FD_READ notification
    if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
    {
       if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
        {
          printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
          break;        
          
        }

        //Read data from socket
        recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
    }

    //Process FD_WRITE notification
    if(NetworkEvents.lNetworkEvents&FD_WRITE)
    {
       if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
       {
          printf("FD_WRITE failed with error %d/n",
          NetworkEvents.iErrorCode[FD_WRITE_BIT]);
          break;
       }
    
       send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);         
    }
   
    if(NetworkEvents.lNetworkEvents&FD_CLOSE)
    {
        if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
        {
            printf("FD_CLOSE failed with error &d/n",
            NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
            break;
        }
    }
     
    closesocket(Socket[index - WSA_WAIT_EVET_0]);
     
    //Remove socket and associated event from the socket and evnet array and decrement eventtatal
    CompressArrays(Event, socket,&EventTotal);
   }
}

8.2.4 重疊模型
s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
1.事件通知 將win32事件對象與WSAOVERLAPPED結構關聯在一起。
typedef struct WSAOVERLAPPED
{
   DWORD Internal;
   DWORD InternalHigh;
   DWORD offset;
   DWORD OffsetHigh;
   WSAEVENT hEvent;
} WSAOVERLAPPED, FAR*LPWSAOVERLAPPED;
hEvent 它允許應用程序將一個事件對象句柄同一個套接字關聯起來。

BOOL WSAGetOverlappedResult(SOCKET S, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWari, LPDWORD lpdwFlags);
void main()
{
   WSABUF DataBuf;
   DWORD  EventTotal = 0;
   WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
   WSAOVERLAPPED AcceptOverlapped;
   SOCKET        ListenSocket, AcceptSocket;

   //step 1
   //start winsock and set up a listening socket

   //step 2
   //accepte an inbound connection

   AcceptSocket = accept(ListenSocket, NULL, NULL);

   //step 3
   //set up an overlapped structure

   EventArray[EventTotal]=WSACreateEvent();
   ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));

   AcceptOverlapped.hEvent = EventArray[EventTotal];

   DataBuf.len = DATA_BUFSIZE;
   DataBuf.buf = buffer;

   EventTotal++;

   //step 4;
   //post a WSARecv request to begin receiving data on the socket.
   WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);

   //Process overlapped receives on the socket.
   while (TRUE)
   {
    //step 5
    //wait for overlapped io call to complete 
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);

    //Index should be 0 because we have one event handle in eventarray.

    //step 6
    //reset the signaled event 
    WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);

    //step 7
    //Determine the status of the overlapped request.
    WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);

    //First check to see whether the peer has closed the connection ,and if so , close the socket.
    if (BytesTransferred == 0)
    {
     printf("Closing socket %d/n", AcceptSocket);
     closesocket(AcceptSocket);
     WSACloseEvent(Index - WSA_WAIT_EVENT_0);
     return;
    }

    //Do something with the received data DataBuf contains the received data.
    ...

    //step 8
    //post another WSARecv() request on the socket
     
    Flags = 0;
    ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
    
    AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];

    DataBuf.len = DATA_BUFSIZE;
    DataBuf.buf = Buffer;

    WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);
   }
}

2.完成例程
在一個重疊IO請求完成時由系統調用的函數。他們的基本設計宗旨是通過調用者的線程,為一個已完成的I請求提供服務。除此之外,應用程序可通過完成例程,繼續進行重疊IO處理。
void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);

WSABUF DataBuf;
SOCKET AcceptSocket;

void main()
{
 WSAOVERLAPPED Overlapped;
  
   //step 1
   //start winsock and set up a listening socket

   //step 2
   //accept a new connection
 AcceptSocket = accept(ListenSocket, NULL, NULL);


   //step 3
   //To get the overlapped IO processing started, first submit an overlapped WSARecv() request.

   Flags = 0;
   ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));

   DataBuf.len = DATA_BUFSIZE;
   DataBuf.buf = buffer;

   EventTotal++;

   //step 4;
   //post a WSARecv request to begin receiving data on the socket.
  if(WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, WorkerRoutine)==SOCKET_ERROR)
  {
     if (WSAGetLastError()!=WSA_IO_PENDING)
  {
   printf("WSARecv() failed with error%d/n",WSAGetLastError());
   return;
     }
  }

   //since the WSAWaitForMutipleEvents()API requires waiting on one or more event objects, we will have to create a dummy event object, Aa
  //an alterative , we can use SleepEx() instead.
  EventArray[0]=WSACreateEvent();

   while (TRUE)
   {
    //step 5
    Index = WSAWaitForMultipleEvents(1,EventArray, FALSE, WSA_INFINITE, FALSE);

    //step 6
    if (Index == WAIT_IO_COMPLETION)
    {
     //An overlapped request completion routine just completed ,continue servicing more completion rountines.
    }
    else
    {
       //A bad error occurred --stop processing! if we were also processing an event object, this could be index to the event array.

    return;
    }
   }
}

void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags)
{
   DWORD SendBytes, RecvBytes;
   DWORD Flags;
   if (Error !=0)||BytesTransferred ==0 
   {
    //Either a bad error occurred on the socket or the socket was closed by peer closesocket(AcceptSocket);
    return;
   }

   //At this point, an overlapped WSARecv() request completed successfully. Now we can retrieve the received data that is contained in the variable DataBuf
   //After processing the received data, we need to post another overlapped WSARecv() or WSASend() request. For simplicity , we will post another WSARecv() requst.
   Flags = 0;
   ZeroMemory(&Overlapped,sizeof(WSAOVERLAPPED));

   DataBuf.len = DATA_BUFSIZE;
   DataBuf.buf = Buffer;

   if (WSARecv(AcceptSocket, &DataBuf,1,&RecvBytes,&Flags, &Overlapped, WorkerRoutine)==SOCKET_ERROR)
   {
    if (WSAGetLastError()!=WSA_IO_PENDING)
    {
     printf("failed with error%d/n",WSAGetLastError());
     return;
    }
   }
}

8.2.2 WSAAsyncSelect
接收以windows消息為基礎的網絡事件通知。 
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT和FD_CLOSE.
從鎖定變成非鎖定狀態。
#define WM_SOCKET WM_USER+1
#define

int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
{
   SOCKET Listen;
   HWND   Window;
   sockaddr  InternetAddr;

   Window = CreateWindow();

   WSAStartup(...);

   Listen = socket(...);

   InternetAddr.sin_family = AF_INET;
   InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
   InternetAddr.sin_port = htons(5150);

   bind(Listen, (PSOCKADDR)&InternetAddr,sizeof(InternetAddr));

   WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);

   listen(Listen, 5);

   //Translate and dispatch window messages until the application terminates
}

BOOL CALLBACK ServerWinProc(HWND hDlg, WORD wMsg,WORD wParam, DWORD lParam)
{
    SOCKET Accept;
 switch(wMsg)
 {
 case WM_PAINT:
  break;
 case WM_SOCKET:

   //Determine whether an error occurred on the socket by using the WSAGETSLECTERRROR() macro
  if (WSAGETSELECTERROR(lParam))
  {
   closesocket(wParam);
   break;
  }

  //Determine what event occurred on the socket
  switch(WSAGETSELECTEVENT(lParam))
  {
  case FD_ACCEPT:
   Accept = accept(wParam, NULL, NULL);

   //Prepare accepted socket for read, write and close notification.
   WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
   break;
  case FD_READ:
   //Receive data from the socket in wparam;
   break;
  case  FD_WRITE:
   //The socket in param is ready for sending data;
   break;
  case FD_CLOSE:
   //The connection is now closed
   closesocket(wParam);
   break;
  }
  break;
 }
 return true;
}
產生FD_WRITE通知的三種條件
1.使用connect或者WASConnect,一個套接字首次建立了連接。
2.使用accept or WSAAccept,套接字被接受以后。
3.若send WSASend,sendto WSASendto操作失敗,返回了WSAEWOULDBLOCK錯誤,而且緩沖區的空間變得可用。

8.2.3WSAEventSelect
應用程序在一個或者多個套接字上,接收以事件為基礎的網絡事件通知。網絡事件投遞至一個事件對象句柄。
事件通知
WSAVENT WSACreateEvent(void);
BOOL    WSAResetEvent(WSAEVENT hEvent);
BOOL    WSACloseEvent(WSAEVENT hEvent);
int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);

DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);

Index = WSAWaitForMultipleEvents();
MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);

SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
SOCKET  Accept, Listen;
DWORD   EventTotal = 0;
DWORD   Index;

//Set up a TCP socket for listening on port 5150;
Listen = socket(PF_INET, SOCKET_STREAM, 0);

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5105);

bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));

NewEvent = WSACreateEvent();

WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);

listen(Listen, 5);

Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;

while(TRUE)
{
    //Wait for network events on all sockets
    Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
    
    WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);
    
    //check for FD_ACCEPT messages
    if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
     {
        if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
         {
            printf("FD_ACCEPT failed with error%d/n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
            break;
         }       
     
     //Accept a new connection and it to the socket and event lists
     Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);
     
     //We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close  the accepted socket
     if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
     {
       printf("Too many connecttions");
       closesocket(Accept);
       break;
     }
     
     NewEvent = WSACreateEvent();
    
     WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);
     
     Event[EventTotal] = NewEvent;
     Socket[EventTotal] = Accept;
     EventTotal++;
     
     printf("socket &d connected/n",ACCept);
   } 

    //Process FD_READ notification
    if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
    {
       if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
        {
          printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
          break;        
          
        }

        //Read data from socket
        recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
    }

    //Process FD_WRITE notification
    if(NetworkEvents.lNetworkEvents&FD_WRITE)
    {
       if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
       {
          printf("FD_WRITE failed with error %d/n",
          NetworkEvents.iErrorCode[FD_WRITE_BIT]);
          break;
       }
    
       send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);         
    }
   
    if(NetworkEvents.lNetworkEvents&FD_CLOSE)
    {
        if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
        {
            printf("FD_CLOSE failed with error &d/n",
            NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
            break;
        }
    }
     
    closesocket(Socket[index - WSA_WAIT_EVET_0]);
     
    //Remove socket and associated event from the socket and evnet array and decrement eventtatal
    CompressArrays(Event, socket,&EventTotal);
   }
}

第8章 WinsockIO方法
套接字模式:鎖定和非鎖定
8.1套接字模式
生產者-消費者
8.1.2非鎖定模式
SOCKET s;
unsigned long ul = 1;
int           nRet;

s = socket(AF_INET, SOCK_STREAM, 0);
nRet = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
if(nRet == SOCKET_ERROR)
{
....
}

8.2.1 select模型
利用這個函數,我們判斷套接字上是否存在數據,或者能否向一個套接字寫入數據。
int select(int nfds,
           fd_set FAR* readfds,
           fd_set FAR* writefds,
           fd_set FAR* exceptfds,
           const struct timeval FAR* timeout);

FD_CLR(s,*set);從set中刪除套接字s。
FD_ISSET(s,*set);檢查s是否在set中。
FD_SET(s,*set);將s加入集合set。
FD_ZERO(*set);將set初始化。

SOCKET s;
fd_set fdread;
int    ret;

//create a socket and accept a connection

//Manage I/O on the socket
while(1)
{
   FD_ZERO(&fdread);
   
   FD_SET(s,&fdread);
   
   if((ret = select(0, &fdread, NULL, NULL, NULL)== SOCKET_ERROR)
   { //ERROR }
    
   if(ret >0)
   {
     if(FD_ISSER(s, &fdread))
     {
      //a read event has occurred on socket s.
     }
   }
   
}

第7章 Winsock基礎
7.1Winsock的初始化
int WSAStartup(WORD wVersionRequested, LPWSASATA lpWSData);
7.2錯誤檢查和控制
int WSAGetLastError(void);
7.3面向連接的協議
7.3.1服務器API函數
1.bind 將指定的套接字同一個已經知道的地址綁定在一起。
2.listen(SOCKET s, int backlog);
3.accept和WSAAccept(SOCKET s, struct socket FAR*addr, int FAR* addrlen);
addr 返回客戶機的ip地址信息
accept返回一個新的套接字,它對應於已經接受的那個客戶機連接,對於該客戶機后續的所有操作,都使用這個新套接字。
原來的套接字仍然用於接受客戶機連接而處於監聽模式。

int CALLBACK ConditionFunc(
LPWSABUF lpCallerId,
LPWSABUF lpCallerData,
LPQQS    lpSOOS,
LPQOS    lpGOOS,
LPWSABUF lpCalleeId,
LPWSABUF lpCalleeData,
GROUP    FAR*g,
DWORD    dwCallbackData);

lpCallerId: 指定建立連接的協議 包含建立連接的那個客戶機的IP地址。
lpCallerData:包含了隨連接一道,由客戶機發出的任何連接數據。
lpSQOS,lpGQOS:對客戶機請求的任何一個服務質量參數進行指定。
lpCalleeId:結構中包含與客戶機需要與之連接的本地地址。
服務器將傳遞給條件的參數處理完之后,必須指出CF_ACCEPT, CF_REJECT , CF_DEFER.

 

3.1IP
網際協議:lan wan 是一個無連接的協議。
6.1.1TCP
Transmission Control Protocol,
6.1.2UDP
user Datagram Protocal
6.1.3定址
struct socketaddr_in
{
   short           sin_family;
   u_short         sin_port;
   struct in_addr  sin_addr;
   char            sin_zero[8];
};

端口分為:已知端口 已注冊端口和動態端口
0~1023固定服務 
1024~49151已注冊端口供普通用戶進程使用
49152~65535動態端口
unsiged long inet_addr(const char FAR* cp);//把點式的ip地址轉換成一個32位的無符號的長整數。
1.特殊地址
INADDR_ANy 允許服務器應用監聽主機計算機上面每個網絡接口上客戶機活動。一般情況下,在該地址綁定套接字和本地接口時,網絡應用才利用這個地址來監聽連接。
INADDR_BROADCAST用於在一個IP網絡中發送廣播UDP數據報。
2.字節排序
u_long htonl               u_long ntohl
int WSAHtonl               int WSANtohl
u_short htons();           u_short ntohs
int WSAHtons();            int WSANtohs

SOCKADDR_IN InternetAddr;
INT nPortId = 5150;

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = inet_addr("136.149.3.29");
InternetAddr.sin_port = htons(nPortId);
6.1.4創建套接字
s = socket(AF_INET, SOCK_STREAM,0);
s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WAS_FLAG_OVERLAPPED);
6.1.5名字解析
gethostbyname
WSAAsynGetHostByName
gethostbyaddr
WSAAsynGetHostByName
6.2紅外線套接字
IrSock
6.3IPX/SPX
"互聯網包交換"IPX

第二部分 WinsockAPI
第五章 網絡原理和協議
5.1協議的特征
5.1.1 面向消息
對每個離散寫命令來說,如果傳送協議把他們當做一條 獨立的消息在網上傳送,我們就說該協議是面向協議的。在接收端接收數據時,返回的數據是發送端寫入的一條離散消息。這成為:保護消息邊界。
無 保護消息邊界的協議成為“基於流的協議”,這個術語常用來指代附加特性,流服務的定義是連續的數據傳輸;不管消息邊界是否存在,接收端都會盡量地讀取有效 數據。對發送端來說 ,意味着允許將原始消息分解成小消息或把幾條消息積累在一起,形成一個較大的數據包。對接收端來說,則是數據一到達網絡堆棧,網絡堆棧就開始讀取它,並將 它緩存下來等候進程處理。是否將各個對立的數據包累積在一起受許多因素的影響,比如最大傳輸單元或Nagle算法。
偽流(pseudo-stream):使用的協議是基於消息的,發送的數據在各自獨立的數據包內,但在接收端可以把這些消息緩存在一起,這樣接收端便可以讀取任意大小的數據塊。
5.1.2面向連接和無連接
5.1.3可靠性與次序性 可靠性和次序性兩者不能兼得。
51.4從容關閉 面向連接 TCP協議連接雙方都必須執行一次關閉以便完全中斷連接。發送方FIN->接收方ACK->發送方
5.1.5廣播數據 即數據從一個工作站發出,局域網內的其他所有工作站都能收到它。每台機器都必須對該消息進行處理。網絡堆棧。
5.1.6多播數據 是指一個進程發送數據的能力,這些數據即將由一個或者多個接收端進行接收。多播要求對收發數據感興趣的所有主機加入一個特定的組,視頻會議。
5.1.7服務質量(Qos)實時視頻流式傳輸
5.1.8部分消息 大消息
5.1.9路由選擇的考慮
5.3winsock2協議信息
WSAEnumProtocals()
WSAStartup()
int WSACleanup()
SOCKET socket(int af, int type, int protocol);
原始套接字 允許你把其他協議封裝在UDP數據包中,比如“互聯網控制消息協議”(ICMP)。ICMP的目的是投遞互聯網主機間的控制、錯誤和信息類型消息。
Winsock API和OSI模型
每個傳輸協議都會提供一種傳輸數據的方法,但他們本身又是另一個網絡協議的成員,而網絡協議位於網絡層,比如UDP和TCP就是傳輸協議但兩者又都屬於因特網協議IP。
winsockAPI安裝在“會話層”和“傳輸層”之間。提供了一種為制定傳輸層協議打開、計算和關閉會話的能力。

第四章 命名管道
是一種簡單的進程之間通信的機制。
4.1實施細節
命名管道是圍繞Windows文件系統設計的一種技術,采用命名管道文件系統(Named Pipe File System NPFS)接口。
它依賴MSNP重定向器再網上進行命名管道數據的發送和接收。
4.1.1命名規范
//server/Pipe/[path]name 
4.1.2字節模式和消息模式
字節模式:在任何一個特定的時間段內,客戶服務器不知道有多少字節從管道讀入和寫入。
消息模式:每次在管道發送一條消息后,必須作為一條完整的消息讀入。
4.1.3應用程序的編譯
winbase.h+kernel32.lib
4.1.4錯誤代碼
winerror.h
4.2客戶機服務器基礎
4.2.1服務器細節
1)CreateNamedPipe
2)ConnectNamedPipe監聽客戶機的連接
3)ReadFile WriteFile
4)DisconnectNamedPipe()
5)CloseHandle()

void main()
{
   HANDLE ThreadHandle;
   int i;
   DOWRD ThreadId;
   for(i=0; i<5;i++)
   {
      if((ThreadHandle = CreateThread(NULL, 0, PipeInstanceProc,
           NULL, 0, &ThreadId)) == NULL)
       {
         return;
       }
       CloseHandle(ThreadHandle)
      _getch();
   }
}

DWORD WINAPI PipeInstanceProc(LPVOID lpParameter)
{
  HANDLE PipeHandle;
  DWORD  BytesRead;
  DWORD  BytesWrite;
  CHAR   Buffer[256];

  if((PipeHandle = CreateNamedPiped("////.//PIPE//jim ",
     PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE,
     NUM_PIPES,0,0,1000, NULL))==INVALID_HANDLE_VALUE)
   {return 0;}
   
  while(1)
  {
    if(ConnectNamedPipe(PipeHandle, NULL)==0)
    { break;}
  
    while(ReadFile(PipeHandle, Buffer, sizeof(Buffer),&BytesRead,NULL)>0)
    {
    }
    
    if(DisconnectNamedPipe(PipeHandle)==0)
    {return;}
    
    CloseHandle(PipeHandle);
   
  }
}
2.重疊IO
允許Win32API函數在發出IO之后以一部方式工作。
4.2.3客戶機細節
1)WaitNamedPipe
2)CreateFile
3)WriteFile&ReadFile
4)CloseHanlde
4.3OtherAPI
CallNamedPipe
TransactNamedPipe
SetNamedPipeHandleState
GetNamedPipeHandleState

進程間通信,通過槽客戶機進程可將消息或廣播給一個或多個服務器進程。在同一台機器的不同進程之間
,或跨越整個網絡的不同計算機進程之間,協助進行消息的傳輸,客戶服務器模式單向消息傳遞。
3.1郵槽的細節
郵槽必須依賴windows重定向器,通過一個郵槽文件系統(Mailslot File System,MSFS),來創建及標識郵槽。
消息通常是無連接的傳輸方式發送,但亦可強迫windows重定定向器在windows NT和windows 2000上使用面向連接的
的方式傳輸。
3.1.1郵槽的名字
//server/Mailslot/[path]name 
3.1.2 消息的長度
3.1.3 應用程序的編譯
windows.h/winbase.h & Kernel32.lib
3.2基本客戶機/服務器
3.2.1郵槽服務器
1) CreateMailslot()
2) ReadFile()//在默認情況下,處於暫停狀態,直到有數據可以讀入為止。
3) CloseFile()
3.2.2客戶機
1) CreateFile()針對郵槽打開指向它的的一個引用句柄。
2) WriteFile()
3) CloseFile()

windows使應用程序能通過操作系統內建的文件系統服務在網絡上通信。“網絡操作系統”。若應用程序希望訪問本地系統中的文件,需要依賴操作系統來滿足
I/O請求。我們稱之為本地I/O。例如,在一個應用程序打開或關閉文件時, 需要由操作系統來決定如何訪問包含了制定文件內容的一個設備。找到設備后,
I/O請求會被轉發給一個本地設備驅動程序。通過網絡來訪問一個設備也是同樣。然而,IO請求必須通過網絡會被轉發給對應的遠程設備。我們稱之為
“IO重定向(IO redirection)”.
如何將普通的IO請求重定向到遠程設備。
通用命名規范 Universal Naming Convention UNC
多UNC提供者(Multiple UNC Provider)MUP
2.1 通用命名規范
它最大的特點是不必制定或引用一個已映射到遠程文件系統的本地驅動器字母。“與驅動器字母無關”。它的格式:
//[服務器]/[共享名]/[路徑]
2.2MUP
網絡提供者Network Provider,其實就是一種服務,可通過網絡硬件訪問位於一台遠程計算機上的資源(如文件和打印機)。網絡
提供者如“Microsoft網絡用戶”。還有例如Novell公司的Novell Client v3.10 for Windows 95/98.
MUP的基本任務是決定具體由哪個網絡提供者來滿足一個UNC請求。為作出這個決定,MUP需將請求中提到的UNC名字發給已經安裝好的每一個提供者
(以並行方式)。若某個網絡提供這表明自己能夠提供UNC名字牽涉到的那一種服務,MUP便會將請求中剩余的部分發給他。如果有多個提供者都表示能夠服務一個UNC
請求,MUP便會根據優先級挑選最恰當的一個提供者。
2.3網絡提供者
網絡提供者只是一種服務,通過網絡硬件來訪問位於遠程計算機上的共享資源,比如文件和打印機。這正是網絡操作系統的一種核心功能。網絡
提供者具備最主要的功能之一便是將本地磁盤標識符如(E)重定向至遠程機器上的一個磁盤目錄。
2.4重定向器簡介
重定向器由網絡提供者展示給用於接收和處理遠程IO服務請求的操作系統。要做到這一點他需要格式化服務請求消息,再將其發給遠程計算機的
重定向器服務器服務。遠程機器的重定向器服務器收到這個請求之后,會發出本地IO請求的方式,來滿足這一請求。
MSNP提供了一個特殊的重定向器,可直接與網絡傳輸層和NetBIOS打交道,以便在客戶機與服務器之間建立通信。
2.5服務器消息塊
SMB:包含三個基本組件:命令代碼、命令特有的和用戶數據。
SMB協議采用非常簡單的“客戶機請求/服務器相應”傳輸模型。MSNP重定向器創建一個SMB結合時,需要再命令代碼字段中指定一個
特定的請求。若命令要求的是發送數據,比如寫指令,數據便會隨結構一道傳送出去。隨后,SMB結構會通過一種想TCP/IP這樣的傳輸協議傳給一個遠程工作站的服務器服務。
遠程工作站的服務器服務會對收到的客戶機請求進行處理,然后將一個SMB相應數據結構傳回客戶機。
WINS:windows互聯網命名服務器。
NBTstat-n 查看本機已經注冊的NetBIOS名字列表。
windows最有特色的一個網絡提供者稱為“microsoft網絡用戶”,以前叫做“Microsoft網絡提供者(MSNP)”
2.6 安全問題
本地的安全。訪問權限:讀、寫、執行。windows NT和windows2000是通過安全描述符和訪問令牌來實現安全的。

第一部分傳統網絡API
NetBIOS:和winsock類似,也是一種與協議無關的網絡API。他提供了異步調用,同時兼容OS/2、DOS等操作系統。
重定向器:提供了與傳輸無關的文件輸入輸出方式。
郵槽:是一種簡單的接口,可在windows機器之間實現廣播和單向數據通信。
命名管道:可建立一種雙向信道。提供了對windows安全通信的支持。

第一章 NetBIOS(Newwork Basic Input/Output System)
OSI(開放系統互連)網絡模型
應用層     為用戶提供相應的界面,以便使用提供的聯網功能
表示層     完成數據的格式化
會話層     控制兩個主機間的通信鏈路(開放、操作和關閉)
傳輸層     提供數據傳輸服務(可靠與不可靠)
網絡層     在兩個主機之間提供一套定址/尋址機制,同時負責數據包的路由選擇
數據鏈路層 控制連個主機間的物理通信鏈路:同時還要負責對數據整形,以便在物理媒體上傳輸
物理層     物理媒體負責以一系列電子信號的形式傳出數據
NetBIOS主要在會話和傳輸層發揮作用。


1.1.1 LANA(LAN adapter)編號 每張物理網卡被分配的獨一無二的值,通常在0-9之間。每個LANA編號對應於網卡和傳輸協議的唯一組合。
1.1.2 NetBIOS名字 對一個進程來說,他會注冊自己希望與其通信的每個LANA編號。一個NetBIOS名字長度為16個字符。 在Win32環境中,針對每個可用的LANA編號,每個進程都會
為其維持一張BetBIOS名字表。
1.1.3 NetBIOS特性:同時提供面向鏈接和無鏈接服務。
1.2 NetBIOS編程基礎
UCHAR Netbios(PNCB pNCB);//NCB 網絡控制塊
Nb30.h , Netapi32.lib
1.3 常規BetBIOS例程

CDragListBox
In addition to proving the functionality of a windows list box, the CDragListBox class allows
the user to move list box items.


修改socket屬性
int TimeOut=6000; //設置發送超時6秒
if(::setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
   ::closesocket (sock);
   continue;
}


TimeOut=6000;//設置接收超時6秒
if(::setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
  ::closesocket (sock);
  continue;
}

//設置非阻塞方式連接
unsigned long ul = 1;
ret = ioctlsocket(sock, FIONBIO, (unsigned long*)&ul);
if(ret==SOCKET_ERROR)
{
   ::closesocket (sock);
   continue;
}

重疊模型就是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或者多個winsock i/0請求。針對這些請求,在他們完成之后,應用程序會收到通知,於是就可以用自己的代碼來處理這些數據了。
有兩個方法來管理重疊i/o請求的完成情況(就是說接到重疊操作完成的通知): 時間通知 和 完成例程。
基 於事件通知:就是把windows事件和WSAOVERLAPPED結構關聯在一起。使用WSASend, WSASendto,WSARecvFrom。可以把重疊結構與這些函數綁定在一起,提交請求,其他的事情就交給重疊結構去操心了。這樣我們就可以坐享其 成了,等到重疊結構完成之后,自然會有與之對應的事件來通知我們。我們根據重疊操作的結果取得我們需要的數據。
1。WSAOVERLAPPED
WSAEVENT event;
WSAOVERLAPPED AcceptOverlapped;
event = WSACreateEvent();
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AccepOverlapped.hEvent = event;
2. WSARecv系列函數

SOCKET S;
WSABUF DataBuf;
char buffer[5095];
ZeroMemorry(buffer,5095);
DataBuf.len = 5095;

DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
WSAOVERLAPPED AcceptOVerlapped;
WSAEVENT event;
event = WSACreateEvent();
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = event;

WSARecv(s, &DataBuf, dwBufferCount,&dwRecvBytes, &Flags, &AcceptOverlapped,NULL);

3.WSAWaitForMultipleEvents
等待事件的觸發。

4.WSAGetOverlappedResult
查詢重疊操作的結果。


[1] 定義變量
#define DATA_BUFSIZE 4096

SOCKET ListenSocket;//監聽套節字
Socket AcceptSocket;//與客戶端通信的套節字
WSAOVERLAPPED AcceptOverlapped;//重疊結構
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];//用來通知重疊操作完成的事件句柄數組
WSABUF DataBuf[WSA_MAXIMUM_WAIT_EVENTS];
DWORD dwEventTotal = 0;//事件總數
DWORD dwRecvBytes = 0;//接收的字符長度
Flags= 0 ;
[2] 創建套節字並在指定的端口上監聽連接請求。
WSADATA wsaData;
WSAStartup();

ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

SOCKADDR_IN ServerAddr;
serverAddr.sin.family = AF_INET;
serverAddr.sin.addr.S_un.S_addr = htonl(INNADDR_ANY);
serverAddr.sin_port = htons(11111);

bind(ListenSocket, &serveraddr, sizeof());

listen(Listensocket,5);
[3] 接收一個入站的連接請求。
SOCKADDR_IN clientAddr;
int addr_length = sizeof(clientAddr);
AcceptSocket= accept(ListenSocket, &clientsocket, &addr_length);
LPCTSTR lpIP = inet_ntoa(clientAddr.sin_addr);//client ip
UINT nPort = clientAddr.sin_port;//client port

[4]建立並初始化重疊結構

EventArray[dwEventTotal] = WSACreateEvent;
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[dwEventTotal];//關聯事件

char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);

DataBuffer.len = DATA_BUFFER;//初始化一個WSABUF結構
DataBuffer.buf = buffer;

dwEventTotal++;
[5] 以WSAOVERLAPPED為參數,在套節字上投遞WSARecv請求。

 

http://blog.csdn.net/tjb_1216/article/details/4628957


免責聲明!

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



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