C++網絡編程之select


select函數決定一個或者多個套接字(socket)的狀態,如果需要的話,等待執行異步I/O。

int select(

              __in        int    nfds,

              __inout    fd_set *readfds,

              __inout  fd_set *writefds,

              __inout  fd_set *exceptfds,

              __int       const struct timeval *timeout

              );

參數

 nfds:忽略。

 readnfds: 指向檢查可讀性的套接字集合的可選的指針。

 writefds: 指向檢查可寫性的套接字集合的可選的指針。

 exceptfds: 指向檢查錯誤的套接字集合的可選的指針。

 timeout: select函數需要等待的最長時間,需要以TIMEVAL結構體格式提供此參數,對於阻塞操作,此參數為null。

 

返回值

       select函數返回那些准備好並且包含在fd_set結構體的套接字的總數,如果超時,則返回0;如果錯誤發生,返回SOCKET_ERROR。如果返回值為SOCKET_ERROR,可以通過WSAGetLastError函數檢索指定的錯誤碼。

錯誤碼

解釋

WSANOTINITIALISTED

在使用此函數之前,WSAStartup函數必須成功的執行

WSAEFALUT

套接字執行時不能分配需要的資源或者readfds、writefds、exceptfds、timeval參數不是用戶地址空間的一部分。

WSAENETDOWN

網絡子系統失敗

WSAEINVAL

超時值不合法的,或者其他的三個參數為空。

WSAEINTR

阻塞的套接字1.1調用通過WSACancelBlockingCall取消

WSAEINPROGRESS

阻塞的套接字1.1調用正在處理或者服務提供者正在處理一個掉用戶函數。

WSAENOTSOCK

描述集中包括一個不是套接字的入口。

說明

       select函數用於決定一個或者多個套接字的狀態。對於每一個套接字,調用者可以請求讀、寫或者錯誤狀態信息。一個請求給定狀態的套接字集由fd_set結構體指定。在fd_set結構體中的套接字必須和單個服務提供者聯系在一起。基於此,如果WSAPROTOCOL_INFO結構體中有相同的providerId值,套接字被認為來自同一個服務提供者。直到返回,結構體更新去反映滿足指定條件套接字子集。select函數返回滿足條件的套接字個數。fd_set集合可以通過一些宏手動操作。這些宏也適合伯克利套接字,但是它們的機理是根本不同的。

       參數readfds指示檢查套接字的可讀性。當套接字在listen狀態,如果已經接收一個連接請求,這個套接字會被標記為可讀,例如一個accept會確保不會阻塞的完成。對於其他的套接字,可讀性意味着隊列中的數據適合讀,當調用recv,WSARecv,WSARecvFrom或者recvfrom后不會阻塞。

       對於面向連接的套接字,可讀性也可以指示關閉套接字的從另一端接收的請求。如果虛電路正常關閉,並且所有的數據都已經接收,然后recv會立刻返回(沒有數據接收),如果虛電路重置,recv會立刻返回錯誤碼,例如WSAECONNRESET。如果套接字選項SO_OOBINLINE置位(參見setsockop),出現的OOB數據將會被檢查。

      參數writefds指示檢查套接字的可寫性。如果套接字處理connect調用(非阻塞的),並且完全建立連接,這時套接字是可寫。如果套接字沒有處理connect調用,可寫性意味着擔保send,sendto或者WSASendto執行成功。但是,如果len參數超過系統的緩存空間大小,它們在阻塞套接字中是可以阻塞的。不確定多長的長度是合法的,尤其在多線程環境下。

      參數exceptfds指示套接字被檢查OOB數據出現或者異常錯誤環境。

      注意:OOB數據僅僅應用當SO_OOBINLINE設置為FALSE的情況下。如果一個套接字處理連接調用(非阻塞模式),試圖連接的錯誤信息在exceptfds中,這個文檔並沒有定義那些錯誤需要包含其中。

      readfd,writefds或者exceptfds中任何兩個參數在調用的時候需要為null。至少一個必須為非空,並且任何一個非空描述設置必須包括至少一個套接字句柄。

      總之,一個套接字將會被指定在一個特殊的集合當select返回如果:

readfds:

①     如果listen函數已經調用並且連接掛起,accept會執行成功。

②     數據適合讀(如果SO_OOBINLINE置位,包括OOB數據)

③     連接被關/重置/終止

writefds:

①     如果處理一個connect調用(非阻塞),連接成功。

②     數據可以發送。

exceptfds:

①     如果處理一個connect調用(非阻塞),連接失敗。

②     OOB數據適合讀(僅當SO_OOBINLINE未置位)

 

在頭文件Winsock2.h中定義四個宏來操作和檢查描述集。FD_SETSIZE決定在描述集合中最大數量(FD_SETSIZE的默認值為64,此值可以在導入Winsock2.h之前通過FD_SETSIZE修改)。

使用這些宏是為了在不同的套接字環境中維護軟件便利。這些宏操作和檢查fd_set內容為:

FD_CLR(s, *set)

         從set集合中移除描述符s

FD_ISSET(s, *set)

         如果s在set中,返回非0,否則返回0

FD_SET(s, *set)

         增加描述符s到set中

FD_ZERO(*set)

         初始化set集合為null集合

#include<WinSock2.h>
#include<stdio.h>
#include<Windows.h>
#include<string>
#include<iostream>
#include<thread>
#include<exception>
#include<future>
#include<vector>
using namespace std;
#pragma comment(lib,"WS2_32.lib")//顯示連接套接字庫
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define SIZE 5

FILE * ffp;
struct fd_set rfds;
struct sockaddr_in ipadd;
struct timeval timeout = { 0, 200 };
char * readbuff[10] = { 0 };


string ss;
vector<SOCKET> v;
char sztext[1024] = { 0 };
char sztext1[1024] = { 0 };
SOCKET s;
int n;
sockaddr_in addr, addr2;
int ret;

void Close()
{
    ::closesocket(s);
    if (!v.empty())
    {
        v.clear();
    }
    ::WSACleanup();
}

void Initialize()
{
    WSADATA data;
    WORD w = MAKEWORD(2, 0);//版本號
    //strcpy(sztext, lastSend.c_str());
    ::WSAStartup(w, &data); //動態鏈接庫初始化
    s = ::socket(AF_INET, SOCK_STREAM, 0);
    n = sizeof(addr2);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(75);
    addr.sin_addr.S_un.S_addr = INADDR_ANY;
    ::bind(s, (sockaddr*)&addr, sizeof(addr));
    ::listen(s, SIZE);
    printf("服務器已經啟動\n");
}

void MyRecv()
{
    try{
        while (true)
        {
            for (int i = 0; i < v.size(); ++i)
            {
                FD_ZERO(&rfds);  /* 清空集合 */
                FD_SET(v.at(i), &rfds);  /* 將fp添加到集合,后面的FD_ISSET和FD_SET沒有必然關系,這里是添加檢測 */

                switch (select(0, &rfds, NULL, NULL, &timeout)) //select使用 
                {
                    case -1:
                        v.erase(v.begin() + i); 
                        printf("客服端斷開\n");
                        break; //select錯誤退出程序 
                    case 0:
                        continue; //再次輪詢 
                    default:
                        if (FD_ISSET(v.at(i), &rfds)) //測試sock是否可讀即是否網絡上有數據
                        {
                            if (::recv(v.at(i), sztext1, sizeof(sztext1), 0) != -1)
                            {
                                printf("%s\r\n", sztext1);
                            }
                            else
                            {
                                v.erase(v.begin() + i);
                                printf("客服端斷開\n");
                                continue;
                            }
                            ss = "";
                            ss += sztext1;
                            strcpy(sztext, ss.c_str());
                            for (int j = 0; j < v.size(); ++j)
                            { 
                                ::send(v.at(j), sztext, sizeof(sztext), 0);
                            }
                        }
                }
            }
        }
    }
    catch (const exception& e)
    {
        cerr << "出錯了" << endl;
        Close();
    }
    return;
}

int main()
{
    SOCKET s1;
    Initialize();
    auto w = async(launch::async, [&]{
        while (true)
        {
            if (v.size() < SIZE)
            {
                for (int i = v.size(); i < SIZE; ++i)
                {
                    s1 = ::accept(s, (sockaddr*)&addr2, &n);
                    if (s1 != NULL)
                    {
                        v.push_back(s1);
                        printf("%s已經連接上\r\n", inet_ntoa(addr2.sin_addr));
                    }
                }
                printf("服務器接收額已滿!\n");
            }
        }
        return;
    });
    try{
        thread t1(MyRecv);
        t1.join();
    }
    catch (const exception& e)
    {
        cerr << "出錯了" << endl;
    }
    Close();
    system("pause");
    return 0;
}

 

         參數time-out控制select函數完成的時間(超過這個時間返回超時)。如果time-out是個空指針,select會一直保持阻塞指導至少一個描述符符合指定的准則。否則,time-out指向一個TIMEVAL結構體,這個結構體指定select在返回之前應該等待最大時間。當select返回,TIMEVAL結構體中的內容是不會改變的。如果TIMEVAL初始化為{0,0},select會立刻返回;這用於得到選擇的套接字的狀態。如果select立刻返回,然后select調用認為是非阻塞的,此時非阻塞調用的標准假設適用。例如,阻塞鈎子不會調用,窗體套接字不會退出。


免責聲明!

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



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