I/O的知识储备

背景

前一段时间一直在关注一些nio的相关技术,也和公司的架构师交流了一下,学到了一些相关原理,比如nio的优势和劣势。以及一些排查nio bug问题的方式,受益量多。为自己做一下技术储备,以后可以多玩玩nio的相关技术

知识点

unix网络编程第6章: 几种unix下的I/O模型。

 

  • 阻塞I/O模型    (java io)
  • 非阻塞I/O模型
  • I/O复用          (java 5的nio)
  • 信号驱动I/O
  • 异步I/O          (java7的aio)

几点说明: 

1.  阻塞和非阻塞的概念

   两者的区别侧重点在于,当前的线程是否会处于挂起,阻塞的状态。

2.  同步和异步的概念

   两者的区别侧重点在于,是当前业务的处理方式是否是一个串行的过程,异步的操作也可能是阻塞的动作。

几种模型的比较:

 

阻塞I/O模型: 

这个在java中平时使用比较多,不用多做介绍。 注意下stream &  reader的区别,自己面试别人也问的比较多。

I/O复用模型: 

介绍java nio之前,先了解一下unix中几种i/o复用的支持: select / poll 模型。 

linux早期有select / pselect : 

select 函数: 

1.#include<sys/select.h>
2.
3.int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval* restrict tvptr);

参数说明: 

1.  最大描述符大小,一般是最大值+1。

2.  中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或出于异常条件的各个描述符。就是一个位表,每次check一下当前描述符在对应位上的值是否为1

3.  第4个参数,就是对应的超时参数,精确到微妙,细节我也不关注。

返回值: 0超时,-1出错, 正数代表准备好的描述符

pselect函数:

 

1.#include <sys/select.h>
2.
3.int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);

它与select的区别在于:

  pselect使用timespec结构指定超时值。timespec结构以秒和纳秒表示时间,而非秒和微秒。

  pselect的超时值被声明为const,这保证了调用pselect不会改变timespec结构。

  pselect可使用一个可选择的信号屏蔽字。在调用pselect时,以原子操作的方式安装该信号屏蔽字,在返回时恢复以前的信号屏蔽字。 多了参数5

poll函数: 

1.#include <poll.h>
2.
3.int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);

参数说明: 

1. pollfd是一个结构体,我们需要关注的描述符

1.struct pollfd {
2.     int fd; /* file descriptor to check, or <0 to ignore */
3.     short events; /* events of interest on fd */
4.     short revents; /* events that occurred on fd */
5.};

2. nfds代表pollfd的长度

 

返回值:0超时,-1出错, 正数代表准备好的描述符

 

同样变种的有ppoll函数,具体可以见man ppoll,两者的区别和select/pselect区别一样,多了时间精度的支持+信号屏蔽字

 

和select系列的区别点,poll不再受限于select中位数组的长度限制,我们可以将关心的描述符添加到poolfd中。

 

再看epoll函数,是对select/poll的一个增强版:

 写道

它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用 率:
1. 因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合
2. 另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll的除了提供select/poll 那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

电平触发(Level Triggered): select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息
边沿触发(Edge Triggered: 只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发。

 

几个接口:

 

1.int epoll_create(int size);
2.
3.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
4.1. 第一个参数是epoll_create()的返回值.
5.2. 第二个参数表示动作,用三个宏来表示:
6.    EPOLL_CTL_ADD:注册新的fd到epfd中;
7.    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
8.    EPOLL_CTL_DEL:从epfd中删除一个fd;
9.3. 第三个参数是需要监听的fd
10.4. 第四个参数是告诉内核需要监听什么事
11.events可以是以下几个宏的集合:
12.    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
13.    EPOLLOUT:表示对应的文件描述符可以写;
14.    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
15.    EPOLLERR:表示对应的文件描述符发生错误;
16.    EPOLLHUP:表示对应的文件描述符被挂断;
17.    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
18.
19.int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
20.1. 等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,
21.2. 参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有 说法说是永久阻塞)。
22.该函数返回需要处理的事件数目,如返回0表示已超时。

再看一下jdk中对nio的使用:

1.public static SelectorProvider create() {
2.    PrivilegedAction pa = new GetPropertyAction("os.name");
3.    String osname = (String) AccessController.doPrivileged(pa);
4.        if ("SunOS".equals(osname)) {
5.            return new sun.nio.ch.DevPollSelectorProvider();
6.        }
7.
8.        // use EPollSelectorProvider for Linux kernels >= 2.6
9.        if ("Linux".equals(osname)) {
10.            pa = new GetPropertyAction("os.version");
11.            String osversion = (String) AccessController.doPrivileged(pa);
12.            String[] vers = osversion.split("\\.", 0);
13.            if (vers.length >= 2) {
14.                try {
15.                    int major = Integer.parseInt(vers[0]);
16.                    int minor = Integer.parseInt(vers[1]);
17.                    if (major > 2 || (major == 2 && minor >= 6)) {
18.                        return new sun.nio.ch.EPollSelectorProvider();
19.                    }
20.                } catch (NumberFormatException x) {
21.                    // format not recognized
22.                }
23.            }
24.        }
25.
26.        return new sun.nio.ch.PollSelectorProvider();
27.    }

比较明显: 如果当前是sunos系统,直接使用DevPoll,在linux 2.6内核下,使用Epoll模型,否则使用Poll。

 

DevPoll估计是sunos自己整的一套poll模型,公司一般使用redhat系列,内核2.6.18,64位主机。所以就介绍下Epoll的实现

 

java实现类: EPollSelectorImpl

1.// wake up使用的两描述符
2.protected int fd0;
3.protected int fd1;
4.
5.// The poll object , native的实现
6.EPollArrayWrapper pollWrapper;
7.
8.// Maps from file descriptors to keys , 文件描述符和SelectKey的关系
9.private HashMap fdToKey;

看下select()的实现:

1.PollSelectorImpl类: ==============
2.
3.protected int doSelect(long timeout) //具体执行epoll调用
4.        throws IOException
5.    {
6.        if (closed)
7.            throw new ClosedSelectorException();
8.        processDeregisterQueue();
9.        try {
10.            begin();
11.            pollWrapper.poll(timeout);
12.        } finally {
13.            end();
14.        }
15.        processDeregisterQueue();
16.        int numKeysUpdated = updateSelectedKeys();
17.        if (pollWrapper.interrupted()) {
18.            // Clear the wakeup pipe
19.            pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
20.            synchronized (interruptLock) {
21.                pollWrapper.clearInterrupted();
22.                IOUtil.drain(fd0);
23.                interruptTriggered = false;
24.            }
25.        }
26.        return numKeysUpdated;
27.    }
28.
29.继续看下EPollArrayWrappe :  ==========================
30.
31.  int poll(long timeout) throws IOException {
32.        updateRegistrations();
33.        updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
34.        for (int i=0; i<updated; i++) { // 这里判断下当前响应的描述符是否为fd0,后面再细说
35.            if (getDescriptor(i) == incomingInterruptFD) {
36.                interruptedIndex = i;
37.                interrupted = true;
38.                break;
39.            }
40.        }
41.        return updated;
42.    }
43.<span style="white-space: normal;">
44.继续看下EPollArrayWrapper 的native实现: epollWait():  ====================
45.</span><span style="white-space: normal;"><pre class="java" name="code">JNIEXPORT jint JNICALL
46.Java_sun_nio_ch_EPollArrayWrapper_epollWait(JNIEnv *env, jobject this,
47.                                            jlong address, jint numfds,
48.                                            jlong timeout, jint epfd)
49.{
50.    struct epoll_event *events = jlong_to_ptr(address);
51.    int res;
52.
53.    if (timeout <= 0) {           /* Indefinite or no wait */
54.        RESTARTABLE((*epoll_wait_func)(epfd, events, numfds, timeout), res);
55.    } else {                      /* Bounded wait; bounded restarts */
56.        res = iepoll(epfd, events, numfds, timeout);
57.    }
58.
59.    if (res < 0) {
60.        JNU_ThrowIOExceptionWithLastError(env, "epoll_wait failed");
61.    }
62.    return res;
63.}
64.
65.static int iepoll(int epfd, struct epoll_event *events, int numfds, jlong timeout)
66.{
67.    jlong start, now;
68.    int remaining = timeout;
69.    struct timeval t;
70.    int diff;
71.
72.    gettimeofday(&t, NULL);
73.    start = t.tv_sec * 1000 + t.tv_usec / 1000;  //转化为ns单位
74.
75.    for (;;) {
76.        int res = (*epoll_wait_func)(epfd, events, numfds, timeout);
77.        if (res < 0 && errno == EINTR) {  //处理异常
78.            if (remaining >= 0) {
79.                gettimeofday(&t, NULL);
80.                now = t.tv_sec * 1000 + t.tv_usec / 1000;
81.                diff = now - start;
82.                remaining -= diff;
83.                if (diff < 0 || remaining <= 0) {
84.                    return 0;
85.                }
86.                start = now;
87.            }
88.        } else {
89.            return res;
90.        }
91.    }
92.}</pre>
93.</span>

看下wakeup的实现 : 

1.EPollSelectorImpl类:
2.
3.    EPollSelectorImpl(SelectorProvider sp) {
4.    super(sp);
5.        int[] fdes = new int[2];
6.        IOUtil.initPipe(fdes, false);
7.        fd0 = fdes[0];
8.        fd1 = fdes[1];
9.        pollWrapper = new EPollArrayWrapper();
10.        pollWrapper.initInterrupt(fd0, fd1);    // 设置中断的两个描述符
11.        fdToKey = new HashMap();
12.    }
13.     public Selector wakeup() {
14.        synchronized (interruptLock) {
15.            if (!interruptTriggered) {
16.                pollWrapper.interrupt();   //调用warpper进行中断
17.                interruptTriggered = true;
18.            }
19.        }
20.    return this;
21.    }
22.
23.继续看下EPollArrayWrapper :
24.
25.    void initInterrupt(int fd0, int fd1) {
26.        outgoingInterruptFD = fd1;  //保存pipeline的描述符
27.        incomingInterruptFD = fd0;
28.        epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);   //注册到epoll上。
29.    }
30.    public void interrupt() {
31.        interrupt(outgoingInterruptFD);  //调用native方法
32.    }
33.
34.继续看下EPollArrayWrapper.c native实现:
35.
36.JNIEXPORT void JNICALL Java_sun_nio_ch_EPollArrayWrapper_interrupt(JNIEnv *env, jobject this, jint fd)
37.{
38.    int fakebuf[1];
39.    fakebuf[0] = 1;
40.    if (write(fd, fakebuf, 1) < 0) {  //发送一字节的内容,让epoll_wait()能得到及时响应
41.        JNU_ThrowIOExceptionWithLastError(env,"write to interrupt fd failed");
42.    }
43.}

 实现方式也是挺简单的,弄了两个fd,一个往另一个写1byte的内容,促使epoll_wait能得到响应。

 

异步I/O模型: 

 

暂时还未真实用过,只是大致看过其api,有兴趣可以自己baidu。 

 

 

最后

 

后续会起一篇,单独针对nio在服务端和客户端使用上的注意点,主要是吸收了一些大牛门的经验,需要做个总结,消化一下。

时间: 2024-08-30 17:12:08

I/O的知识储备的相关文章

网瘾少年到游戏策划从事游戏业也需知识储备

彭栋利用上班空隙恶补高等数学等知识. 从网瘾少年到资深游戏策划 他以亲身经历劝说沉迷游戏的少年 本报讯 (记者卢迎新摄影报道)本报报道<网瘾少年:在游戏中获得认可>(6月27日A16版)刊出后,报道中的主人公彭栋和其父亲接到了十来个读者来电.大部分来电家长希望彭栋能与自己的孩子聊一聊,劝其孩子戒除网瘾,或是希望从彭栋身上了解到有参考价值的经验. "我仍想对现在玩游戏正玩得起劲的弟弟妹妹们说:游戏是有价值的,但你们不要只顾着眼前的玩乐,要在游戏中进步.游戏中学习.游戏中成长."

成为一个web(ASP&amp;amp;gt;NET)开发人员应该具备什么样的知识储备?两年的经验应该达到什么程度?

问题描述 成为一个web(ASP>NET)开发人员应该具备什么样的知识储备?两年的经验应该达到什么程度?还有C#.net程序员应具备啥条件,才可胜任工作?工作一年以后应该达到什么程度?请各位高人给个答案 解决方案 解决方案二:我也想知道,同问解决方案三:该回复于2008-05-09 11:18:38被版主删除

IT从业人员的知识储备日益难以跟上它的发展速度

据国外媒体报道,随着科技发展越来越多样化和强大,IT从业人员的知识储备日益难以跟上它的发展速度.就专业知识和所需的技能而言,IT从业人员面临着更广泛的责任.当今,从事IT工作要求IT从业人员能够成为一个多面手,而非某一个领域内的专家,这就给他们带来了掌握多种知识的压力.那些拥有这些能力的人拥有更好的发展机遇,因此能够在小组或者公司内更好的地发挥自己的才能.而对于那些不具备这些能力或者说不思进步的从业人员来讲,他们的职业选择狭窄很多.调查结构Tech Pro Research近期展开了一个全球调查

请教完成以下项目需要的知识储备及图书推荐

问题描述 想一个基本图书管理系统需求1.一定是基于web的,2.可以多用户同时查阅的,3.除了图书数据,还能记录用户借阅数据的.4.使用微信扫码登陆,或者通过微信服务号登陆的5.使用c#和mssql的.-----------------------------请教完成以上项目需要的知识储备及图书推荐.非常感谢. 解决方案 解决方案二:aspx,微信接口开发,数据库开发

开办网站需要新手重点关注的三项知识储备

中介交易 SEO诊断 淘宝客 云主机 技术大厅 当下社会鼓励人才自主创业.有许多大学生崇拜比尔·盖茨和乔布斯,愿意像他们那样走自己创业之路.要知道,比尔·盖茨和乔布斯当初创业时,他们还只是立足于PC机的发明,那时候是单机尚未实现大规模互联的时代,他们能够迎合市场需求,让PC机进一步个人化.家庭化.视窗化.商品化,以方便每个人使用.进入21世纪,计算机界的科技进步之快,已令人难以想象了.在互联网时代,云计算.物联网已不算什么新鲜事物,安全快捷的网上支付平台的建立运行,给市场迁移到互联网提供了经济意

Windows 8开发入门(二).Windows 8开发知识储备

在本文中将整理出一些Windows 8开发前需要了解的一些基础知识并且列为相应的几个表格以供大家参考, 其中部分来源于MSDN.部分来源于本人整理,如有误之处请指正,谢谢. 一.新建常用XAML项目类型 模板和各种语言的关系 项目类型名 项目类型 作用以及描述 适用语言 空白应用程序 创建一个最简化的空白XAML应用 以供用户使用,需要自己添加 界面.公共代码等. C#/VB/C++/Javascript   拆分应用布局程序 显示主从式列表,如新闻列 表 (分为很多个新闻类别,每个新闻类别 下

Python_基础知识储备

前言 前面的博文从记录了如何Setup Python开发环境,到IPython的基本使用.现在便由浅入深的学习Python编程艺术.学习一门编程语言,首先要了解其底层的实现机制和程序处理的过程,也就是了解其设计思想和解析实现.当然,初始一门语言,可以无须太过深入的去研究这些,但也要在心中建立起一个Python的知识框架,在接下来的学习过程中,不断的其填充和完善这一框架,有如造房筑楼. 初识Python Python 支持OOP(Object Oriented Programming面向对象编程实

想用C#做类似QQ的企业IM通讯工具,需要什么知识储备,读哪方面的书

问题描述 烦请大侠推荐入门书籍~ 解决方案 解决方案二: 解决方案三:Socket和winForm其实直接在网上找个demo就好了解决方案四:看你服务端端详怎么做,是基于socket还是给予service的如果是基于socket那么socket这块知识是必备的(,后者找个socket通信框架了解下如果基于service的可以学习下WCF等解决方案五:界面:WPF通信:WCF其他框架知识,设计模式之类也需要了解其他的就是编程中遇到的小问题需要一些零散的知识处理,现成google就好了.解决方案六:

【手游】手游行业专业相关知识储备

相关概念整理 SP 移动互联网服务内容应用服务的直接提供者,负责根据用户的要求开发和提供适合手机用户使用的服务.SP为Service Provider服务提供商的简称,直译就是"服务提供" 门户型 SP 由门户网站提供的短信服务.主要有搜狐.新浪.网易.中华网.Tom等几家.其短信服务的内容主要有铃声.图片.文字传情.新闻.游戏等. 专业型 SP 短信作为主业的公司.从提供的服务品种来看,专业型 SP和门户型SP几乎完全重合.但从具体的业务来看,专业型SP的优势在于需要不断创新的技术性