IO模式总结

网上看了好多IO,NIO的文字,参差不齐,每篇总是差一两个点没有讲到,所以这里对于我自己理解的做一个总结,也许有不对的地方。

1,基本概念

1.1)同步/异步,阻塞/非阻塞

同步异步主要针对C端:

所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

异步的概念和同步相对。当c端一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

阻塞非阻塞主要针对S端:

阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

1.2)面向流,面向缓冲区

面向流:每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。

面向缓冲区:数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。

1.4)内核态和用户态

内核空间可以访问受保护的内存(32位下高位1G为内核空间),剩下3G为用户空间

操作系统限制用户态不能直接访问硬件设备,所以数据从硬件移动到用户进程的内存时需要2步操作。

image.png

1.3)mmap(内存映射)

将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系


image.png
image2.png

2,linux下的5种IO模型

ea103750d79f9799947fdb5a99da11fb.jpg

5种IO模式关于同步异步,阻塞非阻塞的关系

阻塞 非阻塞
同步 BIO nonblockingIO/多路复用IO(NIO1.0)
异步 X AIO
1,BIO:

阻塞当前线程,等待数据准备

image.png

2,nonblockingIO:

image.png

相当于轮训,将大片的等待时间切割成小片,

3,多路复用IO(IO mulitplexing)NIO

监听多个socket,当任何一个数据准备好后就返回,用户进程再调用read函数读取数据,极限情况,如果只监听一个socket那么和BIO是一样的,只有监听的socket多时,才会凸显效率。

重点,后面详细说明

image2.png

4,信号驱动 I/O

5, asynchronous IO

需要操作系统内核支持

image.png

aio目前在linux下存在BUG,使用场景少

3,为什么说JAVA的标准IO是面向流,NIO面向缓冲区

标准IO:

SocketInputStream.read

int read(byte b[], int off, int length, int timeout) throws IOException {

int n;

// EOF already encountered

if (eof) {

return -1;

}

// connection reset

if (impl.isConnectionReset()) {

throw new SocketException("Connection reset");

}

// bounds check

if (length <= 0 || off < 0 || off + length > b.length) {

if (length == 0) {

return 0;

}

throw new ArrayIndexOutOfBoundsException();

}

boolean gotReset = false;

// acquire file descriptor and do the read

FileDescriptor fd = impl.acquireFD();

try {

n = socketRead(fd, b, off, length, timeout);//native函数

if (n > 0) {

return n;

}

} catch (ConnectionResetException rstExc) {

gotReset = true;

} finally {

impl.releaseFD();

}

/*

* We receive a "connection reset" but there may be bytes still

* buffered on the socket

*/

if (gotReset) {

impl.setConnectionResetPending();

impl.acquireFD();

try {

n = socketRead(fd, b, off, length, timeout);//native函数,这里从内核态中读取数据到数组b中

if (n > 0) {

return n;

}

} catch (ConnectionResetException rstExc) {

} finally {

impl.releaseFD();

}

}

/*

* If we get here we are at EOF, the socket has been closed,

* or the connection has been reset.

*/

if (impl.isClosedOrPending()) {

throw new SocketException("Socket closed");

}

if (impl.isConnectionResetPending()) {

impl.setConnectionReset();

}

if (impl.isConnectionReset()) {

throw new SocketException("Connection reset");

}

eof = true;

return -1;

}

面向缓冲区(NIO):

DatagramChannelImpl.receive

private int receive(FileDescriptor fd, ByteBuffer dst)

throws IOException

{

int pos = dst.position();

int lim = dst.limit();

assert (pos <= lim);

int rem = (pos <= lim ? lim - pos : 0);

if (dst instanceof DirectBuffer && rem > 0)

return receiveIntoNativeBuffer(fd, dst, rem, pos);

// Substitute a native buffer. If the supplied buffer is empty

// we must instead use a nonempty buffer, otherwise the call

// will not block waiting for a datagram on some platforms.

int newSize = Math.max(rem, 1);

ByteBuffer bb = Util.getTemporaryDirectBuffer(newSize);//申请一块newSize大小的缓冲区块

try {

int n = receiveIntoNativeBuffer(fd, bb, newSize, 0);//数据读取到缓冲区中,buffer可以做标记,操作指针等

bb.flip();

if (n > 0 && rem > 0)

dst.put(bb);

return n;

} finally {

Util.releaseTemporaryDirectBuffer(bb);

}

}

channel buffer 说明:

http://www.jianshu.com/p/052035037297

4,select,poll,epoll详解

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

监视所有的readFD,writeFD,exceptFD

select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

pollfd并没有最大数量限制

select和poll没有太大区别,都是轮训所有的fd/pollfd来获取准备好的fd,当有大量连接的客户端时,效率会线性下降

epoll

1)int epfd = epoll_create(intsize); 创建一个ep句柄(/proc/进程id/fd/),用于监听所有注册的套接字,存在一个红黑树的数据结构中,这棵红黑树的存储通过mmap将内核态和用户态共享,减少用户态和内核态之间的数据交换,而select/poll每次轮训时都要将相关的句柄从内核态拷贝至用户态。

2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 添加/修改/删除注册的套接字,由于是在红黑树中,效率较高,当事件添加时,该事件会与相应的设备(网卡)驱动程序建立回调连接,一旦事件发生(文件fd改变)相应fd会回调这个函数,将事件添加到一个rdllist(双向链表)中

事件类型:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断;

3)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

3.1)调用ep_poll,当rdllist为空时挂起,一直到rdllist不为空时唤醒

3.2)ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。

epoll.jpg

3,asynchronous IO

IO详解:https://segmentfault.com/a/1190000003063859#articleHeader14

select、poll、epoll之间的区别总结[整理]http://www.cnblogs.com/Anker/p/3265058.html

http://www.cnblogs.com/lojunren/p/3856290.html

http://www.smithfox.com/?e=191

这篇对IO流操作写的比较好 http://www.cnblogs.com/hapjin/p/5736188.html

补充:

创建Selectorprovider

当linux内核>2.6时使用epoll

public static SelectorProvider create() {

String osname = AccessController.doPrivileged(

new GetPropertyAction("os.name"));

if ("SunOS".equals(osname)) {

return new sun.nio.ch.DevPollSelectorProvider();

}

// use EPollSelectorProvider for Linux kernels >= 2.6

if ("Linux".equals(osname)) {

String osversion = AccessController.doPrivileged(

new GetPropertyAction("os.version"));

String[] vers = osversion.split("\\.", 0);

if (vers.length >= 2) {

try {

int major = Integer.parseInt(vers[0]);

int minor = Integer.parseInt(vers[1]);

if (major > 2 || (major == 2 && minor >= 6)) {

return new sun.nio.ch.EPollSelectorProvider();

}

} catch (NumberFormatException x) {

// format not recognized

}

}

}

return new sun.nio.ch.PollSelectorProvider();

}

select poll模式

int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

通过程序去控制传入的监控fd

代码层面监视多个描述符,描述文件越多越慢

nio selector:

http://www.jianshu.com/p/0d497fe5484a

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所...
    Daniel521阅读 1,368评论 0 6
  • IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步、异步、阻塞、非阻塞 同步(synchronous)...
    可笑的黑耀斑阅读 1,158评论 0 2
  • 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所...
    lxqfirst阅读 2,137评论 0 47
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,506评论 0 5
  • python之路——IO模型 IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步、异步、阻塞、非阻塞 ...
    go以恒阅读 542评论 0 2