• <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>
            posts - 297,  comments - 15,  trackbacks - 0
            Winsock 的I/O操作:

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

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

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

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

            #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;
            }

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

            比 較容易想到的一種服務(wù)器模型就是采用一個主線程,負(fù)責(zé)監(jiān)聽客戶端的連接請求,當(dāng)接收到某個客戶端的連接請求后,創(chuàng)建一個專門用于和該客戶端通信的套接字和 一個輔助線程。以后該客戶端和服務(wù)器的交互都在這個輔助線程內(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的管 理。最初設(shè)計該模型時,主要面向的是某些使用UNIX操作系統(tǒng)的計算機(jī),它們采用的是Berkeley套接字方案。Select模型已集成到 Winsock 1.1中,它使那些想避免在套接字調(diào)用過程中被無辜“鎖定”的應(yīng)用程序,采取一種有序的方式,同時進(jìn)行對多個套接字的管理。由于 Winsock 1.1向后兼容于Berkeley套接字實施方案,所以假如有一個Berkeley套接字應(yīng)用使用了select函數(shù),那么從理論角度 講,毋需對其進(jìn)行任何修改,便可正常運行。(節(jié)選自《Windows網(wǎng)絡(luò)編程》第八章)
            下面的這段程序就是利用選擇模型實現(xiàn)的Echo服務(wù)器的代碼(已經(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;
            }

            服務(wù)器的幾個主要動作如下:
            1.創(chuàng)建監(jiān)聽套接字,綁定,監(jiān)聽;
            2.創(chuàng)建工作者線程;
            3.創(chuàng)建一個套接字?jǐn)?shù)組,用來存放當(dāng)前所有活動的客戶端套接字,每accept一個連接就更新一次數(shù)組;
            4. 接受客戶端的連接。這里有一點需要注意的,就是我沒有重新定義FD_SETSIZE宏,所以服務(wù)器最多支持的并發(fā)連接數(shù)為64。而且,這里決不能無條件的 accept,服務(wù)器應(yīng)該根據(jù)當(dāng)前的連接數(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 (當(dāng)前連接數(shù) < FD_SETSIZE)
              return CF_ACCEPT;
            else
              return CF_REJECT;
            }

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

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

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

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

            二.異步選擇
            Winsock 提供了一個有用的異步I/O模型。利用這個模型,應(yīng)用程序可在一個套接字上,接收以Windows消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知。具體的做法是在建好一個套接 字后,調(diào)用WSAAsyncSelect函數(shù)。該模型最早出現(xiàn)于Winsock的1.1版本中,用于幫助應(yīng)用程序開發(fā)者面向一些早期的16位 Windows平臺(如Windows for Workgroups),適應(yīng)其“落后”的多任務(wù)消息環(huán)境。應(yīng)用程序仍可從這種模型中得到好處,特別是它 們用一個標(biāo)準(zhǔn)的Windows例程(常稱為"WndProc"),對窗口消息進(jìn)行管理的時候。該模型亦得到了 Microsoft Foundation Class(微軟基本類,MFC)對象CSocket的采納。(節(jié)選自《Windows網(wǎng)絡(luò)編程》第八章)
            我還是先貼出代碼,然后做詳細(xì)解釋:
            #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寫過窗口類應(yīng)用程序的人應(yīng)該都能看得懂。這里,我們需要做的僅僅是:
            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ù)被設(shè)置為WM_SOCKET;
            3.在WM_SOCKET的消息處理函數(shù)中,分別對FD_ACCEPT、FD_READ和FD_CLOSE事件進(jìn)行處理;
            4.在窗口銷毀消息(WM_DESTROY)的處理函數(shù)中,我們關(guān)閉監(jiān)聽套接字,清除Windows Socket library

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

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

            三.事件選擇
            Winsock 提供了另一個有用的異步I/O模型。和WSAAsyncSelect模型類似的是,它也允許應(yīng)用程序在一個或多個套接字上,接收以事件為基礎(chǔ)的網(wǎng)絡(luò)事件通 知。對于表1總結(jié)的、由WSAAsyncSelect模型采用的網(wǎng)絡(luò)事件來說,它們均可原封不動地移植到新模型。在用新模型開發(fā)的應(yīng)用程序中,也能接收和 處理所有那些事件。該模型最主要的差別在于網(wǎng)絡(luò)事件會投遞至一個事件對象句柄,而非投遞至一個窗口例程。(節(jié)選自《Windows網(wǎng)絡(luò)編程》第八章)
            還是讓我們先看代碼然后進(jìn)行分析:
            #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)起來也不是太復(fù)雜,它的基本思想是將每個套接字都和一個WSAEVENT對象對應(yīng)起來,并且在關(guān)聯(lián)的時候指定需要關(guān)注的哪些網(wǎng) 絡(luò)事件。一旦在某個套接字上發(fā)生了我們關(guān)注的事件(FD_READ和FD_CLOSE),與之相關(guān)聯(lián)的WSAEVENT對象被Signaled。程序定義 了兩個全局?jǐn)?shù)組,一個套接字?jǐn)?shù)組,一個WSAEVENT對象數(shù)組,其大小都是MAXIMUM_WAIT_OBJECTS(64),兩個數(shù)組中的元素一一對 應(yīng)。
            同樣的,這里的程序沒有考慮兩個問題,一是不能無條件的調(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來進(jìn)行Socket I/O。伴隨而來的,用于普通文件I/O的 重疊I/O模型和完成端口模型對Socket I/O也適用了。這些模型的優(yōu)點是可以達(dá)到更佳的系統(tǒng)性能,但是實現(xiàn)較為復(fù)雜,里面涉及較多的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ù)到達(dá)的時候,被指定的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)在應(yīng) 該做的就是用與調(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)用完成例程(因為完成例程 的運行應(yīng)該和當(dāng)初激活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語言技巧在我后面介紹完成端口的時候還會使用到。

            五.完成端口模型
            “完 成端口”模型是迄今為止最為復(fù)雜的一種I/O模型。然而,假若一個應(yīng)用程序同時需要管理為數(shù)眾多的套接字,那么采用這種模型,往往可以達(dá)到最佳的系統(tǒng)性 能!但不幸的是,該模型只適用于Windows NT和Windows 2000操作系統(tǒng)。因其設(shè)計的復(fù)雜性,只有在你的應(yīng)用程序需要同時管理數(shù)百乃至上 千個套接字的時候,而且希望隨著系統(tǒng)內(nèi)安裝的CPU數(shù)量的增多,應(yīng)用程序的性能也可以線性提升,才應(yīng)考慮采用“完成端口”模型。要記住的一個基本準(zhǔn)則是, 假如要為Windows NT或Windows 2000開發(fā)高性能的服務(wù)器應(yīng)用,同時希望為大量套接字I/O請求提供服務(wù)(Web服務(wù)器便是這方面的典 型例子),那么I/O完成端口模型便是最佳選擇!(節(jié)選自《Windows網(wǎng)絡(luò)編程》第八章)
            完成端口模型是我最喜愛的一種模型。雖然其實現(xiàn)比較 復(fù)雜(其實我覺得它的實現(xiàn)比用事件通知實現(xiàn)的重疊I/O簡單多了),但其效率是驚人的。我在T公司的時候曾經(jīng)幫同事寫過一個郵件服務(wù)器的性能測試程序,用 的就是完成端口模型。結(jié)果表明,完成端口模型在多連接(成千上萬)的情況下,僅僅依靠一兩個輔助線程,就可以達(dá)到非常高的吞吐量。下面我還是從代碼說起:
            #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ù)來決定的,這樣可以達(dá)到最佳性能)
            3.創(chuàng)建監(jiān)聽套接字,綁定,監(jiān)聽,然后程序進(jìn)入循環(huán)
            4.在循環(huán)中,我做了以下幾件事情:
            (1).接受一個客戶端連接
            (2). 將該客戶端套接字與完成端口綁定到一起(還是調(diào)用CreateIoCompletionPort,但這次的作用不同),注意,按道理來講,此時傳遞給 CreateIoCompletionPort的第三個參數(shù)應(yīng)該是一個完成鍵,一般來講,程序都是傳遞一個單句柄數(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模型的比較
            我會從以下幾個方面來進(jìn)行比較
            *有無每線程64連接數(shù)限制
            如 果在選擇模型中沒有重新定義FD_SETSIZE宏,則每個fd_set默認(rèn)可以裝下64個SOCKET。同樣的,受 MAXIMUM_WAIT_OBJECTS宏的影響,事件選擇、用事件通知實現(xiàn)的重疊I/O都有每線程最大64連接數(shù)限制。如果連接數(shù)成千上萬,則必須對 客戶端套接字進(jìn)行分組,這樣,勢必增加程序的復(fù)雜度。
            相反,異步選擇、用完成例程實現(xiàn)的重疊I/O和完成端口不受此限制。

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

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

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

             

            WinSock學(xué)習(xí)筆記
            Socket(套接字)

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

            ◆Socket有五種不同的類型:

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

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

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

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

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

            ◆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)絡(luò)地址的數(shù)據(jù)結(jié)構(gòu),有一個老的和一個新的的,請大家留意,如果想知道為什么,
            請發(fā)郵件給Bill Gate。其實就是計算機(jī)的IP地址,不過一般不用用點分開的IP地
            址,當(dāng)然也提供一些轉(zhuǎn)換函數(shù)。

            ◆ 舊的網(wǎng)絡(luò)地址結(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)絡(luò)地址結(jié)構(gòu)的定義:
            非常簡單,就是一個無符號長整數(shù) unsigned long。舉個例子:IP地址為127.0.0.1的網(wǎng)絡(luò)地址是什么呢?請看定義:
            #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)絡(luò)地址類型,一般為AF_INET,表示該socket在Internet域中進(jìn)行通信,該地址結(jié)構(gòu)隨選擇的協(xié)議的不同而變化,因此一般情況下另一個 與該地址結(jié)構(gòu)大小相同的sockaddr_in結(jié)構(gòu)更為常用,sockaddr_in結(jié)構(gòu)用來標(biāo)識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)絡(luò)地址類型,必須設(shè)定為AF_INET。sin_port為服務(wù)端口,注意不要使用已固定的服務(wù)端口,如HTTP的端口80等。如果端口設(shè)置為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設(shè)置為INADDR_ANY,則表示所有的IP地址,也即所有的計算機(jī)。
            #define INADDR_ANY (u_long)0x00000000

            4、 主機(jī)地址:

            先看定義:
            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為主機(jī)名字。
            h_aliases為主機(jī)別名列表。
            h_addrtype為地址類型。
            h_length為地址類型。
            h_addr_list為IP地址,如果該主機(jī)有多個網(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é)議,大家一看就知道了。

            套接字的屬性

            為了靈活使用套接字,我們可以對它的屬性進(jìn)行設(shè)定。

            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 */
            //設(shè)置為廣播
            #define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
            //使用環(huán)回不通過硬件
            #define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
            //當(dāng)前拖延值
            #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é)議應(yīng)為 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、 設(shè)置socket屬性:
            int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)
            s為欲設(shè)置屬性的套接字。
            level為套接字選項的級別,用法同上。
            optname為設(shè)置選項的名稱
            optval為存放選項值的緩沖區(qū)指針。
            optlen為緩沖區(qū)的長度

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

            套接字的使用步驟

            1、啟動Winsock:對Winsock DLL進(jìn)行初始化,協(xié)商Winsock的版本支持并分配必要的
            資源。(服務(wù)器端和客戶端)
            int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
            wVersionRequested為打算加載Winsock的版本,一般如下設(shè)置:
            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 表示當(dāng)前系統(tǒng)支持socket最高版本為2.2。
            szDescription="WinSock 2.0"
            szSystemStatus="Running" 表示正在運行。
            iMaxSockets=0 表示同時打開的socket最大數(shù),為0表示沒有限制。
            iMaxUdpDg=0 表示同時打開的數(shù)據(jù)報最大數(shù),為0表示沒有限制。
            lpVendorInfo 沒有使用,為廠商指定信息預(yù)留。

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

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

            3、套接字的綁定:將本地地址綁定到所創(chuàng)建的套接字上。(服務(wù)器端和客戶端)
            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),在使用再強(qiáng)制轉(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)聽:(服務(wù)器端)
            int listen(SOCKET s, int backlog )
            s為一個已綁定但未聯(lián)接的套接字。
            backlog為指定正在等待聯(lián)接的最大隊列長度,這個參數(shù)非常重要,因為服務(wù)器一般可
            以提供多個連接。
            用法:
            int nResult=listen(s,5) //最多5個連接
            if(nResult==SOCKET_ERROR)
            {
            //錯誤處理
            }

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

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

            6、套接字的連結(jié):將兩個套接字連結(jié)起來準(zhǔn)備通信。(客戶端)
            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ù):(服務(wù)器端和客戶端)
            int send(SOCKET s, const char FAR * buf, int len, int flags )
            s為服務(wù)器端監(jiān)聽的套接字。
            buf為欲發(fā)送數(shù)據(jù)緩沖區(qū)的指針。
            len為發(fā)送數(shù)據(jù)緩沖區(qū)的長度。
            flags為數(shù)據(jù)發(fā)送標(biāo)記。
            返回值為發(fā)送數(shù)據(jù)的字符數(shù)。

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

            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ù)應(yīng)該帶外發(fā)送,所謂帶外數(shù)據(jù)就是TCP緊急數(shù)據(jù)。
            MSG_PEEK表???使有用的數(shù)據(jù)復(fù)制到緩沖區(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為準(zhǔn)備接收數(shù)據(jù)的套接字。
            buf為準(zhǔn)備接收數(shù)據(jù)的緩沖區(qū)。
            len為準(zhǔn)備接收數(shù)據(jù)緩沖區(qū)的大小。
            flags為數(shù)據(jù)接收標(biāo)記。
            返回值為接收的數(shù)據(jù)的字符數(shù)。

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

            9、中斷套接字連接:通知服務(wù)器端或客戶端停止接收和發(fā)送數(shù)據(jù)。(服務(wù)器端和客戶端)
            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)閉套接字:釋放所占有的資源。(服務(wù)器端和客戶端)
            int closesocket( SOCKET s )
            s為欲關(guān)閉的套接字。

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

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

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

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

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

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

            5、將unsigned short數(shù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換位主機(jī)字節(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值。計算機(jī)內(nèi)部處理IP地址可是不認(rèn)識如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)絡(luò)地址轉(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。
            注意意不要修改返回值或者進(jìn)行釋放動作。如果函數(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、獲取計算機(jī)名:
             int gethostname( char FAR * name, int namelen );
            name是存放計算機(jī)名的緩沖區(qū)
            namelen是緩沖區(qū)的大小
            用法:
             char szName[255];
             memset(szName,0,255);
             if(gethostname(szName,255)==SOCKET_ERROR)
             {
                 //錯誤處理
             }
            返回值為:szNmae="xiaojin"

            11、根據(jù)計算機(jī)名獲取主機(jī)地址: 
             struct hostent FAR * gethostbyname( const char FAR * name );
            name為計算機(jī)名。
            用法:
             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);
               //獲得標(biāo)準(zhǔn)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操作完成前會一直進(jìn)行等待,不會將控制權(quán)交給程序。套接字 默認(rèn)為阻塞模式。可以通過多線程技術(shù)進(jìn)行處理。 
            非阻塞模式:執(zhí)行I/O操作時,Winsock函數(shù)會返回并交出控制權(quán)。這種模式使用 起來比較復(fù)雜,因為函數(shù)在沒有運行完成就進(jìn)行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強(qiáng)大。
            為了解決這個問題,提出了進(jìn)行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為已設(shè)定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)主要是設(shè)置select()函數(shù)的等待值,如果將該結(jié)構(gòu)設(shè)置為(0,0),則select()函數(shù)
            會立即返回。

            ◆再來看看select函數(shù)各參數(shù)的作用: 
            nfds:沒有任何用處,主要用來進(jìn)行系統(tǒng)兼容用,一般設(shè)置為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); //加入套接字,詳細(xì)定義請看winsock2.h
            if(select(0,%fdread,NULL,NULL,NULL)>0
            {
              //成功
              if(FD_ISSET(s,&fread) //是否存在fread中,詳細(xì)定義請看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模型。應(yīng)用程序可以在一個套接字上接收以
            WINDOWS消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知。該模型的實現(xiàn)方法是通過調(diào)用WSAAsynSelect函
            數(shù) 自動將套接字設(shè)置為非阻塞模式,并向WINDOWS注冊一個或多個網(wǎng)絡(luò)時間,并提供一
            個通知時使用的窗口句柄。當(dāng)注冊的事件發(fā)生時,對應(yīng)的窗口將收到一個基于消息的通知。
             int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent); 
            s為需要事件通知的套接字
            hWnd為接收消息的窗口句柄
            wMsg為要接收的消息
            lEvent為掩碼,指定應(yīng)用程序感興趣的網(wǎng)絡(luò)事件組合,主要如下: 
            #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); 
            當(dāng)應(yīng)用程序窗口hWnd收到消息時,wMsg.wParam參數(shù)標(biāo)識了套接字,lParam的低字標(biāo)明
            了網(wǎng)絡(luò)事件,高字則包含錯誤代碼。

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

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

            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處理后,設(shè)置事件對象為未傳信
            BOOL WSAResetEvent( WSAEVENT hEvent );
            Hevent為事件對象

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

            d、等待網(wǎng)絡(luò)事件來觸發(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:當(dāng)lphEvent數(shù)組重所有事件對象同時有信號時返回;
            FALSE:任一事件有信號就返回。
            dwTimeout為等待超時(毫秒)
            fAlertable為指定函數(shù)返回時是否執(zhí)行完成例程

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

            e、判斷網(wǎng)絡(luò)事件類型:
            int WSAEnumNetworkEvents( SOCKET s,
            WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
            s為套接字
            hEventObject為需要重設(shè)的事件對象
            lpNetworkEvents為記錄網(wǎng)絡(luò)事件和錯誤代碼,其結(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 閱讀(8330) 評論(0)  編輯 收藏 引用 所屬分類: Socket
            <2010年11月>
            31123456
            78910111213
            14151617181920
            21222324252627
            2829301234
            567891011

            常用鏈接

            留言簿(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

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            亚洲愉拍99热成人精品热久久| 亚洲欧美国产精品专区久久| 国产婷婷成人久久Av免费高清| 77777亚洲午夜久久多喷| 国产三级久久久精品麻豆三级| 国产精品久久久久久影院| 久久久久亚洲精品中文字幕| 欧美丰满熟妇BBB久久久| 久久久久97国产精华液好用吗| 亚洲午夜久久久久久久久电影网| 久久国产精品成人免费| 久久精品人妻一区二区三区| 亚洲级αV无码毛片久久精品| 香港aa三级久久三级| 久久伊人精品一区二区三区| 蜜桃麻豆www久久| 亚洲精品乱码久久久久久蜜桃不卡| 国产高潮久久免费观看| 亚洲AV日韩AV天堂久久| 久久久久亚洲精品无码网址| 国产精品久久久久影院色| 久久精品成人欧美大片| 国产综合成人久久大片91| 91精品国产综合久久久久久| 热99RE久久精品这里都是精品免费 | 国产农村妇女毛片精品久久| 亚洲国产一成人久久精品| 免费精品久久久久久中文字幕| www.久久99| 国产精品一久久香蕉产线看| 亚洲狠狠婷婷综合久久蜜芽| 久久这里只有精品首页| 亚洲国产成人久久一区久久| 久久99精品久久久久久野外| 嫩草影院久久99| 91精品国产综合久久香蕉| 久久久久久综合一区中文字幕| 7777久久亚洲中文字幕| 国内精品久久久久影院一蜜桃| 久久夜色精品国产网站| 熟妇人妻久久中文字幕|