zoukankan      html  css  js  c++  java
  • 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("服务器已经启动
    ");
    }
    
    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("客服端断开
    ");
                            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
    ", sztext1);
                                }
                                else
                                {
                                    v.erase(v.begin() + i);
                                    printf("客服端断开
    ");
                                    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已经连接上
    ", inet_ntoa(addr2.sin_addr));
                        }
                    }
                    printf("服务器接收额已满!
    ");
                }
            }
            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调用认为是非阻塞的,此时非阻塞调用的标准假设适用。例如,阻塞钩子不会调用,窗体套接字不会退出。

  • 相关阅读:
    jquery_DOM笔记3
    jquery_DOM笔记2
    mac之jdk环境变量配置
    360导致的mysql问题解决
    windows服务器apache配置https教程
    生成自己openssl的证书
    wkhtmltopdf中文乱码
    安装ruby、rails
    extjs中返回数据时id不能重复,否则数据会被合并
    gzip压缩ext-all.js
  • 原文地址:https://www.cnblogs.com/zxllm/p/5420065.html
Copyright © 2011-2022 走看看