socket編程總結(2)

當然TCP方式的模型還有事件選擇模型。
就是把所有的網絡事件和我們的一個程序裏定義的事件梆定。
這個有它的好處,可能可以讓我們更好的寫一個線程來管理
接收與發送。
現在來講一下一個完成端口模型。

  完成端口
 
 一個完成端口其實就是一個通知隊列,由操作系統把已經完成的重疊I/O請求的通知
 放入其中。當某項I/O操作一旦完成,某個可以對該操作結果進行處理的工作者線程
 就會收到一則通知。而套接字在被創建後,可以在任何時候與某個完成端口進行關
 聯。
 
 步驟:
 1、創建一個空的完成端口;
 2、得到本地機器的CPU個數;
 3、開啓CPU*2個工作線程(又名線程池),全部都在等待完成端口的完成包;
 4、創建TCP的監聽socket,使用事件邦定,創建監聽線程;
 5、當有人連接進入的時候,將Client socket保存到一個我們自己定義的關鍵鍵,
    並把它與我們創建的完成端口關聯;
 6、使用WSARecv和WSASend函數投遞一些請求,這是使用重疊I/O的方式;
 7、重複5~6;

 注:1、重疊I/O的方式中,接收與發送數據包的時候,一定要進行投遞請求這是
   它們這個體系結構的特點
  2、完成端口使用了系統內部的一些模型,所以我們只要按照一定的順序調用就
   可以完成了。
  3、完成端口是使用在這樣的情況下,有成千上萬的用戶連接的時候,它能夠
   保證性能不會降低。


#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define PORT 5150
#define DATA_BUFSIZE 8192

//關鍵項
typedef struct
{
   OVERLAPPED Overlapped;
   WSABUF DataBuf;
   CHAR Buffer[DATA_BUFSIZE];
   DWORD BytesSEND;
   DWORD BytesRECV;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);

void main(void)
{
   SOCKADDR_IN InternetAddr;
   SOCKET Listen;
   SOCKET Accept;
   HANDLE CompletionPort;
   SYSTEM_INFO SystemInfo;
   LPPER_HANDLE_DATA PerHandleData;
   LPPER_IO_OPERATION_DATA PerIoData;
   int i;
   DWORD RecvBytes;
   DWORD Flags;
   DWORD ThreadID;
   WSADATA wsaData;
   DWORD Ret;

   if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
   {
      printf("WSAStartup failed with error %d/n", Ret);
      return;
   }

   //打開一個空的完成端口

   if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
   {
      printf( "CreateIoCompletionPort failed with error: %d/n", GetLastError());
      return;
   }

   // Determine how many processors are on the system.

   GetSystemInfo(&SystemInfo);

   // 開啓cpu個數的2倍個的線程

   for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
   {
      HANDLE ThreadHandle;

      // Create a server worker thread and pass the completion port to the thread.

      if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
         0, &ThreadID)) == NULL)
      {
         printf("CreateThread() failed with error %d/n", GetLastError());
         return;
      }

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

   //打開一個服務器socket

   if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
      WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
   {
      printf("WSASocket() failed with error %d/n", WSAGetLastError());
      return;
   }

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

   if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
   {
      printf("bind() failed with error %d/n", WSAGetLastError());
      return;
   }


   if (listen(Listen, 5) == SOCKET_ERROR)
   {
      printf("listen() failed with error %d/n", WSAGetLastError());
      return;
   }

   //開始接收從客戶端來的連接

   while(TRUE)
   {
      if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
      {
         printf("WSAAccept() failed with error %d/n", WSAGetLastError());
         return;
      }

      // 創建一個關鍵項用於保存這個客戶端的信息,用戶接收發送的重疊結構,
      // 還有使用到的緩衝區
      if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR,
         sizeof(PER_HANDLE_DATA))) == NULL)
      {
         printf("GlobalAlloc() failed with error %d/n", GetLastError());
         return;
      }

      // Associate the accepted socket with the original completion port.

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

      //與我們的創建的那個完成端口關聯起來,將關鍵項也與指定的一個完成端口關聯
      if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
         0) == NULL)
      {
         printf("CreateIoCompletionPort failed with error %d/n", GetLastError());
         return;
      }

      // 投遞一次接收,由於接收都需要使用這個函數來投遞一個接收的準備

      if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,          sizeof(PER_IO_OPERATION_DATA))) == NULL)
      {
         printf("GlobalAlloc() failed with error %d/n", GetLastError());
         return;
      }

      ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
      PerIoData->BytesSEND = 0;
      PerIoData->BytesRECV = 0;
      PerIoData->DataBuf.len = DATA_BUFSIZE;
      PerIoData->DataBuf.buf = PerIoData->Buffer;

      Flags = 0;
      if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
         &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
      {
         if (WSAGetLastError() != ERROR_IO_PENDING)
         {
            printf("WSARecv() failed with error %d/n", WSAGetLastError());
            return;
         }
      }
   }
}
//工作線程
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
   HANDLE CompletionPort = (HANDLE) CompletionPortID;
   DWORD BytesTransferred;
   LPOVERLAPPED Overlapped;
   LPPER_HANDLE_DATA PerHandleData;
   LPPER_IO_OPERATION_DATA PerIoData;
   DWORD SendBytes, RecvBytes;
   DWORD Flags;
  
   while(TRUE)
   {
      //完成端口有消息來了
      if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
         (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
      {
         printf("GetQueuedCompletionStatus failed with error %d/n", GetLastError());
         return 0;
      }


      //是不是有人退出了

      if (BytesTransferred == 0)
      {
         printf("Closing socket %d/n", PerHandleData->Socket);

         if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
         {
            printf("closesocket() failed with error %d/n", WSAGetLastError());
            return 0;
         }

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

      //

      if (PerIoData->BytesRECV == 0)
      {
         PerIoData->BytesRECV = BytesTransferred;
         PerIoData->BytesSEND = 0;
      }
      else
      {
         PerIoData->BytesSEND += BytesTransferred;
      }

      if (PerIoData->BytesRECV > PerIoData->BytesSEND)
      {

         // Post another WSASend() request.
         // Since WSASend() is not gauranteed to send all of the bytes requested,
         // continue posting WSASend() calls until all received bytes are sent.

         ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

         PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
         PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;

         if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
            &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
         {
            if (WSAGetLastError() != ERROR_IO_PENDING)
            {
               printf("WSASend() failed with error %d/n", WSAGetLastError());
               return 0;
            }
         }
      }
      else
      {
         PerIoData->BytesRECV = 0;

         // Now that there are no more bytes to send post another WSARecv() request.

         Flags = 0;
         ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

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

         if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
            &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
         {
            if (WSAGetLastError() != ERROR_IO_PENDING)
            {
               printf("WSARecv() failed with error %d/n", WSAGetLastError());
               return 0;
            }
         }
      }
   }
}

發佈了32 篇原創文章 · 獲贊 0 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章