select函数总结

来源:互联网 发布:淘宝古装知乎 编辑:程序博客网 时间:2024/06/04 18:30
select原型:
[windows]WINSOCK_API_LINKAGE int PASCAL select(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*);[linux]int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

1> fd集合里面最大的+1(因为是从0开始)

windows下填0就好

2> read读fd集合
3> write写fd集合
4> error错误fd集合
5> 超时时间

timeval原型:
struct timeval {
    long    tv_sec;
    long    tv_usec;
};

实测:
while(true)    {            //cout << "1" << endl;            nRet=select(0,&readfd,NULL,NULL,&vl);            if(nRet==0)                puts("超时!");            if(nRet==-1)                puts("错误!");            if(nRet > 0)            {                cout << "连接数:" << nRet << endl;            }            //Sleep(1);    }


客户端关闭时:
超时!
错误!
错误!
错误!
。。。
客户端连接时:
两种情况:
A(客户端在超时时间内连接)
连接数:1
连接数:1
连接数:1
。。。
B(客户端在超时时间外连接)
超时!
错误!
错误!
错误!
。。。
这里返回错误代码:10022
10022:返回了一个无效的参数
后查实,发现是FD_SET(listfd,&readfd);没有放在while里面。
加了一段测试代码:
            cout << readfd.fd_count << endl;            nRet=select(0,&readfd,NULL,NULL,&vl);            cout << readfd.fd_count << endl;
结果:
1
0
超时
0
0
错误
。。。
造成结果的原因:
select(0,NULL,NULL,NULL,wait_timeval);

这种方式在Linux下就相当于sleep,而Windows下却之间返回了-1,认为你传递的参数错误。查询了一下MSDN发现有如下说明。
Microsoft MSDN:Any two of the parameters, readfds, writefds, or exceptfds, can be given as null. At least one must be non-null,and any non-null descriptor set must contain at least one handle to a socket.参考文章:http://www.cnblogs.com/fullsail/archive/2012/08/12/2634336.html

以上说明了为什么会返回-1,至于为什么cout打印的值从1变成了0,请看下面:
fd_set的原型:
----------------------------------------------------------------
[windows,down]typedef struct fd_set {    u_int   fd_count;    SOCKET  fd_array[FD_SETSIZE];} fd_set;
----------------------------------------------------------------
[linux,down]typedef struct {    unsigned long fds_bits [__FDSET_LONGS];                 //用ulong数组来表示bitmap} __kernel_fd_set;typedef __kernel_fd_set   fd_set;
----------------------------------------------------------------
linux下的处理方法:
//每个ulong为32位,可以表示32个bit。//fd  >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置 #define __FD_SET(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31)))             //设置对应的bit#define __FD_CLR(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31)))            //清除对应的bit#define __FD_ISSET(fd, fdsetp)   ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0)     //判断对应的bit是否为1#define __FD_ZERO(fdsetp)   (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp))))                             //memset bitmap参考文章:linux中fd_set的内部实现

windows下的处理方法:
#define FD_SET(fd, set) do { u_int __i;\for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\    if (((fd_set *)(set))->fd_array[__i] == (fd)) {\        break;\    }\}\if (__i == ((fd_set *)(set))->fd_count) {\    if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\        ((fd_set *)(set))->fd_array[__i] = (fd);\        ((fd_set *)(set))->fd_count++;\    }\}\} while(0)#define FD_ZERO(set) (((fd_set *)(set))->fd_count=0)#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))

以linux为例:
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
参考文章:http://blog.csdn.net/lingfengtengfei/article/details/12392449

windows下select代码(服务端):

#include <iostream>#include <cstdlib>#include <winsock2.h>#include<cstdio>#include "fcntl.h"using namespace std;int main(){    WSADATA wd;    WSAStartup(0x0202,&wd);    fd_set readfd;    FD_ZERO(&readfd);    timeval vl;    vl.tv_sec=3;    vl.tv_usec=0;    SOCKET listfd=socket(AF_INET,SOCK_STREAM,0);    if(listfd==INVALID_SOCKET)    {        cout << "创建套接字失败,err:" << GetLastError() << endl;    }    //linux下的阻塞模型调整//    int flag=fcntl(listfd,F_GETFL,0);//    fcntl(listfd,F_SETFL,flag | O_NONBLOCK);    u_long non_blk = 1; //1:非阻塞 0:阻塞    ioctlsocket(listfd, FIONBIO, &non_blk); //FIONBIO:socket阻塞模式    sockaddr_in sa= {AF_INET};    sa.sin_addr.s_addr=inet_addr("0.0.0.0");    sa.sin_port=htons(8444);    int nRet=bind(listfd,(sockaddr*)&sa,sizeof(sa));    if(nRet==SOCKET_ERROR)    {        cout << "绑定端口失败,error:" << GetLastError() <<endl;    }    listen(listfd,5);    fd_set sockfd;    FD_ZERO(&sockfd);    FD_SET(listfd,&sockfd);    char sBuf[256];    while(true)    {        memcpy(&readfd,&sockfd,sizeof(fd_set));        nRet=select(0,&readfd,NULL,NULL,&vl);        if(nRet==0)            puts("超时!");        if(nRet==-1)            cout << "错误:" << GetLastError() << endl;        if(nRet > 0)        {            cout << "it is coming" << endl;            for(int i=0; i < readfd.fd_count; i++)            {                if(FD_ISSET(readfd.fd_array[i],&readfd) )                {                    if(readfd.fd_array[i] == listfd)                    {                        SOCKET winsock=accept(listfd,NULL,NULL);                        FD_SET(winsock,&sockfd);                        FD_SET(winsock,&readfd);                    }                    else                    {                        memset(sBuf,0x00,sizeof(sBuf));                        int n=recv(readfd.fd_array[i],sBuf,sizeof(sBuf),0);                        if(n < 0 )                        {                            if(GetLastError() == 10054) //10054:远程主机强迫关闭了一个现有的连接。                            {                                cout << "quit!" << endl;                                closesocket(readfd.fd_array[i]);                               FD_CLR(readfd.fd_array[i],&sockfd);                            }                            else                            {                                cout << "recv error" << GetLastError() << endl; //10035:无法立即完成一个非阻止性套接字操作。                            }                            //阻塞模式下,只要n<0,就执行FD_CLR                            //FD_CLR(readfd.fd_array[i],&sockfd);                        }                        else                        {                            cout << sBuf << endl;                            //int nnn=EWOULDBLOCK;                        }                    }                }            }        }        //Sleep(200);    }    WSACleanup(); //不要忘了    return 0;} 

先看这张图,区分下linux与windows


linux下的实现(客户端):

#include <iostream>#include <cstdlib>#include<cstdio>#include <cstring>#include<sys/socket.h>  #include<arpa/inet.h>  #include <unistd.h>#include "fcntl.h"#include <error.h>#define INVALID_SOCKET -1#define SOCKET_ERROR -1using namespace std;int main(){   // WSADATA wd;    //WSAStartup(0x0202,&wd);    int listfd=socket(AF_INET,SOCK_STREAM,0);    if(listfd==INVALID_SOCKET)    {        cout << "创建套接字失败,err:"  << endl;    }    sockaddr_in sa={AF_INET};    sa.sin_addr.s_addr=inet_addr("127.0.0.1");    sa.sin_port=htons(8444);    int n = connect(listfd,(sockaddr*)&sa,sizeof(sa));    if(n != 0)    {        cout << "connect failed!" << endl;        return 0;    }cout << "sock=" << listfd << endl;    char sBuf[256] ={0};    while(1)    {            memset(sBuf,0x00,sizeof(sBuf));            cout << "input any ..." << endl;            cin >> sBuf ;            n = send(listfd,sBuf,strlen(sBuf),0);            if(n > 0)            {                cout << "send success!" << endl;            }    }    //EWOULDBLOCK    return 0;}

linux下的服务端:

#include <iostream>#include <cstdlib>#include<cstdio>#include <cstring>#include<sys/socket.h>  #include<arpa/inet.h>  #include <unistd.h>#include "fcntl.h"#include <error.h>#define INVALID_SOCKET -1#define SOCKET_ERROR -1using namespace std;int g_nCount=0;int g_max=0;int main(){    //WSADATA wd;    //WSAStartup(0x0202,&wd);    fd_set readfd;    FD_ZERO(&readfd);    timeval vl;    int listfd=socket(AF_INET,SOCK_STREAM,0);    if(listfd==INVALID_SOCKET)    {        cout << "创建套接字失败,err:"  << endl;    }    //linux下的阻塞模型调整int flag=fcntl(listfd,F_GETFL,0);fcntl(listfd,F_SETFL,flag | O_NONBLOCK);//windows//unsigned long flag=1;//ioctlsocket(listfd,FIONBIO,&flag);    sockaddr_in sa= {AF_INET};    sa.sin_addr.s_addr=inet_addr("0.0.0.0");    sa.sin_port=htons(8444);    int nRet=bind(listfd,(sockaddr*)&sa,sizeof(sa));    if(nRet==SOCKET_ERROR)    {        cout << "绑定端口失败,error:" <<endl;    }    listen(listfd,5);    int sockfd[1000]={0};    sockfd[0]=listfd;    char sBuf[256];    g_nCount=1;    g_max=listfd+1;    while(true)    {    FD_ZERO(&readfd); //一定不能忘    memset(&vl,0x00,sizeof(vl));    vl.tv_sec=4;    vl.tv_usec=0;        for(int i=0;i < g_nCount;i++)        {        FD_SET(sockfd[i],&readfd);        cout << sockfd[i] << endl;             }        nRet=select(g_max,&readfd,NULL,NULL,&vl);        if(nRet==0)            puts("超时!");        if(nRet==-1)            cout << "错误!"  << endl;        if(nRet > 0)        {            cout << "it is coming" << endl;            //for(i= sock+1; i <max_fd+1; ++i) ,也是一个思路            for(int i=0; i < g_nCount; i++)            {                if(FD_ISSET(sockfd[i],&readfd) )                {                g_max=(sockfd[i] >= g_max ? (sockfd[i]+1) : g_max);                cout << "coming,and sockfd[i]=" << sockfd[i] << endl;                    if(sockfd[i] == listfd)                    {                        int unixsock=accept(listfd,NULL,NULL);                        sockfd[g_nCount++]=unixsock;                        FD_SET(unixsock,&readfd);                    }                    else                    {                        memset(sBuf,0x00,sizeof(sBuf));                        cout << "test,i=" << i << ",sock="<< sockfd[i] << ",g_nCount=" << g_nCount << endl;                        int n=recv(sockfd[i],sBuf,sizeof(sBuf),0);                        if(n < 0 )                        {                                                              cout << "recv error" << endl;                               //阻塞模式下,只要n<0,就执行FD_CLR                            //FD_CLR(readfd.fd_array[i],&sockfd);                        }                        else if(n==0)                        {                        cout << "quit!" << endl;                            close(sockfd[i]);                            int delete_fd=sockfd[i];                            //memcpy(sockfd,sockfd+i+1,g_nCount-i);   效率比较低                                if(i < g_nCount-1)  //如果i=g_nCount,这个数就抹不掉了                                {                                sockfd[i--]=sockfd[--g_nCount];   //很不错的思路                                }                                else                                {                                sockfd[i]=0;                                g_nCount--;                                }                                //max的值可能已经发生了变化,这里暂不考虑修改max,可以再while后更新max                                //影响不大,无非select多遍历了几个数                               }                        else                        {                            cout << sBuf << endl;                            //int nnn=EWOULDBLOCK;                        }                    }                }            }        }        //Sleep(200);        //sleep(1);    }    return 0;}//linux下select后,描述符集合,timeval,都会清零//windows下,只有描述集清零.//linux下,第一个参数不能为0.是最大值+1,+n(n > 1)就行//windows下,本参数忽略,仅起到兼容作用,设为0即可



原创粉丝点击