I/O多路复用

来源:互联网 发布:cube escape 知乎 编辑:程序博客网 时间:2024/04/24 14:01
  • 非阻塞I/O,recvfrom系统调用后,内核会立马返回,进程可以干点其他事,然后再发起系统调用重复上面的过程,拷贝数据的整个过程进程仍然是阻塞的
  • 阻塞I/O,进程发起系统调用后,在没有数据准备好时,进程会被阻塞,知道数据从内核复制到用户控件完成
  • 多路复用I/O,可以同时等待多个socket,当有一个socket数据准备好时就返回,然后由进程发起系统调用,将数据从内核拷贝到用户空间,此过程进程是阻塞的

  • 异步I/O,用户进程进行aio_read系统调用之后,无论内核数据是否准备好都直接返回,然后用户态进程可以去做别的事情,等socket数据准备好后,内核直接赋值数据给进程,然后由内核向进程发送通知

1. select模型


  • 设备文件描述符被存放在一个数组中,然后select调用会遍历这个数组,遍历结束后如果没有一个可用的设备文件描述符,则会让用户进程睡眠,知道等待资源可用
#include <sys/select.h>#include <sys/time.h>
* int select( //返回fd_set可用的套接字个数        IN int nfds,    //0 无意义,最大文件描述符值+1        IN OUT fd_set* readfds,  //检查可读性,可读描述符集合        IN OUT fd_set* writefds, //检查可写性,可写描述符集合        IN OUT fd_set* exceptfds, //例外数据,异常描述符        IN const struct timeval* timeout); //函数的返回时间struct timeval {    long tv_sec;  //秒    long tv_usec; //毫秒};
  • fd_set是一个SOCKET 队列,可以用一下宏操作
FD_CLR(s, *set) //从队列set删除句柄FD_ISSET(s, *set) //检查句柄s是否存在与队列set中FD_SET(s, *set) //把句柄s添加到队列set中,翔要检查一个套接字是否有数据需要接受,把套接字句柄加入可读性检查队列中FD_ZERO(*set) //把set队列初始化为空队列,初始化select函数的第二三四个参数
  • select调用是内核级别的
  • 调用select函数,如果该套接字没有数据可以接受,select函数会把该套接字从可读性检查队列中删除
  • 利用select函数,可以判断套接字上是否存在数据或者能否向一个套接字写入数据。
  • 存在问题: 都预设了64个客户端连接,虽然实现时不受这个限制(使用分段轮询),但是效率会打折扣。
  • select工作一次需要遍历两次列表,在轮询的时候需要遍历一次,再新的一轮开始时,将列表加入队列有需要遍历一次

1. 普通seclet模型

  • 缺点: 需要一个死循环不停地遍历所有的客户端套接字集合,当连接很多时会影响效率。
  • 解决了每一个客户端都去开辟新的线程与其通信的问题。用到函数为select

2. wsaasyncselect模型

  • 有客户端数据到来时,系统给发送消息给程序,程序只要定义好消息处理方法就行,用到的函数是WSAAsyncSelect
  • 解决了普通select模型的问题,
  • 缺点: 智能用到windows中,需要一个接受系统消息的窗口句柄

3. WsaEventSelecdt模型

  • 不用轮询,在客户端有数据到来时,系统发送通知给我们的程序,但是不是发送消息,而是通过实践的方式通知我们的程序
  • 用到函数WSAEventSelect

4. 流程

SOCKET g_CliSocketArr[FD_SETSIZE];int g_iTotalConn = 0;//以上为全局变量fd_set fdread;int ret;struct timeval tv = {1, 0};while (true) {    FD_ZERO(&fdread); //清空队列    for (int i=0; i<g_iTotalConn; i++)         FD_SET(g_CliSocketArr[i], &fdread); //将检查的套接口加入队列,遍历第一次    ret = select(0, &fdread, NULL, NULL, &tv); //遍历第二次,一次IO两次遍历,低效    // 调用select函数,如果该套接字没有数据可以接受,select函数会把该套接字从可读性检查队列中删除    // 需要在连接数为0的时候做特殊处理,因为如果读集中没有任何套接字,select会立即返回,会导致想成是一个毫无停顿的死循环    if (ret == 0)         continue;    for (int i=0; i<g_iTotalConn; i++) {        if (FD_ISSET(g_CliSocketArr[i], &fdread)) { //是否依然在队列,是的话,说明可以马上进行IO操作而不会被阻塞            //从套接字读取数据            //读取失败关闭套接字            //成功读取数据,继续其他操作        }    }}

这里写图片描述

0 0