select网络模型知识总结

select模型支持IO多路复用,select函数如下

int select (
 IN int nfds,                           //windows下无意义,linux有意义
 IN OUT fd_set* readfds,      //检查可读性
 IN OUT fd_set* writefds,     //检查可写性
 IN OUT fd_set* exceptfds,  //例外数据
 IN const struct timeval* timeout);    //函数的返回时间

逐个解释每个参数意义:

nfds:一个整型变量,表示比最大文件描述符+1

readfds: 这个集合监测读事件的描述符,将要监听

读事件的文件描述符放入readfds中,通过调用select,

readfds中将没有就绪的读事件文件描述符清除,留下

就绪的读事件描述符,可以通过read或者recv来处理

writefds:这个集合监测写事件的描述符,将要监听的

写事件的文件描述符放入writefds中,通过调用select,

writefds中没有就绪的写事件文件描述符被清除,留下

就绪的写事件描述符,可以通过write或者send来处理。

execptfds:这个集合在调用select后会存有错误的文件

描述符。根据Linux网络网络编程第二版中介绍,可以

监视带外数据OOB,带外数据使用MSG_OOB标志发送

到套接字上,当select()函数返回的时候,readfds将清除

其中的其他文件描述符,留下OOB数据

函数返回值:

当返回0时表示超时,-1表示有错误,大于0表示没有错误。

当监视文件集中有文件描述符符合要求,即读文件描述符集

合中有文件可读,写文件描述符集合中有文件可写,或者

错误文件描述符集合中有错误的描述符,都会返回大于0的数。

 

timeval结构体解释

struct  timeval {
        long    tv_sec;        //
        long    tv_usec;     //毫秒
};

 

timeval指针为NULL,表示一直等待,直到有符合条件的描述符

触发select返回

如果timeval中个参数均为0,表示立即返回,否则在select没有

符合条件的描述符,等待对应的时间和,然后返回。

 

 另外需要了解一些select的操作宏函数

fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:
FD_CLR( s, fd_set *set) 从队列set删除句柄s;
FD_ISSET( s,fd_set *set) 检查句柄s是否存在与队列set中;
FD_SET( s, fd_set *set )把句柄s添加到队列set中;
FD_ZERO( fd_set *set ) 把set队列初始化成空队列.

看一个select的使用示例

//前面是服务器socket的创建,绑定和监听

int  listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);     
 // Bind     
  
 local.sin_family = AF_INET;     
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);     
 local.sin_port = htons(PORT);     
 bind(listenSocket, (sockaddr*)&local, sizeof(SOCKADDR_IN));     
  
 // Listen     
  
 listen(listenSocket, 3);
 //设置非阻塞
 fcntl(listenSocket, F_SETFL, O_NONBLOCK );
 
 //这个使用来统计所有已经连接上来的文件描述符,
 //也可以用数组表示,只是遍历的时候要根据数据结构进行更改
FD_SET socketSet;  
FD_SET writeSet;  
FD_SET readSet;
 
//清空 socketSet
FD_ZERO(&socketSet);
//将文件描述符放入 socketSet,
//用于accept  
FD_SET(listenSocket,&socketSet); 
//统计最大的socket 
int maxfd = listenSocket;
int conNum = 1;
//数组存储连接的socket
int connectArray[1024]={0};
while(true)  
{  
    //清空读写集合
    FD_ZERO(&readSet);  
    FD_ZERO(&writeSet);  
    //读写都监听
    readSet=socketSet;  
    writeSet=socketSet;  
  
    //同时检查套接字的可读可写性。 
    //为等待时间传入NULL,则永久等待。传入0立即返回。不要勿用。
    int ret=select(maxfd,&readSet,&writeSet,NULL,NULL);  
    if(ret==-1)  
    {  
        return false;  
    }  
    sockaddr_in addr;  
    int len=sizeof(addr);  
    //是否存在客户端的连接请求。  
    //在readset中会返回已经调用过listen的套接字
    if(FD_ISSET(listenSocket,&readSet))
    {  
        acceptSocket=accept(listenSocket,(sockaddr*)&addr,&len);  
        if(acceptSocket==INVALID_SOCKET)  
        {  
            return false;  
        }  
        else  
        {  
            //大于我们最大的监听数量了
            if(conNum > 1024)
            {
                return false;
            }
            //更新数组
            connectArray[conNum] = acceptSocket;
            //设置非阻塞
            fcntl(connectArray[conNum], F_SETFL, O_NONBLOCK );
            //加到socketset里,以后赋值给读写集合
            FD_SET(connectArray[conNum],&socketSet);
            if(acceptSocket > maxfd)
            {
                maxfd = acceptSocket;
            }
            conNum++;
        }  
    }  
  
    for(int i=0;i<conNum;i++)  
    {  
        //判断是否有读事件
       if(FD_ISSET(connectArray[i],&readSet))  
        {  
            //调用recv,接收数据。  
            //判断recv结果,为0则客户端断开,
            //那么调用FD_CLR并关闭对应的socket
        }  
        //判断是否有写事件
        if(FD_ISSET(connectArray[i],&writeSet)  
        {  
            //调用send,发送数据。
            //判断send结果,为0客户端断开,
            //那么调用FD_CLR并关闭对应的socket    
        }  
    }  
}  
 

 

上面的例子结合了网上提供的一些demo,其实writeSet不一定要放入socket,当某个socket需要send内容时

再调用FD_SET(socket,&writeSet),写成功后再调用FD_CLR(socket,&writeSet);避免造成busyloop,

因为当缓冲区非空时,写事件是一直就绪的。

我的微信公众号,定期推送一些技术总结,一起努力吧。

 

posted @ 2016-05-18 17:43  恋恋风辰  阅读(2492)  评论(5编辑  收藏  举报