select机制

select机制_第1张图片

 

背景知识分析:

 1. fd_set 结构体

  fd_set是文件句柄的集合。   
    
   FD_ZERO    清空这个集合;   
   FD_SET 往这个集合里面加入一个文件句柄;   
   FD_ISSET      查看某一个文件句柄是否被设置了;

   'fd_set') 是一组文件描述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:  

   fd_set set;      

   FD_ZERO(&set);        /* 将set清零 */    

   FD_SET(fd, &set);     /* 将fd加入set */   

   FD_CLR(fd, &set);     /* 将fd从set中清除 */    

   FD_ISSET(fd, &set);   /* 判断fd是否处于可用状态 是为true */  

2使用介绍:

[cpp]  view plain copy
  1. <font face="Verdana">fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */  
  2. struct timeval tv; /* 申明一个时间变量来保存时间 */  
  3. int ret; /* 保存返回值 */  
  4. FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */  
  5. FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */  
  6. tv.tv_sec = 1;  
  7. tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */  
  8. ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */  
  9. if(ret < 0) perror("select");/* 这说明select函数出错 */  
  10. else if(ret == 0) printf("超时/n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */  
  11. else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */  
  12.     printf("ret=%d/n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */  
  13.     /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */  
  14.     if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */  
  15.         /* 读取socket句柄里的数据 */  
  16.         recv(...);  
  17.     }  
  18. }  
  19.  </font>  

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:

[cpp]  view plain copy
  1. int sa, sb, sc;  
  2. sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */  
  3. connect(sa,...);  
  4. sb = socket(...);  
  5. connect(sb,...);  
  6. sc = socket(...);  
  7. connect(sc,...);  
  8. FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */  
  9. FD_SET(sb, &rdfds);  
  10. FD_SET(sc, &rdfds);  
  11.    

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

[cpp]  view plain copy
  1. int maxfd = 0;  
  2. if(sa > maxfd) maxfd = sa;  
  3. if(sb > maxfd) maxfd = sb;  
  4. if(sc > maxfd) maxfd = sc;  

然后调用select函数:

[cpp]  view plain copy
  1. ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */  

 

3 关于 Select 的 FD_SETSIZE

 

nt select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct timeval *timeout);
Select 用到了fd_set结构,从man page里可以知道fd_set能容纳的句柄和FD_SETSIZE相关。实际上fd_set在*nix下是一个bit标志数组,每个bit表示对应下标 的fd是不是在fd_set中。fd_set只能容纳编号小于 FD_SETSIZE的那些句柄。
FD_SETSIZE默认是1024,如果向 fd_set里放入过大的句柄,数组越界以后程序就会垮掉。系统默认限制了一个进程最大的句柄号不超过1024,但是可以通过ulimit -n命令/setrlimit函数来扩大这一限制。如果不幸一个程序在FD_SETSIZE=1024的环境下编译,运行时又遇到ulimit n > 1024的,那就只有祈求上帝保佑不会垮掉了。

通过上述说明,可以看到如果连接很多的话,建议还是使用epoll来解决这个问题。

4 select 机制的优势

为什么会出现select模型?

先看一下下面的这句代码:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返 回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。
再看代码:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过 你跟踪 一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。 看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。

select模型的出现就是为了解决上述问题。

select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度 。

看核心代码:(这里只给出服务端的)

while ( 1 )
{
// 初始化fdset
FD_ZERO( &fdsRead );

// 将server套接字添加到可读集合中
FD_SET( sockServer, &fdsRead );

// 调用select
select( 0, &fdsRead, NULL, NULL, &tv );

// 判断server套接字的状态,如果套接字还在可读集合中,
// 说明有数据可以读入,则建立套接字可以成功
if ( FD_ISSET( sockServer, &fdsRead ) )
{
sockAccept = accept( sockServer, (sockaddr*)&addr, &nLen );
// 有数据可读,进行相关处理
}

 

 


 

你可能感兴趣的:(多线程,socket,server,struct,null,buffer)