青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 297,  comments - 15,  trackbacks - 0
Winsock 的I/O操作:

1、 兩種I/O模式 
阻塞模式:執(zhí)行I/O操作完成前會一直進行等待,不會將控制權(quán)交給程序。套接字 默認為阻塞模式。可以通過多線程技術(shù)進行處理。 
非阻塞模式:執(zhí)行I/O操作時,Winsock函數(shù)會返回并交出控制權(quán)。這種模式使用 起來比較復雜,因為函數(shù)在沒有運行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種:

Windows Socket五種I/O模型——代碼全攻略

如 果你想在Windows平臺上構(gòu)建服務器應用,那么I/O模型是你必須考慮的。Windows操作系統(tǒng)提供了選擇(Select)、異步選擇 (WSAAsyncSelect)、事件選擇(WSAEventSelect)、重疊I/O(Overlapped I/O)和完成端口 (Completion Port)共五種I/O模型。每一種模型均適用于一種特定的應用場景。程序員應該對自己的應用需求非常明確,而且綜合考慮到程序 的擴展性和可移植性等因素,作出自己的選擇。

我會以一個回應反射式服務器(與《Windows網(wǎng)絡編程》第八章一樣)來介紹這五種I/O模型。
我們假設客戶端的代碼如下(為代碼直觀,省去所有錯誤檢查,以下同):

#include <WINSOCK2.H>
#include <stdio.h>

#define SERVER_ADDRESS "137.117.2.148"
#define PORT           5150
#define MSGSIZE        1024

#pragma comment(lib, "ws2_32.lib")

int main()
{
  WSADATA     wsaData;
  SOCKET      sClient;
  SOCKADDR_IN server;
  char        szMessage[MSGSIZE];
  int         ret;
  
  // Initialize Windows socket library
  WSAStartup(0x0202, &wsaData);

  // Create client socket
  sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Connect to server
  memset(&server, 0, sizeof(SOCKADDR_IN));
  server.sin_family = AF_INET;
  server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
  server.sin_port = htons(PORT);

  connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

  while (TRUE)
  {
    printf("Send:");
  gets(szMessage);

    // Send message
    send(sClient, szMessage, strlen(szMessage), 0);

    // Receive message
    ret = recv(sClient, szMessage, MSGSIZE, 0);
    szMessage[ret] = '\0';

    printf("Received [%d bytes]: '%s'\n", ret, szMessage);
  }

  // Clean up
  closesocket(sClient);
  WSACleanup();
  return 0;
}

客戶端所做的事情相當簡單,創(chuàng)建套接字,連接服務器,然后不停的發(fā)送和接收數(shù)據(jù)。

比 較容易想到的一種服務器模型就是采用一個主線程,負責監(jiān)聽客戶端的連接請求,當接收到某個客戶端的連接請求后,創(chuàng)建一個專門用于和該客戶端通信的套接字和 一個輔助線程。以后該客戶端和服務器的交互都在這個輔助線程內(nèi)完成。這種方法比較直觀,程序非常簡單而且可移植性好,但是不能利用平臺相關(guān)的特性。例如, 如果連接數(shù)增多的時候(成千上萬的連接),那么線程數(shù)成倍增長,操作系統(tǒng)忙于頻繁的線程間切換,而且大部分線程在其生命周期內(nèi)都是處于非活動狀態(tài)的,這大 大浪費了系統(tǒng)的資源。所以,如果你已經(jīng)知道你的代碼只會運行在Windows平臺上,建議采用Winsock I/O模型。

一.選擇模型
Select(選 擇)模型是Winsock中最常見的I/O模型。之所以稱其為“Select模型”,是由于它的“中心思想”便是利用select函數(shù),實現(xiàn)對I/O的管 理。最初設計該模型時,主要面向的是某些使用UNIX操作系統(tǒng)的計算機,它們采用的是Berkeley套接字方案。Select模型已集成到 Winsock 1.1中,它使那些想避免在套接字調(diào)用過程中被無辜“鎖定”的應用程序,采取一種有序的方式,同時進行對多個套接字的管理。由于 Winsock 1.1向后兼容于Berkeley套接字實施方案,所以假如有一個Berkeley套接字應用使用了select函數(shù),那么從理論角度 講,毋需對其進行任何修改,便可正常運行。(節(jié)選自《Windows網(wǎng)絡編程》第八章)
下面的這段程序就是利用選擇模型實現(xiàn)的Echo服務器的代碼(已經(jīng)不能再精簡了):

#include <winsock.h>
#include <stdio.h>

#define PORT       5150
#define MSGSIZE    1024

#pragma comment(lib, "ws2_32.lib")

int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];

DWORD WINAPI WorkerThread(LPVOID lpParameter);

int main()
{
  WSADATA     wsaData;
  SOCKET      sListen, sClient;
  SOCKADDR_IN local, client;
  int         iaddrSize = sizeof(SOCKADDR_IN);
  DWORD       dwThreadId;

  // Initialize Windows socket library
  WSAStartup(0x0202, &wsaData);

  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

  // Listen
  listen(sListen, 3);

  // Create worker thread
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);  

  while (TRUE)
  {
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    // Add socket to g_CliSocketArr
    g_CliSocketArr[g_iTotalConn++] = sClient;
  }
  
  return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
  int            i;
  fd_set         fdread;
  int            ret;
  struct timeval tv = {1, 0};
  char           szMessage[MSGSIZE];
  
  while (TRUE)
  {
    FD_ZERO(&fdread);
    for (i = 0; i < g_iTotalConn; i++)
    {
      FD_SET(g_CliSocketArr, &fdread);
    }

    // We only care read event
    ret = select(0, &fdread, NULL, NULL, &tv);

    if (ret == 0)
    {
      // Time expired
      continue;
    }

    for (i = 0; i < g_iTotalConn; i++)
    {
      if (FD_ISSET(g_CliSocketArr, &fdread))
      {
        // A read event happened on g_CliSocketArr
        ret = recv(g_CliSocketArr, szMessage, MSGSIZE, 0);
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
    {
     // Client socket closed
          printf("Client socket %d closed.\n", g_CliSocketArr);
     closesocket(g_CliSocketArr);
     if (i < g_iTotalConn - 1)
          {            
            g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
          }
        }
    else
    {
     // We received a message from client
          szMessage[ret] = '\0';
     send(g_CliSocketArr, szMessage, strlen(szMessage), 0);
        }
      }
    }
  }
  
  return 0;
}

服務器的幾個主要動作如下:
1.創(chuàng)建監(jiān)聽套接字,綁定,監(jiān)聽;
2.創(chuàng)建工作者線程;
3.創(chuàng)建一個套接字數(shù)組,用來存放當前所有活動的客戶端套接字,每accept一個連接就更新一次數(shù)組;
4. 接受客戶端的連接。這里有一點需要注意的,就是我沒有重新定義FD_SETSIZE宏,所以服務器最多支持的并發(fā)連接數(shù)為64。而且,這里決不能無條件的 accept,服務器應該根據(jù)當前的連接數(shù)來決定是否接受來自某個客戶端的連接。一種比較好的實現(xiàn)方案就是采用WSAAccept函數(shù),而且讓 WSAAccept回調(diào)自己實現(xiàn)的Condition Function。如下所示:

int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId,
 LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
if (當前連接數(shù) < FD_SETSIZE)
  return CF_ACCEPT;
else
  return CF_REJECT;
}

工作者線程里面是一個死循環(huán),一次循環(huán)完成的動作是:
1.將當前所有的客戶端套接字加入到讀集fdread中;
2.調(diào)用select函數(shù);
3. 查看某個套接字是否仍然處于讀集中,如果是,則接收數(shù)據(jù)。如果接收的數(shù)據(jù)長度為0,或者發(fā)生WSAECONNRESET錯誤,則表示客戶端套接字主動關(guān) 閉,這時需要將服務器中對應的套接字所綁定的資源釋放掉,然后調(diào)整我們的套接字數(shù)組(將數(shù)組中最后一個套接字挪到當前的位置上)

除了需要有條件接受客戶端的連接外,還需要在連接數(shù)為0的情形下做特殊處理,因為如果讀集中沒有任何套接字,select函數(shù)會立刻返回,這將導致工作者線程成為一個毫無停頓的死循環(huán),CPU的占用率馬上達到100%。

關(guān) 系到套接字列表的操作都需要使用循環(huán),在輪詢的時候,需要遍歷一次,再新的一輪開始時,將列表加入隊列又需要遍歷一次.也就是說,Select在工作一次 時,需要至少遍歷2次列表,這是它效率較低的原因之一.在大規(guī)模的網(wǎng)絡連接方面,還是推薦使用IOCP或EPOLL模型.但是Select模型可以使用在 諸如對戰(zhàn)類游戲上,比如類似星際這種,因為它小巧易于實現(xiàn),而且對戰(zhàn)類游戲的網(wǎng)絡連接量并不大.

對于Select模型想要突破 Windows 64個限制的話,可以采取分段輪詢,一次輪詢64個.例如套接字列表為128個,在第一次輪詢時,將前64個放入隊列中用Select進 行狀態(tài)查詢,待本次操作全部結(jié)束后.將后64個再加入輪詢隊列中進行輪詢處理.這樣處理需要在非阻塞式下工作.以此類推,Select也能支持無限多個.

二.異步選擇
Winsock 提供了一個有用的異步I/O模型。利用這個模型,應用程序可在一個套接字上,接收以Windows消息為基礎的網(wǎng)絡事件通知。具體的做法是在建好一個套接 字后,調(diào)用WSAAsyncSelect函數(shù)。該模型最早出現(xiàn)于Winsock的1.1版本中,用于幫助應用程序開發(fā)者面向一些早期的16位 Windows平臺(如Windows for Workgroups),適應其“落后”的多任務消息環(huán)境。應用程序仍可從這種模型中得到好處,特別是它 們用一個標準的Windows例程(常稱為"WndProc"),對窗口消息進行管理的時候。該模型亦得到了 Microsoft Foundation Class(微軟基本類,MFC)對象CSocket的采納。(節(jié)選自《Windows網(wǎng)絡編程》第八章)
我還是先貼出代碼,然后做詳細解釋:
#include <winsock.h>
#include <tchar.h>

#define PORT      5150
#define MSGSIZE   1024
#define WM_SOCKET WM_USER+0

#pragma comment(lib, "ws2_32.lib")

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
  static TCHAR szAppName[] = _T("AsyncSelect Model");
  HWND         hwnd ;
  MSG          msg ;
  WNDCLASS     wndclass ;

  wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc   = WndProc ;
  wndclass.cbClsExtra    = 0 ;
  wndclass.cbWndExtra    = 0 ;
  wndclass.hInstance     = hInstance ;
  wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
  wndclass.lpszMenuName  = NULL ;
  wndclass.lpszClassName = szAppName ;

  if (!RegisterClass(&wndclass))
  {
    MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
    return 0 ;
  }

  hwnd = CreateWindow (szAppName,                  // window class name
                       TEXT ("AsyncSelect Model"), // window caption
                       WS_OVERLAPPEDWINDOW,        // window style
                       CW_USEDEFAULT,              // initial x position
                       CW_USEDEFAULT,              // initial y position
                       CW_USEDEFAULT,              // initial x size
                       CW_USEDEFAULT,              // initial y size
                       NULL,                       // parent window handle
                       NULL,                       // window menu handle
                       hInstance,                  // program instance handle
                       NULL) ;                     // creation parameters

  ShowWindow(hwnd, iCmdShow);
  UpdateWindow(hwnd);

  while (GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg) ;
    DispatchMessage(&msg) ;
  }
  
  return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  WSADATA       wsd;
  static SOCKET sListen;
  SOCKET        sClient;
  SOCKADDR_IN   local, client;
  int           ret, iAddrSize = sizeof(client);
  char          szMessage[MSGSIZE];

  switch (message)
  {
case WM_CREATE:
    // Initialize Windows Socket library
  WSAStartup(0x0202, &wsd);
  
  // Create listening socket
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
  // Bind
    local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
  local.sin_family = AF_INET;
  local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(local));
  
  // Listen
    listen(sListen, 3);

    // Associate listening socket with FD_ACCEPT event
  WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
  return 0;

  case WM_DESTROY:
    closesocket(sListen);
    WSACleanup();
    PostQuitMessage(0);
    return 0;
  
  case WM_SOCKET:
    if (WSAGETSELECTERROR(lParam))
    {
      closesocket(wParam);
      break;
    }
    
    switch (WSAGETSELECTEVENT(lParam))
    {
    case FD_ACCEPT:
      // Accept a connection from client
      sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize);
      
      // Associate client socket with FD_READ and FD_CLOSE event
      WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
      break;

    case FD_READ:
      ret = recv(wParam, szMessage, MSGSIZE, 0);

      if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)
      {
        closesocket(wParam);
      }
      else
      {
        szMessage[ret] = '\0';
        send(wParam, szMessage, strlen(szMessage), 0);
      }
      break;
      
    case FD_CLOSE:
      closesocket(wParam);      
      break;
    }
    return 0;
  }
  
  return DefWindowProc(hwnd, message, wParam, lParam);
}

在我看來,WSAAsyncSelect是最簡單的一種Winsock I/O模型(之所以說它簡單是因為一個主線程就搞定了)。使用Raw Windows API寫過窗口類應用程序的人應該都能看得懂。這里,我們需要做的僅僅是:
1.在WM_CREATE消息處理函數(shù)中,初始化Windows Socket library,創(chuàng)建監(jiān)聽套接字,綁定,監(jiān)聽,并且調(diào)用WSAAsyncSelect函數(shù)表示我們關(guān)心在監(jiān)聽套接字上發(fā)生的FD_ACCEPT事件;
2.自定義一個消息WM_SOCKET,一旦在我們所關(guān)心的套接字(監(jiān)聽套接字和客戶端套接字)上發(fā)生了某個事件,系統(tǒng)就會調(diào)用WndProc并且message參數(shù)被設置為WM_SOCKET;
3.在WM_SOCKET的消息處理函數(shù)中,分別對FD_ACCEPT、FD_READ和FD_CLOSE事件進行處理;
4.在窗口銷毀消息(WM_DESTROY)的處理函數(shù)中,我們關(guān)閉監(jiān)聽套接字,清除Windows Socket library

下面這張用于WSAAsyncSelect函數(shù)的網(wǎng)絡事件類型表可以讓你對各個網(wǎng)絡事件有更清楚的認識:
表1

FD_READ 應用程序想要接收有關(guān)是否可讀的通知,以便讀入數(shù)據(jù) 
FD_WRITE 應用程序想要接收有關(guān)是否可寫的通知,以便寫入數(shù)據(jù) 
FD_OOB 應用程序想接收是否有帶外(OOB)數(shù)據(jù)抵達的通知 
FD_ACCEPT 應用程序想接收與進入連接有關(guān)的通知 
FD_CONNECT 應用程序想接收與一次連接或者多點join操作完成的通知 
FD_CLOSE 應用程序想接收與套接字關(guān)閉有關(guān)的通知 
FD_QOS 應用程序想接收套接字“服務質(zhì)量”(QoS)發(fā)生更改的通知 
FD_GROUP_QOS  應用程序想接收套接字組“服務質(zhì)量”發(fā)生更改的通知(現(xiàn)在沒什么用處,為未來套接字組的使用保留) 
FD_ROUTING_INTERFACE_CHANGE 應用程序想接收在指定的方向上,與路由接口發(fā)生變化的通知 
FD_ADDRESS_LIST_CHANGE  應用程序想接收針對套接字的協(xié)議家族,本地地址列表發(fā)生變化的通知 

三.事件選擇
Winsock 提供了另一個有用的異步I/O模型。和WSAAsyncSelect模型類似的是,它也允許應用程序在一個或多個套接字上,接收以事件為基礎的網(wǎng)絡事件通 知。對于表1總結(jié)的、由WSAAsyncSelect模型采用的網(wǎng)絡事件來說,它們均可原封不動地移植到新模型。在用新模型開發(fā)的應用程序中,也能接收和 處理所有那些事件。該模型最主要的差別在于網(wǎng)絡事件會投遞至一個事件對象句柄,而非投遞至一個窗口例程。(節(jié)選自《Windows網(wǎng)絡編程》第八章)
還是讓我們先看代碼然后進行分析:
#include <winsock2.h>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

int      g_iTotalConn = 0;
SOCKET   g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];

DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int index);

int main()
{
  WSADATA     wsaData;
  SOCKET      sListen, sClient;
  SOCKADDR_IN local, client;
  DWORD       dwThreadId;
  int         iaddrSize = sizeof(SOCKADDR_IN);

  // Initialize Windows Socket library
  WSAStartup(0x0202, &wsaData);

  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

  // Listen
  listen(sListen, 3);

  // Create worker thread
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

  while (TRUE)
  {
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    // Associate socket with network event
    g_CliSocketArr[g_iTotalConn] = sClient;
    g_CliEventArr[g_iTotalConn] = WSACreateEvent();
    WSAEventSelect(g_CliSocketArr[g_iTotalConn],
                   g_CliEventArr[g_iTotalConn],
                   FD_READ | FD_CLOSE);
    g_iTotalConn++;
  }
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
  int              ret, index;
  WSANETWORKEVENTS NetworkEvents;
  char             szMessage[MSGSIZE];

  while (TRUE)
  {
    ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
    if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
    {
      continue;
    }

    index = ret - WSA_WAIT_EVENT_0;
    WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);

    if (NetworkEvents.lNetworkEvents & FD_READ)
    {
      // Receive message from client
      ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
      if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
      {
        Cleanup(index);
      }
      else
      {
        szMessage[ret] = '\0';
        send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);
      }
    }

    if (NetworkEvents.lNetworkEvents & FD_CLOSE)
  {
   Cleanup(index);
  }
  }
  return 0;
}

void Cleanup(int index)
{
  closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);

if (index < g_iTotalConn - 1)
{
  g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
  g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
}

g_iTotalConn--;
}

事 件選擇模型也比較簡單,實現(xiàn)起來也不是太復雜,它的基本思想是將每個套接字都和一個WSAEVENT對象對應起來,并且在關(guān)聯(lián)的時候指定需要關(guān)注的哪些網(wǎng) 絡事件。一旦在某個套接字上發(fā)生了我們關(guān)注的事件(FD_READ和FD_CLOSE),與之相關(guān)聯(lián)的WSAEVENT對象被Signaled。程序定義 了兩個全局數(shù)組,一個套接字數(shù)組,一個WSAEVENT對象數(shù)組,其大小都是MAXIMUM_WAIT_OBJECTS(64),兩個數(shù)組中的元素一一對 應。
同樣的,這里的程序沒有考慮兩個問題,一是不能無條件的調(diào)用accept,因為我們支持的并發(fā)連接數(shù)有限。解決方法是將套接字按 MAXIMUM_WAIT_OBJECTS分組,每MAXIMUM_WAIT_OBJECTS個套接字一組,每一組分配一個工作者線程;或者采用 WSAAccept代替accept,并回調(diào)自己定義的Condition Function。第二個問題是沒有對連接數(shù)為0的情形做特殊處理,程序在連 接數(shù)為0的時候CPU占用率為100%。

四.重疊I/O模型
Winsock2的發(fā)布使得Socket I/O有了和文件I/O統(tǒng) 一的接口。我們可以通過使用Win32文件操縱函數(shù)ReadFile和WriteFile來進行Socket I/O。伴隨而來的,用于普通文件I/O的 重疊I/O模型和完成端口模型對Socket I/O也適用了。這些模型的優(yōu)點是可以達到更佳的系統(tǒng)性能,但是實現(xiàn)較為復雜,里面涉及較多的C語言技巧。 例如我們在完成端口模型中會經(jīng)常用到所謂的“尾隨數(shù)據(jù)”。

1.用事件通知方式實現(xiàn)的重疊I/O模型
#include <winsock2.h>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef struct
{
  WSAOVERLAPPED overlap;
  WSABUF        Buffer;
  char          szMessage[MSGSIZE];
  DWORD         NumberOfBytesRecvd;
  DWORD         Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

int                     g_iTotalConn = 0;
SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];

DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);

int main()
{
  WSADATA     wsaData;
  SOCKET      sListen, sClient;
  SOCKADDR_IN local, client;
  DWORD       dwThreadId;
  int         iaddrSize = sizeof(SOCKADDR_IN);

  // Initialize Windows Socket library
  WSAStartup(0x0202, &wsaData);

  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

  // Listen
  listen(sListen, 3);

  // Create worker thread
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

  while (TRUE)
  {
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    g_CliSocketArr[g_iTotalConn] = sClient;
    
    // Allocate a PER_IO_OPERATION_DATA structure
    g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      sizeof(PER_IO_OPERATION_DATA));
    g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
    g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
    g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();

    // Launch an asynchronous operation
    WSARecv(
      g_CliSocketArr[g_iTotalConn],
      &g_pPerIODataArr[g_iTotalConn]->Buffer,
      1,
      &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
      &g_pPerIODataArr[g_iTotalConn]->Flags,
      &g_pPerIODataArr[g_iTotalConn]->overlap,
      NULL);
    
    g_iTotalConn++;
  }
  
  closesocket(sListen);
  WSACleanup();
  return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
  int   ret, index;
  DWORD cbTransferred;

  while (TRUE)
  {
    ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
    if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
    {
      continue;
    }

    index = ret - WSA_WAIT_EVENT_0;
    WSAResetEvent(g_CliEventArr[index]);

    WSAGetOverlappedResult(
      g_CliSocketArr[index],
      &g_pPerIODataArr[index]->overlap,
      &cbTransferred,
      TRUE,
      &g_pPerIODataArr[g_iTotalConn]->Flags);

    if (cbTransferred == 0)
    {
      // The connection was closed by client
      Cleanup(index);
    }
    else
    {
      // g_pPerIODataArr[index]->szMessage contains the received data
      g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
      send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,\
        cbTransferred, 0);

      // Launch another asynchronous operation
      WSARecv(
        g_CliSocketArr[index],
        &g_pPerIODataArr[index]->Buffer,
        1,
        &g_pPerIODataArr[index]->NumberOfBytesRecvd,
        &g_pPerIODataArr[index]->Flags,
        &g_pPerIODataArr[index]->overlap,
        NULL);
    }
  }

  return 0;
}

void Cleanup(int index)
{
  closesocket(g_CliSocketArr[index]);
  WSACloseEvent(g_CliEventArr[index]);
  HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);

  if (index < g_iTotalConn - 1)
  {
    g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
    g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
    g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
  }

  g_pPerIODataArr[--g_iTotalConn] = NULL;
}

這 個模型與上述其他模型不同的是它使用Winsock2提供的異步I/O函數(shù)WSARecv。在調(diào)用WSARecv時,指定一個WSAOVERLAPPED 結(jié)構(gòu),這個調(diào)用不是阻塞的,也就是說,它會立刻返回。一旦有數(shù)據(jù)到達的時候,被指定的WSAOVERLAPPED結(jié)構(gòu)中的hEvent被 Signaled。由于下面這個語句
g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;
使 得與該套接字相關(guān)聯(lián)的WSAEVENT對象也被Signaled,所以WSAWaitForMultipleEvents的調(diào)用操作成功返回。我們現(xiàn)在應 該做的就是用與調(diào)用WSARecv相同的WSAOVERLAPPED結(jié)構(gòu)為參數(shù)調(diào)用WSAGetOverlappedResult,從而得到本次I/O傳 送的字節(jié)數(shù)等相關(guān)信息。在取得接收的數(shù)據(jù)后,把數(shù)據(jù)原封不動的發(fā)送到客戶端,然后重新激活一個WSARecv異步操作。

2.用完成例程方式實現(xiàn)的重疊I/O模型
#include <WINSOCK2.H>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef struct
{
WSAOVERLAPPED overlap;
WSABUF        Buffer;
  char          szMessage[MSGSIZE];
DWORD         NumberOfBytesRecvd;
DWORD         Flags; 
SOCKET        sClient;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

DWORD WINAPI WorkerThread(LPVOID);
void CALLBACK CompletionROUTINE(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

SOCKET g_sNewClientConnection;
BOOL   g_bNewConnectionArrived = FALSE;

int main()
{
  WSADATA     wsaData;
  SOCKET      sListen;
  SOCKADDR_IN local, client;
  DWORD       dwThreadId;
  int         iaddrSize = sizeof(SOCKADDR_IN);

  // Initialize Windows Socket library
  WSAStartup(0x0202, &wsaData);

  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

  // Listen
  listen(sListen, 3);

  // Create worker thread
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

  while (TRUE)
  {
    // Accept a connection
    g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    g_bNewConnectionArrived = TRUE;
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
  }
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

  while (TRUE)
  {
    if (g_bNewConnectionArrived)
    {
      // Launch an asynchronous operation for new arrived connection
      lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
        GetProcessHeap(),
        HEAP_ZERO_MEMORY,
        sizeof(PER_IO_OPERATION_DATA));
      lpPerIOData->Buffer.len = MSGSIZE;
      lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
      lpPerIOData->sClient = g_sNewClientConnection;
      
      WSARecv(lpPerIOData->sClient,
        &lpPerIOData->Buffer,
        1,
        &lpPerIOData->NumberOfBytesRecvd,
        &lpPerIOData->Flags,
        &lpPerIOData->overlap,
        CompletionROUTINE);      
      
      g_bNewConnectionArrived = FALSE;
    }

    SleepEx(1000, TRUE);
  }
  return 0;
}

void CALLBACK CompletionROUTINE(DWORD dwError,
                                DWORD cbTransferred,
                                LPWSAOVERLAPPED lpOverlapped,
                                DWORD dwFlags)
{
  LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;
  
  if (dwError != 0 || cbTransferred == 0)
{
    // Connection was closed by client
  closesocket(lpPerIOData->sClient);
  HeapFree(GetProcessHeap(), 0, lpPerIOData);
}
  else
  {
    lpPerIOData->szMessage[cbTransferred] = '\0';
    send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0);
    
    // Launch another asynchronous operation
    memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;    

    WSARecv(lpPerIOData->sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      CompletionROUTINE);
  }
}

用 完成例程來實現(xiàn)重疊I/O比用事件通知簡單得多。在這個模型中,主線程只用不停的接受連接即可;輔助線程判斷有沒有新的客戶端連接被建立,如果有,就為那 個客戶端套接字激活一個異步的WSARecv操作,然后調(diào)用SleepEx使線程處于一種可警告的等待狀態(tài),以使得I/O完成后 CompletionROUTINE可以被內(nèi)核調(diào)用。如果輔助線程不調(diào)用SleepEx,則內(nèi)核在完成一次I/O操作后,無法調(diào)用完成例程(因為完成例程 的運行應該和當初激活WSARecv異步操作的代碼在同一個線程之內(nèi))。
完成例程內(nèi)的實現(xiàn)代碼比較簡單,它取出接收到的數(shù)據(jù),然后將數(shù)據(jù)原封不動 的發(fā)送給客戶端,最后重新激活另一個WSARecv異步操作。注意,在這里用到了“尾隨數(shù)據(jù)”。我們在調(diào)用WSARecv的時候,參數(shù) lpOverlapped實際上指向一個比它大得多的結(jié)構(gòu)PER_IO_OPERATION_DATA,這個結(jié)構(gòu)除了WSAOVERLAPPED以外,還 被我們附加了緩沖區(qū)的結(jié)構(gòu)信息,另外還包括客戶端套接字等重要的信息。這樣,在完成例程中通過參數(shù)lpOverlapped拿到的不僅僅是 WSAOVERLAPPED結(jié)構(gòu),還有后邊尾隨的包含客戶端套接字和接收數(shù)據(jù)緩沖區(qū)等重要信息。這樣的C語言技巧在我后面介紹完成端口的時候還會使用到。

五.完成端口模型
“完 成端口”模型是迄今為止最為復雜的一種I/O模型。然而,假若一個應用程序同時需要管理為數(shù)眾多的套接字,那么采用這種模型,往往可以達到最佳的系統(tǒng)性 能!但不幸的是,該模型只適用于Windows NT和Windows 2000操作系統(tǒng)。因其設計的復雜性,只有在你的應用程序需要同時管理數(shù)百乃至上 千個套接字的時候,而且希望隨著系統(tǒng)內(nèi)安裝的CPU數(shù)量的增多,應用程序的性能也可以線性提升,才應考慮采用“完成端口”模型。要記住的一個基本準則是, 假如要為Windows NT或Windows 2000開發(fā)高性能的服務器應用,同時希望為大量套接字I/O請求提供服務(Web服務器便是這方面的典 型例子),那么I/O完成端口模型便是最佳選擇!(節(jié)選自《Windows網(wǎng)絡編程》第八章)
完成端口模型是我最喜愛的一種模型。雖然其實現(xiàn)比較 復雜(其實我覺得它的實現(xiàn)比用事件通知實現(xiàn)的重疊I/O簡單多了),但其效率是驚人的。我在T公司的時候曾經(jīng)幫同事寫過一個郵件服務器的性能測試程序,用 的就是完成端口模型。結(jié)果表明,完成端口模型在多連接(成千上萬)的情況下,僅僅依靠一兩個輔助線程,就可以達到非常高的吞吐量。下面我還是從代碼說起:
#include <WINSOCK2.H>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef enum
{
  RECV_POSTED
}OPERATION_TYPE;

typedef struct
{
WSAOVERLAPPED  overlap;
WSABUF         Buffer;
  char           szMessage[MSGSIZE];
DWORD          NumberOfBytesRecvd;
DWORD          Flags;
OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

DWORD WINAPI WorkerThread(LPVOID);

int main()
{
  WSADATA                 wsaData;
  SOCKET                  sListen, sClient;
  SOCKADDR_IN             local, client;
  DWORD                   i, dwThreadId;
  int                     iaddrSize = sizeof(SOCKADDR_IN);
  HANDLE                  CompletionPort = INVALID_HANDLE_VALUE;
  SYSTEM_INFO             systeminfo;
  LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

  // Initialize Windows Socket library
  WSAStartup(0x0202, &wsaData);

  // Create completion port
  CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

  // Create worker thread
  GetSystemInfo(&systeminfo);
  for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
  {
    CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
  }
  
  // Create listening socket
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind
  local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

  // Listen
  listen(sListen, 3);

  while (TRUE)
  {
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    // Associate the newly arrived client socket with completion port
    CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
    
    // Launch an asynchronous operation for new arrived connection
    lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      sizeof(PER_IO_OPERATION_DATA));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
    lpPerIOData->OperationType = RECV_POSTED;
    WSARecv(sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      NULL);
  }

  PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
CloseHandle(CompletionPort);
closesocket(sListen);
WSACleanup();
return 0;
}

DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
  HANDLE                  CompletionPort=(HANDLE)CompletionPortID;
  DWORD                   dwBytesTransferred;
  SOCKET                  sClient;
  LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

  while (TRUE)
  {
    GetQueuedCompletionStatus(
      CompletionPort,
      &dwBytesTransferred,
      &sClient,
      (LPOVERLAPPED *)&lpPerIOData,
      INFINITE);
    if (dwBytesTransferred == 0xFFFFFFFF)
    {
      return 0;
    }
    
    if (lpPerIOData->OperationType == RECV_POSTED)
    {
      if (dwBytesTransferred == 0)
      {
        // Connection was closed by client
        closesocket(sClient);
        HeapFree(GetProcessHeap(), 0, lpPerIOData);        
      }
      else
      {
        lpPerIOData->szMessage[dwBytesTransferred] = '\0';
        send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);
        
        // Launch another asynchronous operation for sClient
        memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
        lpPerIOData->Buffer.len = MSGSIZE;
        lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
        lpPerIOData->OperationType = RECV_POSTED;
        WSARecv(sClient,
          &lpPerIOData->Buffer,
          1,
          &lpPerIOData->NumberOfBytesRecvd,
          &lpPerIOData->Flags,
          &lpPerIOData->overlap,
          NULL);
      }
    }
  }
return 0;
}

首先,說說主線程:
1.創(chuàng)建完成端口對象
2.創(chuàng)建工作者線程(這里工作者線程的數(shù)量是按照CPU的個數(shù)來決定的,這樣可以達到最佳性能)
3.創(chuàng)建監(jiān)聽套接字,綁定,監(jiān)聽,然后程序進入循環(huán)
4.在循環(huán)中,我做了以下幾件事情:
(1).接受一個客戶端連接
(2). 將該客戶端套接字與完成端口綁定到一起(還是調(diào)用CreateIoCompletionPort,但這次的作用不同),注意,按道理來講,此時傳遞給 CreateIoCompletionPort的第三個參數(shù)應該是一個完成鍵,一般來講,程序都是傳遞一個單句柄數(shù)據(jù)結(jié)構(gòu)的地址,該單句柄數(shù)據(jù)包含了和該 客戶端連接有關(guān)的信息,由于我們只關(guān)心套接字句柄,所以直接將套接字句柄作為完成鍵傳遞;
(3).觸發(fā)一個WSARecv異步調(diào)用,這次又用到了“尾隨數(shù)據(jù)”,使接收數(shù)據(jù)所用的緩沖區(qū)緊跟在WSAOVERLAPPED對象之后,此外,還有操作類型等重要信息。

在工作者線程的循環(huán)中,我們
1.調(diào)用GetQueuedCompletionStatus取得本次I/O的相關(guān)信息(例如套接字句柄、傳送的字節(jié)數(shù)、單I/O數(shù)據(jù)結(jié)構(gòu)的地址等等)
2.通過單I/O數(shù)據(jù)結(jié)構(gòu)找到接收數(shù)據(jù)緩沖區(qū),然后將數(shù)據(jù)原封不動的發(fā)送到客戶端
3.再次觸發(fā)一個WSARecv異步操作

六.五種I/O模型的比較
我會從以下幾個方面來進行比較
*有無每線程64連接數(shù)限制
如 果在選擇模型中沒有重新定義FD_SETSIZE宏,則每個fd_set默認可以裝下64個SOCKET。同樣的,受 MAXIMUM_WAIT_OBJECTS宏的影響,事件選擇、用事件通知實現(xiàn)的重疊I/O都有每線程最大64連接數(shù)限制。如果連接數(shù)成千上萬,則必須對 客戶端套接字進行分組,這樣,勢必增加程序的復雜度。
相反,異步選擇、用完成例程實現(xiàn)的重疊I/O和完成端口不受此限制。

*線程數(shù)
除了異步選擇以外,其他模型至少需要2個線程。一個主線程和一個輔助線程。同樣的,如果連接數(shù)大于64,則選擇模型、事件選擇和用事件通知實現(xiàn)的重??I/O的線程數(shù)還要增加。

*實現(xiàn)的復雜度
我的個人看法是,在實現(xiàn)難度上,異步選擇<選擇<用完成例程實現(xiàn)的重疊I/O<事件選擇<完成端口<用事件通知實現(xiàn)的重疊I/O

*性能
由 于選擇模型中每次都要重設讀集,在select函數(shù)返回后還要針對所有套接字進行逐一測試,我的感覺是效率比較差;完成端口和用完成例程實現(xiàn)的重疊I/O 基本上不涉及全局數(shù)據(jù),效率應該是最高的,而且在多處理器情形下完成端口還要高一些;事件選擇和用事件通知實現(xiàn)的重疊I/O在實現(xiàn)機制上都是采用 WSAWaitForMultipleEvents,感覺效率差不多;至于異步選擇,不好比較。所以我的結(jié)論是:選擇<用事件通知實現(xiàn)的重疊 I/O<事件選擇<用完成例程實現(xiàn)的重疊I/O<完成端口

 

WinSock學習筆記
Socket(套接字)

◆先看定義:
typedef unsigned int u_int;
typedef u_int SOCKET;
◆Socket相當于進行網(wǎng)絡通信兩端的插座,只要對方的Socket和自己的Socket有通信聯(lián)接,雙方就可以發(fā)送和接收數(shù)據(jù)了。其定義類似于文件句柄的定義。

◆Socket有五種不同的類型:

1、流式套接字(stream socket)
定義:
#define SOCK_STREAM 1 
流式套接字提供了雙向、有序的、無重復的以及無記錄邊界的數(shù)據(jù)流服務,適合處理大量數(shù)據(jù)。它是面向聯(lián)結(jié)的,必須建立數(shù)據(jù)傳輸鏈路,同時還必須對傳輸?shù)臄?shù)據(jù)進行驗證,確保數(shù)據(jù)的準確性。因此,系統(tǒng)開銷較大。

2、 數(shù)據(jù)報套接字(datagram socket)

定義:
#define SOCK_DGRAM 2 
數(shù)據(jù)報套接字也支持雙向的數(shù)據(jù)流,但不保證傳輸數(shù)據(jù)的準確性,但保留了記錄邊界。由于數(shù)據(jù)報套接字是無聯(lián)接的,例如廣播時的聯(lián)接,所以并不保證接收端是否正在偵聽。數(shù)據(jù)報套接字傳輸效率比較高。

3、原始套接字(raw-protocol interface)

定義:
#define SOCK_RAW 3 
原始套接字保存了數(shù)據(jù)包中的完整IP頭,前面兩種套接字只能收到用戶數(shù)據(jù)。因此可以通過原始套接字對數(shù)據(jù)進行分析。
其它兩種套接字不常用,這里就不介紹了。

◆Socket開發(fā)所必須需要的文件(以WinSock V2.0為例):

頭文件:Winsock2.h

庫文件:WS2_32.LIB

動態(tài)庫:W32_32.DLL

一些重要的定義

1、數(shù)據(jù)類型的基本定義:這個大家一看就懂。
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
2、 網(wǎng)絡地址的數(shù)據(jù)結(jié)構(gòu),有一個老的和一個新的的,請大家留意,如果想知道為什么,
請發(fā)郵件給Bill Gate。其實就是計算機的IP地址,不過一般不用用點分開的IP地
址,當然也提供一些轉(zhuǎn)換函數(shù)。

◆ 舊的網(wǎng)絡地址結(jié)構(gòu)的定義,為一個4字節(jié)的聯(lián)合:
struct in_addr 
 {
 union 
 {
 struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
 struct { u_short s_w1,s_w2; } S_un_w;
 u_long S_addr;
 } S_un;
 #define s_addr S_un.S_addr /* can be used for most tcp & ip code */
 //下面幾行省略,反正沒什么用處。
 };
其實完全不用這么麻煩,請看下面:

◆ 新的網(wǎng)絡地址結(jié)構(gòu)的定義:
非常簡單,就是一個無符號長整數(shù) unsigned long。舉個例子:IP地址為127.0.0.1的網(wǎng)絡地址是什么呢?請看定義:
#define INADDR_LOOPBACK 0x7f000001

3、 套接字地址結(jié)構(gòu)

(1)、sockaddr結(jié)構(gòu):
struct sockaddr {
 u_short sa_family; /* address family */
 char sa_data[14]; /* up to 14 bytes of direct address */
 };
sa_family 為網(wǎng)絡地址類型,一般為AF_INET,表示該socket在Internet域中進行通信,該地址結(jié)構(gòu)隨選擇的協(xié)議的不同而變化,因此一般情況下另一個 與該地址結(jié)構(gòu)大小相同的sockaddr_in結(jié)構(gòu)更為常用,sockaddr_in結(jié)構(gòu)用來標識TCP/IP協(xié)議下的地址。換句話說,這個結(jié)構(gòu)是通用 socket地址結(jié)構(gòu),而下面的sockaddr_in是專門針對Internet域的socket地址結(jié)構(gòu)。

(2)、sockaddr_in結(jié)構(gòu)
struct sockaddr_in {
 short sin_family;
 u_short sin_port;
 struct in_addr sin_addr;
 char sin_zero[8];
};
sin _family 為網(wǎng)絡地址類型,必須設定為AF_INET。sin_port為服務端口,注意不要使用已固定的服務端口,如HTTP的端口80等。如果端口設置為0,則 系統(tǒng)會自動分配一個唯一端口。sin_addr為一個unsigned long的IP地址。sin_zero為填充字段,純粹用來保證結(jié)構(gòu)的大小。

◆ 將常用的用點分開的IP地址轉(zhuǎn)換為unsigned long類型的IP地址的函數(shù):
unsigned long inet_addr(const char FAR * cp )
用法:
unsigned long addr=inet_addr("192.1.8.84")

◆ 如果將sin_addr設置為INADDR_ANY,則表示所有的IP地址,也即所有的計算機。
#define INADDR_ANY (u_long)0x00000000

4、 主機地址:

先看定義:
struct hostent {
 char FAR * h_name; /* official name of host */
 char FAR * FAR * h_aliases; /* alias list */
 short h_addrtype; /* host address type */
 short h_length; /* length of address */
 char FAR * FAR * h_addr_list; /* list of addresses */
 #define h_addr h_addr_list[0] /* address, for backward compat */
 };
h_name為主機名字。
h_aliases為主機別名列表。
h_addrtype為地址類型。
h_length為地址類型。
h_addr_list為IP地址,如果該主機有多個網(wǎng)卡,就包括地址的列表。
另外還有幾個類似的結(jié)構(gòu),這里就不一一介紹了。

5、 常見TCP/IP協(xié)議的定義:
#define IPPROTO_IP 0 
#define IPPROTO_ICMP 1 
#define IPPROTO_IGMP 2 
#define IPPROTO_TCP 6 
#define IPPROTO_UDP 17 
#define IPPROTO_RAW 255 
具體是什么協(xié)議,大家一看就知道了。

套接字的屬性

為了靈活使用套接字,我們可以對它的屬性進行設定。

1、 屬性內(nèi)容:
//允許調(diào)試輸出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否監(jiān)聽模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字與其他套接字的地址綁定
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持連接
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//設置為廣播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用環(huán)回不通過硬件
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//當前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入帶外數(shù)據(jù)
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER選項
#define SO_DONTLINGER (int)(~SO_LINGER)
//發(fā)送緩沖區(qū)長度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收緩沖區(qū)長度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//發(fā)送超時時間
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超時時間
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//錯誤狀態(tài)
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字類型
#define SO_TYPE 0x1008 /* get socket type */

2、 讀取socket屬性:
int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)
s為欲讀取屬性的套接字。level為套接字選項的級別,大多數(shù)是特定協(xié)議和套接字專有的。如IP協(xié)議應為 IPPROTO_IP。

optname為讀取選項的名稱
optval為存放選項值的緩沖區(qū)指針。
optlen為緩沖區(qū)的長度
用法:
int ttl=0; //讀取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//來自MS platform SDK 2003

3、 設置socket屬性:
int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)
s為欲設置屬性的套接字。
level為套接字選項的級別,用法同上。
optname為設置選項的名稱
optval為存放選項值的緩沖區(qū)指針。
optlen為緩沖區(qū)的長度

用法:
int ttl=32; //設置TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));

套接字的使用步驟

1、啟動Winsock:對Winsock DLL進行初始化,協(xié)商Winsock的版本支持并分配必要的
資源。(服務器端和客戶端)
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
wVersionRequested為打算加載Winsock的版本,一般如下設置:
wVersionRequested=MAKEWORD(2,0)
或者直接賦值:wVersionRequested=2

LPWSADATA為初始化Socket后加載的版本的信息,定義如下:
typedef struct WSAData {
 WORD wVersion;
 WORD wHighVersion;
 char szDescription[WSADESCRIPTION_LEN+1];
 char szSystemStatus[WSASYS_STATUS_LEN+1];
 unsigned short iMaxSockets;
 unsigned short iMaxUdpDg;
 char FAR * lpVendorInfo;
 } WSADATA, FAR * LPWSADATA;
如果加載成功后數(shù)據(jù)為:

wVersion=2 表示加載版本為2.0。
wHighVersion=514 表示當前系統(tǒng)支持socket最高版本為2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running" 表示正在運行。
iMaxSockets=0 表示同時打開的socket最大數(shù),為0表示沒有限制。
iMaxUdpDg=0 表示同時打開的數(shù)據(jù)報最大數(shù),為0表示沒有限制。
lpVendorInfo 沒有使用,為廠商指定信息預留。

該函數(shù)使用方法:
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//錯誤處理
}

2、創(chuàng)建套接字:(服務器端和客戶端)
SOCKET socket( int af, int type, int protocol );
af為網(wǎng)絡地址類型,一般為AF_INET,表示在Internet域中使用。
type為套接字類型,前面已經(jīng)介紹了。
protocol為指定網(wǎng)絡協(xié)議,一般為IPPROTO_IP。
用法:
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//錯誤處理
}

3、套接字的綁定:將本地地址綁定到所創(chuàng)建的套接字上。(服務器端和客戶端)
int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s為已經(jīng)創(chuàng)建的套接字。
name為socket地址結(jié)構(gòu),為sockaddr結(jié)構(gòu),如前面討論的,我們一般使用sockaddr_in
結(jié)構(gòu),在使用再強制轉(zhuǎn)換為sockaddr結(jié)構(gòu)。
namelen為地址結(jié)構(gòu)的長度。

用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保證字節(jié)順序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

4、 套接字的監(jiān)聽:(服務器端)
int listen(SOCKET s, int backlog )
s為一個已綁定但未聯(lián)接的套接字。
backlog為指定正在等待聯(lián)接的最大隊列長度,這個參數(shù)非常重要,因為服務器一般可
以提供多個連接。
用法:
int nResult=listen(s,5) //最多5個連接
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

5、套接字等待連接::(服務器端)
SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )
s為處于監(jiān)聽模式的套接字。
sockaddr為接收成功后返回客戶端的網(wǎng)絡地址。
addrlen為網(wǎng)絡地址的長度。

用法:
sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//錯誤處理
}

6、套接字的連結(jié):將兩個套接字連結(jié)起來準備通信。(客戶端)
int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )
s為欲連結(jié)的已創(chuàng)建的套接字。
name為欲連結(jié)的socket地址。
namelen為socket地址的結(jié)構(gòu)的長度。

用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保證字節(jié)順序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保證字節(jié)順序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

7、套接字發(fā)送數(shù)據(jù):(服務器端和客戶端)
int send(SOCKET s, const char FAR * buf, int len, int flags )
s為服務器端監(jiān)聽的套接字。
buf為欲發(fā)送數(shù)據(jù)緩沖區(qū)的指針。
len為發(fā)送數(shù)據(jù)緩沖區(qū)的長度。
flags為數(shù)據(jù)發(fā)送標記。
返回值為發(fā)送數(shù)據(jù)的字符數(shù)。

◆這里講一下這個發(fā)送標記,下面8中討論的接收標記也一樣:

flag取值必須為0或者如下定義的組合:0表示沒有特殊行為。
#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示數(shù)據(jù)應該帶外發(fā)送,所謂帶外數(shù)據(jù)就是TCP緊急數(shù)據(jù)。
MSG_PEEK表???使有用的數(shù)據(jù)復制到緩沖區(qū)內(nèi),但并不從系統(tǒng)緩沖區(qū)內(nèi)刪除。
MSG_DONTROUTE表示不要將包路由出去。

用法:
char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

8、 套接字的數(shù)據(jù)接收:(客戶端)
int recv( SOCKET s, char FAR * buf, int len, int flags )
s為準備接收數(shù)據(jù)的套接字。
buf為準備接收數(shù)據(jù)的緩沖區(qū)。
len為準備接收數(shù)據(jù)緩沖區(qū)的大小。
flags為數(shù)據(jù)接收標記。
返回值為接收的數(shù)據(jù)的字符數(shù)。

用法:
char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

9、中斷套接字連接:通知服務器端或客戶端停止接收和發(fā)送數(shù)據(jù)。(服務器端和客戶端)
int shutdown(SOCKET s, int how)
s為欲中斷連接的套接字。
How為描述禁止哪些操作,取值為:SD_RECEIVE、SD_SEND、SD_BOTH。
#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02
用法:
int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

10、 關(guān)閉套接字:釋放所占有的資源。(服務器端和客戶端)
int closesocket( SOCKET s )
s為欲關(guān)閉的套接字。

用法:
int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}

與socket有關(guān)的一些函數(shù)介紹

1、讀取當前錯誤值:每次發(fā)生錯誤時,如果要對具體問題進行處理,那么就應該調(diào)用這個函數(shù)取得錯誤代碼。 
 int WSAGetLastError(void );
 #define h_errno WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。

2、將主機的unsigned long值轉(zhuǎn)換為網(wǎng)絡字節(jié)順序(32位):為什么要這樣做呢?因為不同的計算機使用不同的字節(jié)順序存儲數(shù)據(jù)。因此任何從Winsock函數(shù)對IP地址和端口號的引用和傳給Winsock函數(shù)的IP地址和端口號均時按照網(wǎng)絡順序組織的。
 
 u_long htonl(u_long hostlong);
 舉例:htonl(0)=0
 htonl(80)= 1342177280

3、將unsigned long數(shù)從網(wǎng)絡字節(jié)順序轉(zhuǎn)換位主機字節(jié)順序,是上面函數(shù)的逆函數(shù)。 
 
 u_long ntohl(u_long netlong);
 舉例:ntohl(0)=0
 ntohl(1342177280)= 80

4、將主機的unsigned short值轉(zhuǎn)換為網(wǎng)絡字節(jié)順序(16位):原因同2: 
 
 u_short htons(u_short hostshort);
 舉例:htonl(0)=0
 htonl(80)= 20480

5、將unsigned short數(shù)從網(wǎng)絡字節(jié)順序轉(zhuǎn)換位主機字節(jié)順序,是上面函數(shù)的逆函數(shù)。 
 u_short ntohs(u_short netshort);
 舉例:ntohs(0)=0
 ntohsl(20480)= 80

6、將用點分割的IP地址轉(zhuǎn)換位一個in_addr結(jié)構(gòu)的地址,這個結(jié)構(gòu)的定義見筆記(一),實際上就是一個unsigned long值。計算機內(nèi)部處理IP地址可是不認識如192.1.8.84之類的數(shù)據(jù)。 
 unsigned long inet_addr( const char FAR * cp );
 舉例:inet_addr("192.1.8.84")=1409810880
 inet_addr("127.0.0.1")= 16777343
如果發(fā)生錯誤,函數(shù)返回INADDR_NONE值。

7、將網(wǎng)絡地址轉(zhuǎn)換位用點分割的IP地址,是上面函數(shù)的逆函數(shù)。 
 char FAR * inet_ntoa( struct in_addr in );
 舉例:char * ipaddr=NULL;
 char addr[20];
 in_addr inaddr;
 inaddr. s_addr=16777343;
 ipaddr= inet_ntoa(inaddr);
 strcpy(addr,ipaddr); 
這樣addr的值就變?yōu)?27.0.0.1。
注意意不要修改返回值或者進行釋放動作。如果函數(shù)失敗就會返回NULL值。

8、獲取套接字的本地地址結(jié)構(gòu): 
 
 int getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數(shù)調(diào)用后獲得的地址值
namelen為緩沖區(qū)的大小。

9、獲取與套接字相連的端地址結(jié)構(gòu):
 int getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數(shù)調(diào)用后獲得的端地址值
namelen為緩沖區(qū)的大小。

10、獲取計算機名:
 int gethostname( char FAR * name, int namelen );
name是存放計算機名的緩沖區(qū)
namelen是緩沖區(qū)的大小
用法:
 char szName[255];
 memset(szName,0,255);
 if(gethostname(szName,255)==SOCKET_ERROR)
 {
     //錯誤處理
 }
返回值為:szNmae="xiaojin"

11、根據(jù)計算機名獲取主機地址: 
 struct hostent FAR * gethostbyname( const char FAR * name );
name為計算機名。
用法:
 hostent * host;
 char* ip;
 host= gethostbyname("xiaojin");
 if(host->h_addr_list[0])
 {
   struct in_addr addr;
    memmove(&addr, host->h_addr_list[0],4);
   //獲得標準IP地址
   ip=inet_ ntoa (addr);
 }

返回值為:hostent->h_name="xiaojin"
hostent->h_addrtype=2 //AF_INET
hostent->length=4
ip="127.0.0.1"

Winsock 的I/O操作:

1、 兩種I/O模式 
阻塞模式:執(zhí)行I/O操作完成前會一直進行等待,不會將控制權(quán)交給程序。套接字 默認為阻塞模式。可以通過多線程技術(shù)進行處理。 
非阻塞模式:執(zhí)行I/O操作時,Winsock函數(shù)會返回并交出控制權(quán)。這種模式使用 起來比較復雜,因為函數(shù)在沒有運行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種:

2、select模型:

  通過調(diào)用select函數(shù)可以確定一個或多個套接字的狀態(tài),判斷套接字上是否有數(shù)據(jù),或
者能否向一個套接字寫入數(shù)據(jù)。 
int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds, 
 fd_set FAR *exceptfds, const struct timeval FAR * timeout );

◆先來看看涉及到的結(jié)構(gòu)的定義:
a、 d_set結(jié)構(gòu):
#define FD_SETSIZE 64?
typedef struct fd_set {
 u_int fd_count; /* how many are SET? */
 SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
 } fd_set;

fd_count為已設定socket的數(shù)量
fd_array為socket列表,F(xiàn)D_SETSIZE為最大socket數(shù)量,建議不小于64。這是微軟建
議的。

B、timeval結(jié)構(gòu): 
struct timeval {
 long tv_sec; /* seconds */
 long tv_usec; /* and microseconds */
 };
tv_sec為時間的秒值。
tv_usec為時間的毫秒值。
這個結(jié)構(gòu)主要是設置select()函數(shù)的等待值,如果將該結(jié)構(gòu)設置為(0,0),則select()函數(shù)
會立即返回。

◆再來看看select函數(shù)各參數(shù)的作用: 
nfds:沒有任何用處,主要用來進行系統(tǒng)兼容用,一般設置為0。

readfds:等待可讀性檢查的套接字組。

writefds;等待可寫性檢查的套接字組。

exceptfds:等待錯誤檢查的套接字組。

timeout:超時時間。

函數(shù)失敗的返回值:
調(diào)用失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變量至少有一個不為空,同時這個不為空的套接字組
種至少有一個socket,道理很簡單,否則要select干什么呢。 

舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
  //成功
  if(FD_ISSET(s,&fread) //是否存在fread中,詳細定義請看winsock2.h
  {
    //是可讀的
  }
}

◆I/O操作函數(shù):主要用于獲取與套接字相關(guān)的操作參數(shù)。 
 int ioctlsocket(SOCKET s, long cmd, u_long FAR * argp ); 
s為I/O操作的套接字。
cmd為對套接字的操作命令。
argp為命令所帶參數(shù)的指針。

常見的命令: //確定套接字自動讀入的數(shù)據(jù)量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外數(shù)據(jù)都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */

3、WSAAsynSelect模型:
WSAAsynSelect模型也是一個常用的異步I/O模型。應用程序可以在一個套接字上接收以
WINDOWS消息為基礎的網(wǎng)絡事件通知。該模型的實現(xiàn)方法是通過調(diào)用WSAAsynSelect函
數(shù) 自動將套接字設置為非阻塞模式,并向WINDOWS注冊一個或多個網(wǎng)絡時間,并提供一
個通知時使用的窗口句柄。當注冊的事件發(fā)生時,對應的窗口將收到一個基于消息的通知。
 int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent); 
s為需要事件通知的套接字
hWnd為接收消息的窗口句柄
wMsg為要接收的消息
lEvent為掩碼,指定應用程序感興趣的網(wǎng)絡事件組合,主要如下: 
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
  //錯誤處理
}
取消通知:
 int nResult= WSAAsyncSelect(s,hWnd,0,0); 
當應用程序窗口hWnd收到消息時,wMsg.wParam參數(shù)標識了套接字,lParam的低字標明
了網(wǎng)絡事件,高字則包含錯誤代碼。

4、WSAEventSelect模型
WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區(qū)別是網(wǎng)絡事件發(fā)生時會被發(fā)
送到一個事件對象句柄,而不是發(fā)送到一個窗口。

使用步驟如下:
a、 創(chuàng)建事件對象來接收網(wǎng)絡事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函數(shù)的返回值為一個事件對象句柄,它具有兩種工作狀態(tài):已傳信(signaled)和未傳信
(nonsignaled)以及兩種工作模式:人工重設(manual reset)和自動重設(auto reset)。默認未
未傳信的工作狀態(tài)和人工重設模式。

b、將事件對象與套接字關(guān)聯(lián),同時注冊事件,使事件對象的工作狀態(tài)從未傳信轉(zhuǎn)變未
已傳信。
 int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents ); 
s為套接字
hEventObject為剛才創(chuàng)建的事件對象句柄
lNetworkEvents為掩碼,定義如上面所述

c、I/O處理后,設置事件對象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );
Hevent為事件對象

成功返回TRUE,失敗返回FALSE。

d、等待網(wǎng)絡事件來觸發(fā)事件句柄的工作狀態(tài):
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );
lpEvent為事件句柄數(shù)組的指針
cEvent為為事件句柄的數(shù)目,其最大值為WSA_MAXIMUM_WAIT_EVENTS 
fWaitAll指定等待類型:TRUE:當lphEvent數(shù)組重所有事件對象同時有信號時返回;
FALSE:任一事件有信號就返回。
dwTimeout為等待超時(毫秒)
fAlertable為指定函數(shù)返回時是否執(zhí)行完成例程

對事件數(shù)組中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去
預聲明值WSA_WAIT_EVENT_0,得到具體的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];

e、判斷網(wǎng)絡事件類型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
s為套接字
hEventObject為需要重設的事件對象
lpNetworkEvents為記錄網(wǎng)絡事件和錯誤代碼,其結(jié)構(gòu)定義如下:
typedef struct _WSANETWORKEVENTS {
  long lNetworkEvents;
  int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

f、關(guān)閉事件對象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);
調(diào)用成功返回TRUE,否則返回FALSE。
posted on 2010-01-09 00:15 chatler 閱讀(8346) 評論(0)  編輯 收藏 引用 所屬分類: Socket
<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用鏈接

留言簿(10)

隨筆分類(307)

隨筆檔案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感覺這個博客還是不錯,雖然做的東西和我不大相關(guān),覺得看看還是有好處的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            免费在线欧美黄色| 欧美四级在线观看| 久久国产精品99久久久久久老狼| 一区二区三区不卡视频在线观看| 午夜激情综合网| 在线视频欧美精品| 日韩亚洲一区二区| 国产精品毛片一区二区三区| 久久精品视频在线看| 亚洲一二三区视频在线观看| 国产一区二区三区精品欧美日韩一区二区三区 | 今天的高清视频免费播放成人| 亚洲欧美日韩精品久久久| 亚洲图中文字幕| 99re亚洲国产精品| 亚洲一二三区精品| 亚洲国产精品t66y| 国产精品www色诱视频| 久久国产福利国产秒拍| 亚洲精品欧美专区| 99re8这里有精品热视频免费| 久久经典综合| 欧美国产激情二区三区| 欧美日本高清视频| 欧美成ee人免费视频| 国产精品久久97| 国产一区二区丝袜高跟鞋图片| 欧美一区1区三区3区公司| 欧美成人影音| 午夜欧美大尺度福利影院在线看| 亚洲第一精品福利| 亚洲一区二区三区精品视频| 亚洲激情综合| 亚洲国产成人高清精品| 亚洲青色在线| 亚洲国产精品久久久久久女王| 亚洲一区二区三区色| 亚洲第一在线综合在线| 国产精品黄视频| 亚洲一区二区久久| 可以看av的网站久久看| 在线日韩电影| 亚洲精品一区二区三区福利| 老司机成人网| 国内精品国语自产拍在线观看| 久久蜜桃资源一区二区老牛| 亚洲伊人网站| 亚洲精品欧美日韩专区| 国产一区二区三区观看| 你懂的亚洲视频| 一本色道久久88精品综合| 午夜天堂精品久久久久 | 精品白丝av| 久久久国产成人精品| 久久久久久久久一区二区| 久久久久高清| 欧美成人午夜77777| 久久综合婷婷| 亚洲精品网址在线观看| 国产精品99久久久久久久vr| 久久午夜国产精品| 国产精品www网站| 国产一区二区0| 91久久国产综合久久蜜月精品| 久久xxxx| 免费成人美女女| 久久精品欧美日韩精品| 欧美精品久久久久久久免费观看| 亚洲韩国一区二区三区| 久久久久久综合| 欧美激情bt| 9色porny自拍视频一区二区| 国产欧美日韩视频在线观看 | 亚洲第一区色| 欧美国产在线电影| 久久精品国产久精国产一老狼 | 亚洲精品日韩综合观看成人91| 免费成人黄色| 国产精品久久久久999| 亚洲经典在线| 久久久精品视频成人| 最新成人在线| 亚洲欧美成人| 欧美在线免费观看| 欧美国产日韩免费| 亚洲欧美一区二区在线观看| 亚洲国产成人久久| 激情五月婷婷综合| 亚洲第一福利在线观看| 亚洲手机视频| 女女同性女同一区二区三区91| 一区二区三区日韩精品| 亚洲第一在线| 国产亚洲毛片| 一区二区三区四区五区视频| 噜噜噜91成人网| 精品88久久久久88久久久| 你懂的成人av| 久久久久久久性| 欧美一级精品大片| 欧美精品一卡| 亚洲特黄一级片| 久久伊人一区二区| 亚洲一区精品视频| 日韩午夜av| 亚洲女性喷水在线观看一区| 亚洲欧美在线磁力| 欧美中文字幕视频在线观看| 麻豆91精品| 久久久久网址| 免费久久99精品国产自| 欧美va天堂在线| 久久久99国产精品免费| 亚洲一区二区成人| 玖玖综合伊人| 宅男精品视频| 午夜免费日韩视频| 亚洲国产欧洲综合997久久| 欧美在线视频免费| 久久久精品视频成人| 久久免费视频网| 久久久久久久久综合| 国产精品久线观看视频| 狠狠色狠狠色综合人人| 亚洲精品免费一区二区三区| 亚洲综合三区| 一区二区三区欧美在线| 国产精品一区二区在线观看网站| 媚黑女一区二区| 国产欧美一区视频| 亚洲午夜精品网| 亚洲三级国产| 久久久久久久久伊人| 亚洲国产精品综合| 榴莲视频成人在线观看| 欧美成人精品| 久久看片网站| 91久久久一线二线三线品牌| 久热精品视频| 欧美激情第8页| 久久成人av少妇免费| 亚洲欧美国产另类| 性久久久久久久久| 欧美午夜电影在线| 久久久久久久综合色一本| 老鸭窝毛片一区二区三区| 一区二区av在线| 久久久美女艺术照精彩视频福利播放| 欧美一级黄色录像| 欧美电影免费观看| 欧美三级在线| 男人的天堂亚洲| 亚洲风情亚aⅴ在线发布| 牛夜精品久久久久久久99黑人 | 亚洲午夜羞羞片| 欧美午夜视频在线观看| 小黄鸭精品密入口导航| 老司机午夜免费精品视频| 久久成人人人人精品欧| 久久精品夜色噜噜亚洲a∨| 欧美一区二区三区播放老司机| 亚洲精品一区在线观看| 国产精品高清网站| 日韩视频免费大全中文字幕| 亚洲第一中文字幕| 欧美成人午夜| 亚洲二区视频| 亚洲精品极品| 欧美在线视频免费| 亚洲国产日韩欧美一区二区三区| 午夜国产精品影院在线观看| 久久精品视频免费| 国产欧美日韩一级| 亚洲欧美日韩电影| 亚洲一区二区三区四区视频| 国产精品va在线播放| 亚洲福利一区| 久久久五月天| 亚洲第一天堂av| 国产精品久久久久久久久| 亚洲午夜电影在线观看| 久久久久久九九九九| 国产日本欧美一区二区三区在线| 一区二区电影免费在线观看| 狠狠色综合一区二区| 性欧美大战久久久久久久免费观看| 欧美一区二区三区电影在线观看| 国内成+人亚洲| 欧美精品二区| 亚洲私人影院在线观看| 久久大综合网| 国产伦精品一区二区三| 欧美91视频| 午夜亚洲一区| 亚洲欧美日韩另类| 一区二区三区视频观看| 亚洲黄色片网站| 欧美中文在线观看| 欧美一区二区福利在线| 欧美日韩激情小视频|