登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

樱之花

叶散的时候,你明白欢聚;花谢的时候,你明白青春.

 
 
 

日志

 
 
关于我

分类中“我的实验室”是我在日常工作中的一些知识总结,有些写的比较匆忙,可能大家在阅读时会产生困扰,后期有时间我会重新整理编辑,谢谢大家的到访,您们的支持是我前进的动力!

I/O多路复用器两种实现方法  

2012-11-06 17:34:59|  分类: C++学习之路 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

I/O多路复用器两种实现方法:
对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞。
所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止。
而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待。
缺省情况下, 文件描述符处于阻塞状态。例如,在实现聊天室时, server 需要轮流查询与各client 建立的 socket, 一旦可读就将该 socket 中的字符读出来并向所有其他client 发送。并且, server 还要随时查看是否有新的 client 试图建立连接,这样, 如果 server 在任何一个地方阻塞了, 其他 client 发送的内容就会受到影响,得不到服务器的及时响应。新 client 试图建立连接也会受到影响。
非阻塞通信方法
使用fcntl()函数,伪代码:
server端:

while ( 1)

      if 有新连接 then 建立并记录该新连接;
      for ( 所有的有效连接)
            begin
                  if 该连接中有字符可读 then
                        begin
                              读入字符串;
                        for ( 所有其他的有效连接)
                              begin
                                    将该字符串发送给该连接;
                              end;
                        end;
            end;
      end.

client端:
建立连接之后, 只需要处理两个文件描述符, 一个是建立了连接的 socket 描述符, 另一个是标准输入. 和 server 一样, 如果使用阻塞方式的话, 很容易因为其中一个暂时没有输入而影响另外一个的读入。 因此将它们都变成非阻塞的, 然后client 进行如下动作:

while ( 不想退出)
      begin
      if ( 与 server 的连接有字符可读)
            begin
            从该连接读入, 并输出到标准输出上去.
            End;
      if ( 标准输入可读)
            Begin
            从标准输入读入, 并输出到与 server 的连接中去.
            End;
      End.

分析上面的程序可以知道, 不管是 server 还是 client, 它们都不停的轮流查询各个文件描述符, 一旦可读就读入并进行处理. 这样的程序, 不停的在执行, 只要有CPU 资源, 就不会放过。因此对系统资源的消耗非常大。


select 方法
这种I/O模型主要涉及以下几个函数及宏:

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

FD_ZERO(fd_set *set) – 清除一个文件描述符集合
FD_SET(int fd, fd_set *set) – 添加fd到集合 
FD_CLR(int fd, fd_set *set) – 从集合中移去fd 
FD_ISSET(int fd, fd_set *set) – 测试fd是否在集合中 





虽然我们不希望在某一个用户没有反应时阻塞其他的用户,但我们却应该在没有任何用户有反应的情况之下停止程序的运行,让出抢占的系统资源,进入阻塞状态。有没有这种方法呢?现在的UNIX系统中都提供了select方法,具体实现方式如下:
select 方法中, 所有文件描述符都是阻塞的. 使用 select 判断一组文件描述符中是否有一个可读(写), 如果没有就阻塞, 直到有一个的时候就被唤醒. 我们先看比较简单的 client 的实现:

由于 client 只需要处理两个文件描述符, 因此, 需要判断是否有可读写的文件描述符只需要加入两项:
FD_ZERO( sockset);
// 将 sockset 清空
FD_SET( sockfd, sockset);
// 把 sockfd 加入到 sockset 集合中
FD_SET( 0, sockset);
// 把 0 (标准输入) 加入到 sockset 集合中
FD_CLR(sockfd, sockset)
// 从清除 sockset 集合中清除sockfd  
client端:

while ( 不想退出)
{
      select( sockfd+1, &sockset, NULL, NULL, NULL);
      // 此时该函数将阻塞直到标准输入或者 sockfd 中有一个可读为止
      // 第一个参数是 0 和 sockfd 中的最大值加一
      // 第二个参数是 读集, 也就是 sockset
      // 第三, 四个参数是写集和异常集, 在本程序中都为空
      // 第五个参数是超时时间, 即在指定时间内仍没有可读, 则出错
      // 并返回. 当这个参数为NULL 时, 超时时间被设置为无限长.
      // 当 select 因为可读返回时, sockset 中包含的只是可读的
      // 那些文件描述符.

      if ( FD_ISSET( sockfd, &sockset))
      {
            // FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
            从 sockfd 中读入, 输出到标准输出上去.
      }
      if ( FD_ISSET( 0, &sockset))
      {
            // FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
            从标准输入读入, 输出到 sockfd 中去.
      }
      重新设置 sockset. (即将 sockset 清空, 并将 sockfd 和 0 加入)
}


server服务端:

FD_ZERO( sockset);
FD_SET( sockfd, sockset);
for ( 所有有效连接)
FD_SET( userfd[i], sockset);

maxfd = 最大的文件描述符号 + 1;

while ( 1)
{
      select( maxfd, &sockset, NULL, NULL, NULL);
      if ( FD_ISSET( sockfd, &sockset))
      {
            // 有新连接
            建立新连接, 并将该连接描述符加入到 sockset 中去了.
      }
      for ( 所有有效连接)
      {
            if ( FD_ISSET ( userfd[i], &sockset))
            {
                  // 该连接中有字符可读
                  从该连接中读入字符, 并发送到其他有效连接中去.
            }
      }
      重新设置 sockset;
}

由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态,最小程度的占用CPU 资源。
缺点:
对套接字数组扫描的效率问题,由于存在对套接字句柄的扫描处理,这个肯定会影响效率。看看是否有办法解决。
对客户端实时响应问题,程序在处理待决的套接字的时候,是逐个处理的,如果响应某个Client的时间长到一定程度的话,肯定会影响对其它客户端的响应。解决方法是当这个套接字处于可读的待决状态的话,产生一个子线程去处理(接收数据和处理数据)。这样主线程继续自己的工作,其它Client可以得及时的响应;当然当有大量的Client请求时,对线程的控制会成为一个新问题。
原文参考:
http://fish.cndev.org/2005/02/24/7287/
  评论这张
 
阅读(1426)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018