专栏名称: 唤之
目录
相关文章推荐
程序猿  ·  再见 ... ·  3 天前  
程序员小灰  ·  DeepSeek俱乐部,最后一天优惠! ·  3 天前  
OSC开源社区  ·  2024年AI编程技术与工具发展综述 ·  3 天前  
程序员的那些事  ·  苹果放弃 DeepSeek ... ·  3 天前  
51好读  ›  专栏  ›  唤之

Java NIO分析(2): I/O多路复用历史杂谈

唤之  · 掘金  · 程序员  · 2018-07-17 02:43

正文

前面 Java NIO分析(1): Unix网络模型 讲过5种经典I/O模型,
现代企业的场景一般是 高并发高流量 , 长连接 , 假设硬件资源充足,如何提高应用单机能接受链接的上限?
先讲段历史

UNIX的出现

20世纪60年代中期, 那会儿还是 批处理任务 的天下,也就是有一堆job一个个顺序做,
一个做完了才做下一个. 举个栗子, 你没法边听音乐边写博客,也没法边下边播x老师的电影. 随后 分时 这革命性的理念提出来了, 每个job只允许占有一小段CPU时间片执行代码, 假如cpu处理的够快,看起来就像是一堆job并行一样。

分时 理念无疑极大地减少了写代码和获取代码执行结果的时间,到了 70 年代,有人提出要发明一种 更好的 多用户的 , 分时的 环境来执行大多数的共同任务,比如 执行需要大量CPU计算的程序,大量的磁盘访问等等, 这个环境后来就发展成了 Unix

当时,程序阻塞的条件是:

  • 等待CPU
  • 等待磁盘I/O
  • 等待用户输入
  • 等待shell命令结果或者终端结果

在当时也没有多少真正的IPC手段, pipe算是一个。不过对于当时的情况来说,一个进程最多只能打开20个fd, 每个用户最多只能开20个进程
也没有多少IPC和复杂I/O的需求。

早期的Unix也没有fd复用的概念, 如果你ssh远程登录Unix系统,系统要同时处理用户的输入,还给用户输出.当时是靠 cu 这个命令来实现的,
cu 会创建俩进程,一个负责读一个负责写。因为当时的I/O都是阻塞的,如果要同时读写就得搞俩进程.

Socket

到了1983年,BSD4.2发布的时候,一起发布的还有我们今天耳熟能详的 BSD Socket API TCP/IP 协议栈。
Socket 解决了不一定在同一台机器的不同进程之间的通信问题,是一种有效的IPC手段。 Socket 结合 TCP/IP 协议还解决了计算机之间的网络通讯问题.

然而读写fd依然是阻塞的, 假如你要处理俩socket,那么可能在阻塞读socket1的时候,socket2的数据因为来不及处理丢失了。

随着 Socket API一起发布的还有大名鼎鼎的 select 系统调用, 也即 I/O多路复用 的实现。 I/O多路复用 通过使用一个系统函数,如 select , 可以同时等待多个fd的可读,可写等状态。

在没有select之前,一般的unix网络程序是这么写的(accept-and-fork模型)

listenfd = bind();
while(1) {
 fd = accept(listenfd);
 if (fork() == 0) {
   close(fd);

   // 具体的处理代码
   ...
   ...

   exit(0); // 处理完子进程退出
 }
 
 // 关闭fd避免fd泄露
 close(fd)
}

accept-and-fork 是非常费系统资源的,因为每启动一个新的进程,就需要开辟新的栈,分配虚拟内存等,而且多个进程之间由于缺乏IPC手段,
状态难以共享,对于服务端程序来说是灾难。

Select

Select 发布以后, I/O就能复用了,你可以询问内核哪些fd准备就绪了,然后去发系统调用读数据,
读fd的过程是阻塞的,使用select可以避免无意义的阻塞, 这样即使只有一个进程也可以处理多个socket fd的读写

当时贝尔实验室有个产品叫 blit , 是一种多用户实时终端,和现在的terminal差不多,这就要求应用能同时处理读和写, 像 cu 这种靠俩进程







请到「今天看啥」查看全文