1.什么是kqueue和IO复用

kueue是在UNIX上比较高效的IO复用技术。

所谓的IO复用,就是同时等待多个文件描述符就绪,以系统调用的形式提供。如果所有文件描述符都没有就绪的话,该系统调用阻塞,否则调用返回,允许用户进行后续的操作。

常见的IO复用技术有select, poll, epoll以及kqueue等等。其中epoll为Linux独占,而kqueue则在许多UNIX系统上存在,包括OS X(好吧,现在叫macOS了。。)

2. 使用概览

kueue在设计上是非常简洁的,在易用性上可能比select和epoll更好一些。

使用kqueue的大致代码如下:(后面会给出一个完整的示例)


  1. const static int FD_NUM = 2 // 要监视多少个文件描述符
  2. int kq = kqueue(); // kqueue对象
  3. // kqueue的事件结构体,不需要直接操作
  4. struct kevent changes[FD_NUM]; // 要监视的事件列表
  5. struct kevent events[FD_NUM]; // kevent返回的事件列表(参考后面的kevent函数)
  6. int stdin_fd = STDIN_FILENO;
  7. int stdout_fd = STDOUT_FILENO;
  8. // 在changes列表中注册标准输入流的读事件 以及 标准输出流的写事件
  9. // 最后一个参数可以是任意的附加数据(void * 类型),在这里给事件附上了当前的文件描述符,后面会用到
  10. EV_SET(&changes[0], stdin_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd);
  11. EV_SET(&changes[1], stdout_fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd);
  12. // 进行kevent函数调用,如果changes列表里有任何就绪的fd,则把该事件对应的结构体放进events列表里面
  13. // 返回值是这次调用得到了几个就绪的事件 (nev = number of events)
  14. int nev = kevent(kq, changes, FD_NUM, events, FD_NUM, NULL); // 已经就绪的文件描述符数量
  15. for(int i=0; i<nev; i++){
  16. struct kevent event = events[i]; // 一个个取出已经就绪的事件
  17. int ready_fd = *((int *)event.udata); // 从附加数据里面取回文件描述符的值
  18. if( ready_fd == stdin_fd ){
  19. // 读取ready_fd
  20. }else if( ready_fd == stdin_fd ){
  21. // 写入ready_fd
  22. }
  23. }

3. 相关结构体与函数解析

可以看出来,kqueue体系只有三样东西:struct kevent结构体,EV_SET宏以及kevent函数。

struct kevent 结构体内容如下:

  1. struct kevent {
  2. uintptr_t ident; /* identifier for this event,比如该事件关联的文件描述符 */
  3. int16_t filter; /* filter for event,可以指定监听类型,如EVFILT_READ,EVFILT_WRITE,EVFILT_TIMER等 */
  4. uint16_t flags; /* general flags ,可以指定事件操作类型,比如EV_ADD,EV_ENABLE, EV_DELETE等 */
  5. uint32_t fflags; /* filter-specific flags */
  6. intptr_t data; /* filter-specific data */
  7. void *udata; /* opaque user data identifier,可以携带的任意数据 */
  8. };

EV_SET 是用于初始化kevent结构的便利宏,其签名为:

  1. EV_SET(&kev, ident, filter, flags, fflags, data, udata);

可以发现和kevent结构体完全对应,除了第一个,它就是你要初始化的那个kevent结构。

kevent 是真正进行IO复用的函数,其签名为:

  1. int kevent(int kq,
  2. const struct kevent *changelist, // 监视列表
  3. int nchanges, // 长度
  4. struct kevent *eventlist, // kevent函数用于返回已经就绪的事件列表
  5. int nevents, // 长度
  6. const struct timespec *timeout); // 超时限制

4. 完整示例

下面给出一个完整的示例,这个程序将从标准输入中读取数据,写到标准输出中。其中输入输出全部使用kqueue来进行IO复用。可以使用重定向把文件写入标准输入来进行测试。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #include <sys/event.h>
  6. #include <errno.h>
  7. #include <string.h>
  8. // 为文件描述符打开对应状态位的工具函数
  9. void turn_on_flags(int fd, int flags){
  10. int current_flags;
  11. // 获取给定文件描述符现有的flag
  12. // 其中fcntl的第二个参数F_GETFL表示要获取fd的状态
  13. if( (current_flags = fcntl(fd, F_GETFL)) < 0 ) exit(1);
  14. // 施加新的状态位
  15. current_flags |= flags;
  16. if( fcntl(fd, F_SETFL, current_flags) < 0 ) exit(1);
  17. }
  18. // 错误退出的工具函数
  19. int quit(const char *msg){
  20. perror(msg);
  21. exit(1);
  22. }
  23. const static int FD_NUM = 2; // 两个文件描述符,分别为标准输入与输出
  24. const static int BUFFER_SIZE = 1024; // 缓冲区大小
  25. // 完全以IO复用的方式读入标准输入流数据,输出到标准输出流中
  26. int main(){
  27. struct kevent changes[FD_NUM];
  28. struct kevent events[FD_NUM];
  29. // 创建一个kqueue
  30. int kq;
  31. if( (kq = kqueue()) == -1 ) quit("kqueue()");
  32. // 准备从标准输入流中读数据
  33. int stdin_fd = STDIN_FILENO;
  34. int stdout_fd = STDOUT_FILENO;
  35. // 设置为非阻塞
  36. turn_on_flags(stdin_fd, O_NONBLOCK);
  37. turn_on_flags(stdout_fd, O_NONBLOCK);
  38. // 注册监听事件
  39. int k = 0;
  40. EV_SET(&changes[k++], stdin_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, &stdin_fd);
  41. EV_SET(&changes[k++], stdout_fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &stdout_fd);
  42. int nev, nread, nwrote = 0; // 发生事件的数量, 已读字节数, 已写字节数
  43. char buffer[BUFFER_SIZE];
  44. while(1){
  45. nev = kevent(kq, changes, FD_NUM, events, FD_NUM, NULL); // 已经就绪的文件描述符数量
  46. if( nev <= 0 ) quit("kevent()");
  47. int i;
  48. for(i=0; i<nev; i++){
  49. struct kevent event = events[i];
  50. if( event.flags & EV_ERROR ) quit("Event error");
  51. int ev_fd = *((int *)event.udata);
  52. // 输入流就绪 且 缓冲区还有空间能继续读
  53. if( ev_fd == stdin_fd && nread < BUFFER_SIZE ){
  54. int new_nread;
  55. if( (new_nread = read(ev_fd, buffer + nread, sizeof(buffer) - nread)) <= 0 )
  56. quit("read()"); // 由于可读事件已经发生,因此如果读出0个字节也是不正常的
  57. nread += new_nread; // 递增已读数据字节数
  58. }
  59. // 输出流就绪 且 缓冲区有内容可以写出
  60. if( ev_fd == stdout_fd && nread > 0 ){
  61. if( (nwrote = write(stdout_fd, buffer, nread)) <=0 )
  62. quit("write()");
  63. memmove(buffer, buffer+nwrote, nwrote); // 为了使实现的代码更简洁,这里把还没有写出去的数据往前移动
  64. nread -= nwrote; // 减去已经写出去的字节数
  65. }
  66. }
  67. }
  68. return 0;
  69. }

程序中对stdin和stdout设置非阻塞的原因是我们希望有多少就绪的数据就读多少,或者能写入多少进缓冲区就写入多少。否则在阻塞模式下,如果read没有填满buffer(文件没读完时),或者还有buffer数据没写入时,系统调用(read和write)会阻塞,这会对性能造成很大影响。因此这里设置为非阻塞模式。

kqueue用法简介的更多相关文章

  1. IOS NSInvocation用法简介

    IOS NSInvocation用法简介 2012-10-25 19:59 来源:博客园 作者:csj007523 字号:T|T [摘要]在 iOS中可以直接调用某个对象的消息方式有两种,其中一种就是 ...

  2. JodaTime用法简介

    JodaTime用法简介 Java的Date和Calendar用起来简直就是灾难,跟C#的DateTime差距太明显了,幸好有JodaTime 本文简单罗列JodaTime的用法 package co ...

  3. Apache自带压力测试工具ab用法简介

    ab命令原理 ab命令会创建很多的并发访问线程,模拟多个访问者同时对某一URL进行访问.它的测试目标是基于URL的,因此,既可以用来测试Apache的负载压力,也可以测试nginx.lighthttp ...

  4. Postman用法简介

    转自:http://blog.csdn.net/flowerspring/article/details/52774399 Postman用法简介 转载 2016年10月10日 09:04:10 10 ...

  5. MSSQL Sql加密函数 hashbytes 用法简介

    转自:http://www.maomao365.com/?p=4732 一.mssql sql hashbytes 函数简介 hashbytes函数功能为:返回一个字符,通过 MD2.MD4.MD5. ...

  6. java assert的用法简介【转】

    assert的基本用法 assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制,如C,C++和Eiffel等,但是支持的形式不尽相同,有的是通过语言本身.有的是通过库 ...

  7. glVertexAttribPointer 用法简介

    在内存中采用交叉模式存储,向gpu传入顶点数据的方法  GPU: #version 100 attribute highp vec2 aPosition; attribute highp vec2 a ...

  8. C#中IPAddress类/Dns类/IPHostEntry类/IPEndPoint用法简介

    C#中IPAddress类/Dns类/IPHostEntry类/IPEndPoint用法简介 IP是一种普遍应用于因特网.允许不同主机能够相互找到对方的寻址协议.IP地址由4个十进制的数字号码所组成, ...

  9. Postman用法简介----https://blog.csdn.net/flowerspring/article/details/52774399

    https://blog.csdn.net/flowerspring/article/details/52774399 Postman用法简介

随机推荐

  1. Ubuntu NFSserver 简易安装及使用

    服务器端(ip:192.168.1.100): sudo apt-get install nfs-kernel-server #安装nfs 客户端(ip:192.168.1.110): sudo ap ...

  2. TypeScript之基本数据类型

    前言 最近项目很急,所以没有什么时间回答关于Xamarin.Android方面的问题,也有一段时间没有更新.主要是手头很缺人,如果有谁有兴趣加入我们的话,可以私聊我,这样我就能继续造福社区了,同时还有 ...

  3. Cloneable接口和Object的clone()方法

    为什么要克隆 为什么要使用克隆,这其实反映的是一个很现实的问题,假如我们有一个对象: public class SimpleObject implements Cloneable { private ...

  4. 自制Unity小游戏TankHero-2D(2)制作敌方坦克

    自制Unity小游戏TankHero-2D(2)制作敌方坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  5. AWS系列之一 亚马逊云服务概述

    云计算经过这几年的发展,已经不再是是一个高大上的名词,而是已经应用到寻常百姓家的技术.每天如果你和互联网打交道,那么或多或少都会和云扯上关系.gmail.github.各种网盘.GAE.heroku等 ...

  6. Winform文件下载之断点续传

    在本系列的前两篇文章中,分别向大家介绍了用于完成下载任务的 WebClinet 和 WinINet 的基本用法和一些实用技巧. 今天来为大家讲述下载过程中最常遇到的断点续传问题. 首先明确一点,本文所 ...

  7. Lucene系列-facet

    1.facet的直观认识 facet:面.切面.方面.个人理解就是维度,在满足query的前提下,观察结果在各维度上的分布(一个维度下各子类的数目). 如jd上搜“手机”,得到4009个商品.其中品牌 ...

  8. Git Day01,仓库,commit,版本切换

    1st,创建版本库:  2nd,添加文件:  3rd,修改文件,并提交: 4th,版本切换:git log查看版本:版本回退: 又回到原始版本了: 回到“未来”: 今天就到这里,明天继续.Git确实挺 ...

  9. svn import-纳入版本控制

    转svn import-纳入版本控制 import: 将未纳入版本控制的文件或目录树提交到版本库.用法: import [PATH] URL 递归地提交 PATH 的副本至 URL.  如果省略 PA ...

  10. Ubuntu & MacOS安装Mysql & connector

    Ubuntu & MacOS安装Mysql & connector 1. 安装MySql sudo apt-get install mysql-server apt-get insta ...