select函数总结

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 -1
using 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 -1
using 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即可



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值