文件描述符(descriptors)

Unix中I/O的基本组成元素是字节序列。大多数程序应用于字节流或I/O流。
进程通过描述符引用I/O流,也被称作文件描述符。管道、文件、POSIX IPC's(消息队列,信号量,共享内存),事件队列等都是通过文件描述符引用I/O流。

创建和释放描述符

描述符创建:

  • 通过系统命令调用(open,pipe,socket等)创建;
  • 继承自父进程。

描述符释放:

  • 进程退出
  • 系统调用close
  • 标记为close on exec的描述符在exec后释放

Close-on-exec

当进程forks时,所有描述符都会复制到子进程中。如果任意描述符被标记为close on exec,那么当子进程execs之前,父进程forks之后,这些描述符将关闭并且在子进程中不再可用。

使用描述符通过readwrite命令调用的数据转换

File Entry

每个描述符都指向内核中的File entry的数据结构。file entry为每个描述符维度了一个file offset。系统调用命令open创建file entry.

Fork/Dup and File Entries

fork创建的描述符被父子进程共享,在file entry中引用同一个offsetdup/dup2的系统调用与此类似。

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>int main(char *argv[]) {  int fd = open("abc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);  fork();  write(fd, "xyz", 3);  printf("%ldn", lseek(fd, 0, SEEK_CUR));  close(fd);  return 0;
}

运行结果

3
6

Offset-per-descriptor

因为多个描述符可能引用同一个file entry, file entry为每个描述符维护了一个file offset。read和write操作从这个file offset开始,并且在数据转换之后file offset也将更新。offset决定了下次read write操作的位置。当进程终止时,内核将回收所有该进程所持有的描述符,如果此进程是引用file entry的最后一个进程,内核将回收整个file entry。

剖析File Entry

每个file entry包含:

  • 类型
  • 函数指针数组。这个函数指针数组将通用的对描述符的操作转换为具体文件类型的实现。

稍微解释下,所有的描述符都对外提供了一套通用的API操作,包含读、写、修改描述符模式、截断描述符、ioctl操作、polling等。
针对不同类型的文件,这些操作都有所不同,并且有不同的实现。对sockets的读操作与对pipes的读操作就有所不同,即使它们高层次的API是一样的。open命令并不在此列,因为不同类型的文件的open操作差异非常大。但是一旦file entry由open创建,剩下的操作都可以使用同一套通用的API

大多数的网络通讯使用sockets。sockets由描述符引用,作为传输的终点。两个进程可以创建两个sockets,通过连接这两个sockets建立可靠的字节流传输。一旦连接建立,描述符可以使用file offsets进行读写。内核可以将一个进程的输出重定向到另一台机器的另一个进程。对于字节流连接,统一使用read write命令读写,但对于不同类型的消息(比如网络数据包)使用不同的系统命令处理。

非阻塞描述符

默认情况下,在没有数据可用时,通过描述符read将阻塞。writesend也是如此。多数描述符的操作都是如此,但是磁盘文件除外,因为写磁盘并不是直接写,而是通过内核的buffer cache。只有当open磁盘文件时使用O_SYNC标识才会同步写磁盘。

任何描述符(pipes, FIFOs, sockets, terminals, pseudo-terminals等)都可以设置为非阻塞模式。当一个描述符设置为非阻塞模式时,对此描述符的I/O调用都将立即返回,即使此请求并不能马上完成(请求完成期间将使进程阻塞)。返回值分为下列情况:

  • an error: 操作完全不能完成
  • a partial count: 输入或输出可以部分完成
  • the entire result: I/O操作可以完全完成

通过设置非延迟标识O_NONBLOCK将描述符设置为非阻塞模式。这个标识也被叫做“open-file”状态标识。

描述符就绪

当进程通过描述符执行I/O操作时不被阻塞,称为描述符就绪。描述符就绪与操作是否会传输数据无关,而只与I/O操作是否可以无阻塞执行相关。

当有I/O事件发生时描述符进行就绪状态,例如新输入的到达、socket连接完成或者当TCP将列队中的数据传输后,socket的发送buffer出现可用容量时。

有两种方式可以判断一个描述符是否进入就绪状态——edge triggered和level triggered

Level Triggered

可以把level triggered看作是拉模式(pull或poll模式)。为了判断一个描述符是否就绪,进程尝试执行非阻塞的I/O操作。进程可以执行任意次这样的操作。这为随后的I/O操作提供了更多灵活性。比如,一个描述符进入就绪状态,进程可以读取所有可用数据,也可以不执行任何I/O操作,或者不读取buffer中的所有数据。
下面举例来看下

在t0时间,进程尝试使用非阻塞描述符进行I/O操作。如果I/O操作阻塞,系统调用返回error。

在t1时刻,进程再一次执行I/O,假设这次操作也阻塞并返回error。

在t2时刻,进程又执行了I/O,假设也阻塞或返回error。

假设到了t3时刻,进程拉取描述符的状态并且描述符就绪。进程可以执行整个I/O操作(例如读取socket上所有可用数据)

假设t4时刻,进程拉取描述符状态但描述符并没有就绪,这次调用将再次阻塞或返回error。

t5时刻,描述符就绪,进程只执行了部分I/O操作(例如只读取一半可用数据)

t6时刻,描述符就绪,进程什么I/O操作也没执行

Edge Triggered

当描述符就绪时,进程将收到一个通知(通常是描述符上有新事件发生)。可以把这种模式看作是push模式,这个描述符就绪的通知是被push给进程的。注意,push模式仅通知进程描述符已就绪,而不会通知其他信息,比如有多少数据已到达socket的buffer中。

因此,通过这种方式进程只能获取到不完整的数据,所以进程需要继续进行操作。当每次得到通知时,进程尝试进行最多的I/O操作,如果不这样做,进程不得不等到下一次得到通知时才能获取数据,即使在下一次通知到来前仍有部分数据可用。

下面举例说明

在t2时刻,进程得到描述符就绪的通知

可用的字节流存储在buffer中,假设有1024个字节可读。

假设进程只读取了其中的500个字节

这意味着在t3 t4 t5时刻,buffer中仍然有524个字节可使进程无阻塞地读取。但是因为只有在它得到下次通知时才会执行I/O操作,这524个字节的数据在这期间将一直留在buffer中。

假设进程在t6时刻接到下次通知,buffer中又有1024个字节可用。此时buffer中可用的数据为1548个字节——524字节是上次没读的,1024是新到达的。

假设进程这次读取了1024字节。

这意味着在这次I/O操作结束后仍有524字节的数据留在buffer中,直到一次通知到来进程才能读取到。

当一个描述符在通知来到时如果尝试执行所有I/O操作,可能造成其他描述符“饥饿”。即使使用level triggered,一次大量的writesend也可能导致阻塞。

多路复用I/O

上面我们只讨论了一个进程只处理一个描述符的情况。通常进程处理多个描述符。一个常见的场景是一个应用程序需要打印日志,同时接收socket连接并且和其他服务建立RPC连接。

有以下几种多路复用I/O方式:

  • 非阻塞I/O(描述符本身被标识为非阻塞,操作可能部分完成)
  • 信号驱动I/O(当I/O状态变化时通知拥有描述符的进程)
  • polling I/O(通过selectpoll系统调用,这两者都提供了level triggered方式的描述符就绪通知机制)
  • BSD 机制的内核事件polling(使用kevent系统调用)

非阻塞I/O的多路复用I/O

描述符

将所有描述符都设置为非阻塞模式

进程

进程尝试对描述符执行I/O操作,检查是否有任意I/O操作返回error。

内核

内核在描述符上执行I/O操作,返回error或部分输出或者是全部结果。

缺点

频繁检查:如果进程频繁尝试执行I/O操作,进程不得不持续地重复检查描述符是否就绪的操作。在tight循环中这样的busy-waiting可能会耗尽CPU周期。不频繁检查:如果这样的操作执行不频繁,可能使进程对于有效的I/O事件长时间得不到响应。

何时使用

对于输出描述符(比如write)的操作并不总是阻塞的。在这种场景下,可以首先尝试执行I/O操作,如果返回error再回退到polling。当使用edge-triggered通知方式时也可以使用这种方式,此时描述符设置为非阻塞模式,进程一旦得到一个I/O事件的通知,进程可以重复执行I/O操作直到系统调用被阻塞(EAGAIN or EWOULDBLOCK)。

信号驱动I/O的多路复用I/O

描述符

当任意描述符上可执行I/O操作时,内核将发送通知给进程。

进程

进程等待任何描述符就绪的信号。

内核

跟踪描述符列表,当任意描述符就绪时给进程发送信号通知。

缺点

捕获信号的开销较大,当大量I/O操作时使用信号驱动I/O方式并不现实。

何时使用

通常在一些“特例条件”下使用,此时处理信号的开销低于不断使用select/poll/epollkevent的polling操作。一个“特例条件”的场景是socket上的带外(out-of-band)数据的到达。总之不常用。

人类身份验证 - SegmentFault​segmentfault.com

socket接收时信号量阻塞了会丢数据吗_浅谈Java网络编程——非阻塞I/O相关推荐

  1. Qt:Qt实现Winsock网络编程—非阻塞模式下的简单远程控制的开发(WSAAsyncSelect)

    Qt实现Winsock网络编程-非阻塞模式下的简单远程控制的开发(WSAAsyncSelect) 前言 这边博客应该是 Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 的姐妹篇,上 ...

  2. 浅谈Java网络编程之Socket (2)

    <浅谈Java网络编程之Socket (1)>中我们已经和大家说到客户端的网络编程,下面和大家分享的是服务器的实现代码. import java.net.*; import java.io ...

  3. linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现

    一.<UNIX网络编程>-非阻塞connect 在一个TCP套接口被设置为非阻塞之后调用connect,connect会立即返回EINPROGRESS错误,表示连接操作正在进行中,但是仍未 ...

  4. linux网络编程-----非阻塞connect

    libevent在内部由于使用io多路复用函数进行的事件监控,所以它所有的io读写操作都是非阻塞的,换句话说,libevent提供的接口函数evconnlistener_new_bind()和buff ...

  5. python 网络编程----非阻塞或异步编程

    From: http://blog.chinaunix.net/uid-20730371-id-765038.html 非阻塞或异步编程 例如,对于一个聊天室来说,因为有多个连接需要同时被处理,所以很 ...

  6. 浅谈Java网络编程之Socket (1)

    和大家一起分享的是Java网络编程之Socket.在Java中Socket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有两个关键的方法,一个是getInputStream方法,另一个是get ...

  7. python并发编程之semaphore(信号量)_浅谈Python并发编程之进程(守护进程、锁、信号量)...

    前言:本博文是对Python并发编程之进程的知识延伸,主要讲解:守护进程.锁.信号量. 友情链接: 一.守护进程(daemon) 1.1 守护进程概念 首先我们都知道:正常情况下,主进程默认等待子进程 ...

  8. java epoll select_Java 非阻塞 IO 和异步 IO

    点击上方 Java后端,选择 设为星标 优质文章,及时送达 作者 | HongJie 链接 | javadoop.com/post/nio-and-aio 本文将介绍非阻塞 IO 和异步 IO,也就是 ...

  9. java网络编程阻塞_Java网络编程由浅入深三 一文了解非阻塞通信的图文代码示例详解...

    本文详细介绍组成非阻塞通信的几大类:Buffer.Channel.Selector.SelectionKey 非阻塞通信的流程ServerSocketChannel通过open方法获取ServerSo ...

最新文章

  1. 一文概览 CVPR2021 最新18篇 Oral 论文
  2. Nacos系列:Nacos的三种部署模式
  3. 很多人都不知道,其实博客园给我们博客开了二级域名
  4. 博士学位被撤三天后,她的大学教职也被开除!
  5. redis 转义字符_一份完整的阿里云 Redis 开发规范,值得收藏!
  6. Ubuntu下编译SHTOOLS
  7. vb access mysql数据库教程_VB操作access数据库
  8. 财务自由的日子,我抑郁了
  9. python进行图像识别与分类_使用机器学习模型快速进行图像分类识别
  10. Nacos 服务治理(服务注册中心)
  11. 美团无人配送部总经理夏华夏演讲:从技术细节看美团架构
  12. STL的使用和背后数据结构
  13. python基础课程讲解基本语法常见运算符以及结构语句
  14. 题目内容:你的程序要读入一个整数,范围是[-100000,100000]。然后,用汉语拼音将这个整数的每一位输出出来。如输入1234,则输出:yi er san si注意,每个字的拼音
  15. 武汉新时标文化传媒有限公司抖音电商的算法逻辑
  16. mysql试题百度云_MYSQL练习题及答案
  17. ViT/vit/VIT详解
  18. vue3中使用webcamjs拍照
  19. aarch64指令集_AArch64应用程序级编程模型
  20. [机器学习]最小估计法和最大似然法证明

热门文章

  1. bottomnavigationview放大两边没有_有没有什么HAPPYEND的动漫?
  2. 关于JVM内存的N个问题
  3. 2020远程面试几家公司后,从阿里、美团、携程带回来的面试题及文档
  4. Spring框架----Spring的基于XML的AOP的实现
  5. 第二场周赛(递归递推个人Rank赛)——题解
  6. error in static/js/xxx.js from UglifyJs Unpected token: punc() [static/js/xxx.js]
  7. 用jquery或js实现三个div自动循环轮播
  8. iOS中的中间人-NSURLProtocol 的原理和使用实例
  9. 查找一:C++静态查找
  10. Java中static、final用法小结