目录

Java I/O 基础

Java NIO 发展简史

传统 BIO 编程


Java I/O 基础

Java JDK 1.4 推出 Java NIO 之前,基于 Java 的所有 Socket 通信都采用了同步阻塞IO(Blocking IO—BIO),这种一请求一应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈。平时使用的 TCP 编程就是典型的 BIO 模型!

BIO(Blocking IO)方式导致开发人员在开发高性能的 I/O 程序时,会面临一些巨大的挑战与困难,主要问题如下:

1)没有数据缓冲区,I/O 性能存在问题
2)没有 C 或者 C++ 中的 Channel(通道)概念,只有输入和输出流
3)同步阻塞式 I/O 通信(BIO—Blocking IO),通常会导致通信线程被长时间则塞
4)支持的字符集有限,硬件可移植性不好

于是在 Java 支持异步 I/O 之前的很长一段时间内,高性能服务器开发领域一直被 C++ 和 C 语言长期占据,因为它们可以直接使用操作系统提供的异步 I/O(AIO) 能力。

当并发访问量增大、响应时间延迟增大之后,采用 Java BIO (Blocking IO)开发的服务器软件只能通过硬件的不断扩容来满足高并发和低延迟,这极大地增加了企业的成本,并且随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战,只能通过采购性能更高的硬件服务器来解决问题,这就导致了恶性循环。

正式由于 Java 传统 BIO (Blocking IO) 的拙劣表现,最终在 Java JDK 1.4 版本提供了新的 NIO(New IO) 类库,Java 也终于开始支持非阻塞 I/O。

Java NIO 发展简史

Java JDK 从 1.0 到 1.3,Java 的 I/O 类库都非常原始,很多 Unix 网络编程中的概念或者接口在 I/O 类库中都没有体现,例如 Pipe、Channel、Buffer、Selector 等。

2002 年发布 Java JDK 1.4 时,NIO(New IO) 以 JSR-51 的身份正式随 JDK 发布,新增了 java.nio 包,提供了很多进行异步 I/O 开发的 API 和类库,主要的类和接口如下:

1)进行异步 I/O 操作的缓冲区 ByteBuffer 等
2)支持异步 I/O 操作的管道 Pipe
3)进行各种 I/O 操作(异步或者同步)的 Channel ,包括 ServerSocketChannel、SocketChannel
4)多种字符集的编码能力和解码能力;
5)实现非阻塞 I/O 操作的多路复用器 Selector
6)基于流行的 Perl 实现的正则表达式类库
7)文件通道 FileChannel

JDK 1.4 新增的 NIO (New IO)类库极大地促进了基于 Java 的异步非则塞编程的发展与应用,但是仍然有不完善的地方,特别是对文件系统的处理能力不足,主要问题如下:

1)没有统一的文件属性(例如读写权限)
2)API 能力比较弱,例如目录的级联创建和递归遍历,往往需要自己实现
3)底层存储系统的一些高级 API 无法使用
4)所有的文件操作都是同步则塞调用,不支持异步文件读写操作

2011 年 7 月 28 日,Java JDK 1.7 正式发布,它的一大亮点就是将原来的 NIO(New IO) 类库进行了升级,被称为 NIO2.0。NIO2.0 由 JSR-203 演进而来,它主要提供了如下方面的改进:

1)提供了能够批量获取文件属性的 API,这些 API 具有平台无关性,不与特定的文件系统耦合。另外还提供了标准的文件系统的 SPI (Serial Peripheral Interface—串行外设接口),供各种服务提供商扩展实现
2)提供 AIO( Asynchronous IO—异步非阻塞IO)功能,支持基于文件的异步IO操作和针对网络套接字的异步操作
3)完成 JSR-52 定义的通道功能,包括对配置和多播数据报的支持等


传统 BIO 编程

1、网络编程的基本模型时 Client/Server 模型,也就是两个进程之间相互通信其中服务端提供位置信息(绑定的 IP 地址与监听的端口),客户端通过连接操作向服务器监听地址发起连接骑请求,通过三次握手连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。

2、传统的同步阻塞模型(BIO)开发中,ServerSocket 负责绑定 IP 地址,启动监听端口,Socket 负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。

BIO 通信模型的服务端通常由一个独立的 Accpetor(接受者)线程负责监听客户端的连接,它接收到客户端连接请求后为每个客户端创建一个新的线程进行单独处理,处理完成后,通过输出流返回给客户端,线程销毁,这就是典型的一请求一应答通信模型。

BIO 模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1正比关系,由于线程是 Java 虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急骤下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。

BIO 编 码:这里以一个例子进行说明,客户端往服务器发送数据,服务器回复数据。

·服务端·

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/*** Created by Administrator on 2018/10/14 0014.* 时间服务器*/
public class TimeServer {public static void main(String[] args) {tcpAccept();}public static void tcpAccept() {ServerSocket serverSocket = null;try {/**Tcp 服务器监听端口,ip 默认为本机地址*/serverSocket = new ServerSocket(8080);/**循环监听客户端的连接请求* accept 方法会一直阻塞,直到 客户端连接成功,主线程才继续往后执行*/Socket socket = null;while (true) {System.out.println("等待客户端连接..........");socket = serverSocket.accept();System.out.println("客户端连接成功..........");/*** 为每一个客户端连接都新开线程进行处理*/new Thread(new TimeServerHandler(socket)).start();}} catch (IOException e) {e.printStackTrace();} finally {/**发生意外时,关闭服务端*/if (serverSocket != null && !serverSocket.isClosed()) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

对每个客户端连接新开线程单独处理:

import java.io.*;
import java.net.Socket;
import java.util.Date;
/*** Created by Administrator on 2018/10/14 0014.* 为每个 TCP 客户端新开线程进行处理*/
public class TimeServerHandler implements Runnable {private Socket socket = null;/*** 将每个 TCP 连接的 Socket 通过构造器传入** @param socket*/public TimeServerHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {DataInputStream dataInputStream = null;DataOutputStream dataOutputStream = null;try {/**读客户端数据*/InputStream inputStream = socket.getInputStream();dataInputStream = new DataInputStream(inputStream);String message = dataInputStream.readUTF();System.out.println(Thread.currentThread().getName() + " 收到客户端消息:" + message);/**往客户端写数据*/OutputStream outputStream = socket.getOutputStream();dataOutputStream = new DataOutputStream(outputStream);dataOutputStream.writeUTF(new Date().toString());dataOutputStream.flush();} catch (IOException e) {e.printStackTrace();} finally {/**操作完成,关闭流*/if (dataOutputStream != null) {try {dataOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (dataInputStream != null) {try {dataInputStream.close();} catch (IOException e) {e.printStackTrace();}}/**操作完成,关闭连接,线程自动销毁*/if (socket != null && !socket.isClosed()) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

·客户端·

import java.io.*;
import java.net.Socket;
/*** Created by Administrator on 2018/10/14 0014.* 时间 客户端*/
public class TtimeClient {public static void main(String[] args) {/*** 3个线程模拟3个客户端*/for (int i = 0; i < 3; i++) {new Thread() {@Overridepublic void run() {tcpSendMessage();}}.start();}}/*** Tcp 客户端连接服务器并发送消息*/public static void tcpSendMessage() {Socket socket = null;DataOutputStream dataOutputStream = null;DataInputStream dataInputStream = null;try {/*** Socket(String host, int port):*      host)被连接的服务器 IP 地址*      port)被连接的服务器监听的端口* Socket(InetAddress address, int port)*      address)用于设置 ip 地址的对象* 此时如果 TCP 服务器未开放,或者其它原因导致连接失败,则抛出异常:* java.net.ConnectException: Connection refused: connect*/socket = new Socket("127.0.0.1", 8080);System.out.println("连接成功.........." + Thread.currentThread().getName());/**往服务端写数据*/OutputStream outputStream = socket.getOutputStream();dataOutputStream = new DataOutputStream(outputStream);dataOutputStream.writeUTF("我是长城" + Thread.currentThread().getName());dataOutputStream.flush();/**读服务端数据*/InputStream inputStream = socket.getInputStream();dataInputStream = new DataInputStream(inputStream);String message = dataInputStream.readUTF();System.out.println("收到服务器消息:" + message);} catch (IOException e) {e.printStackTrace();} finally {/**关闭流,释放资源*/if (dataOutputStream != null) {try {dataOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (dataInputStream != null) {try {dataInputStream.close();} catch (IOException e) {e.printStackTrace();}}/** 操作完毕关闭 socket*/if (socket != null && !socket.isClosed()) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

服务端输出:

等待客户端连接..........
客户端连接成功..........
等待客户端连接..........
客户端连接成功..........
等待客户端连接..........
客户端连接成功..........
等待客户端连接..........
Thread-0 收到客户端消息:我是长城Thread-2
Thread-1 收到客户端消息:我是长城Thread-0
Thread-2 收到客户端消息:我是长城Thread-1

客户端输出:

连接成功..........Thread-1
连接成功..........Thread-2
连接成功..........Thread-0
收到服务器消息:Sun Oct 14 18:38:53 CST 2018
收到服务器消息:Sun Oct 14 18:38:53 CST 2018
收到服务器消息:Sun Oct 14 18:38:53 CST 2018

总 结

BIO 阻塞式编程如上所示说明完毕,BIO 主要的问题在于每当有一个新的客户端请求连接时,服务器端必须新建线程进行处理,一个线程只能处理一个客户端连接。

在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足需求。为了改进这种一个线程一个连接的模型,后来又演进出了一种通过线程池或者消息队列实现一个或者多个线程处理 N 个客户端的模型,由于它的底层通信机制仍然使用同步阻塞 I/O ,所以被称为 “伪异步”。

Java I/O 进化之路、传统 BIO 编程相关推荐

  1. Netty入门笔记-BIO编程

    导语   对于网络编程来说最为典型的就是基于客户端.服务器的C/S模型.也就是说客户端有一个线程,服务器端有一个线程,两个线程之间进行相互的通信.其中服务器段提供的是数据的信息,例如IP端口以及数据等 ...

  2. Java的进化之路走到了尽头

    目录 Java的进化之路走到了尽头 Java 的进化之路走到了尽头 编程语言进化的主干 如何保持先发优势 Java的进化之路走到了尽头 " 一百年后,人们使用什么语言开发软件?为什么这个问题 ...

  3. 宜人贷系统架构——高并发下的进化之路

    宜人贷系统架构--高并发下的进化之路 15 JANUARY 2016 演讲嘉宾:宜人贷架构师孙军,拥有10 年的 Java 开发经验,先后在人民银行.1 号店.人人网.当当网从事软件开发与技术架构工作 ...

  4. 宜人贷系统架构——高并发下的进化之路(转载)

    http://www.jianshu.com/p/410250e006cb 宜人贷系统架构--高并发下的进化之路 字数3583阅读159评论0喜欢0 演讲嘉宾:宜人贷架构师孙军,拥有10 年的 Jav ...

  5. Netty(二)(入门篇)传统的Bio编程

    假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情 ...

  6. Java BIO编程

    I/O 模型 1.I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能. (比如客户端和服务器端进行通信用的是单通道还是双通道,异步通信还是同步通信,是阻塞的 ...

  7. 525、Java工程师的进阶之路 -【 RocketMQ (二)】 2022.01.06

    目录 1. RocketMQ 设计目的 1.1. 发布/订阅 1.2. 消息优先级 1.3. 消息顺序 1.4. 消息过滤 1.5. 消息持久化 1.6. 消息可靠性 1.7. 消息实时性 1.8. ...

  8. Java中IO流的分类和BIO,NIO,AIO的区别

    到底什么是IO 我们常说的IO,指的是文件的输入和输出,但是在操作系统层面是如何定义IO的呢?到底什么样的过程可以叫做是一次IO呢? 拿一次磁盘文件读取为例,我们要读取的文件是存储在磁盘上的,我们的目 ...

  9. Java 工程师成神之路 | 2019正式版 1

    Java 工程师成神之路 | 2019正式版 基础篇 01 面向对象→ 什么是面向对象 面向对象.面向过程 面向对象的三大基本特征和五大基本原则 → 平台无关性 Java 如何实现的平台无关 JVM ...

  10. 搜狗网盟CTR预估的进化之路

    [转]http://wenda.chinahadoop.cn/question/931 搜狗王兴星--网盟CTR预估的进化之路,DCon2015文字实录 今天最后一位嘉宾我们也老朋友了,搜狗的王兴星, ...

最新文章

  1. 从一个Bug开始,重新认识一个强大的 Gson
  2. 声音信标发出白噪声和发出chirp信号的对比测距说明
  3. IPFS Series -- Bitswap Protocol
  4. Silverlight与数据库的三种互操作[源代码]
  5. atom上网本 安装linux,拆东墙补西墙?多数Atom上网本或将无法安装Windows 7
  6. JavaScript学习(四十)—字面量创建对象图解
  7. 使用CSS修改HTML5 input placeholder颜色
  8. UML交互图——鲁棒图的三元素:抽象对象,实体对象和控制对象
  9. Abseil之拆分字符串
  10. 素数II题解(素数筛)
  11. 前端JS/TS面试题
  12. 央行征信与互联网征信技术接口区别(征信架构篇)
  13. 干货分享 | B站SLO由失败转成功,B站SRE做对了什么?
  14. 王炸-GPT4.0的新能力与商业价值
  15. PgAdmin中的数据库查询功能
  16. python教科书能在ios系统操作吗_[初学python]苹果何时开放IOS降级通道啊?
  17. NFT会接力Defi,成为下一个热点么?
  18. html中如何写if判断,HTML中的if判断用法
  19. cygwin php5.6,cygwin-autoconf环境搭建
  20. 【模拟面试-10年工作】项目多一定是优势吗?

热门文章

  1. 从Bezier到NURBS曲线(1) - Bezier曲线
  2. cnc加工中心保养表_CNC加工中心常用指令以及保养项目
  3. java 邮箱模板_Java:Spring同时集成JPA与Mybatis
  4. 拓端tecdat:R语言GARCH建模常用软件包比较、拟合标准普尔SP 500指数波动率时间序列和预测可视化
  5. 拓端tecdat|R语言聚类有效性:确定最优聚类数分析IRIS鸢尾花数据和可视化
  6. 拓端tecdat|R如何与Tableau集成分步指南 - 适用于数据科学和商业智能专业人员
  7. JUC与JVM并发编程学习笔记01
  8. 栈的应用 算术表达式转换为后缀表达式
  9. UserWarning: h5py is running against HDF5 1.10.5 when it was built against 1.10.4
  10. Ubuntu如何查看端口已经开放