
Java IO相关操作(socket)
概念:
java中最频繁的IO操作就是对网络的的IO了,其本质也是对文件进行IO操作,java中网络通信基石就是socket,windows和unix中网络通讯首选socket,当然也有其他方式的,待探讨.
关于socket:
我的理解既然socket是tcp协议实现的一种接口,工作在传输层,其后的应用层http,ftp,dns等也是基于tcp,本质上也是通过socket进行实现(各自是一种特殊的socket),再高级一点的协议和websocket,netty,emqtt,activemq等都需要用到socket(待补充)
Socket本质上还是文件,因为Linux上一切皆文件,就是io操作,这里先看一下unix下的5种网络io模型,对比java中的实现理解要清晰点.
“阻塞”与”非阻塞”与”同步”与“异步”不能简单的从字面理解,提供一个从分布式系统角度的回答(转)
关于阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
关于同步与异步:
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
典型的异步编程模型比如Node.js
阻塞与非阻塞与是否同步异步无关
Unix的5种Io模型:
- 阻塞IO
- 非阻塞IO
- IO多路复用
- 信号驱动IO(SIGIO)
- 异步IO
一、同步阻塞IO(Blocking IO):
用户调用Io操作后,进入block状态,直到数据读取数据完成,才释放cpu,大多数操作都是同步阻塞的,看下图
《linux高级程序设计》中讲到
一旦进程期望读取数据,就调用read/write函数,进程从调用这些函数开始,一直到返回这段时间都处在阻塞状态,当recv正常返回时,进程继续其他操作
二、同步非阻塞IO(Non-blocking IO):
非阻塞IO就是用户进程调用IO系统调用,如果被读取的数据没有准备好,则立即返回,与阻塞式io区别就是,当数据没准备好时,阻塞式就会阻塞当前进程直到数据读写完毕,非阻塞则是数据没准备好,那好我先返回,返回个错误值。
这种方式最大的问题应该就是它需要反复地去轮询数据是否准备好,这将是一个极浪费 CPU资源的操作,但是在大量短连接请求的http服务器中,由于并发请求数很高,所以几乎每一次recvfrom调用都有数据拷贝,所以也不会十分浪费CPU资源。
三、IO多路复用(IO Multiplexing):
IO多路即经典的Reactor设计模式,有时也称为异步阻塞IO,unix下由select()系统调用或poll()或epoll()来实现(下面会说道它们的区别),本质上是将访问数据是否就绪的函数与读取数据的函数分开。
《linux高级程序设计》中
多路复用方式任然是以阻塞方式等待文件Io准备好,但其可以同时等待多个文件描述符,如果当前有一个或多个socket有状态变化,则从阻塞状态返回,转而处理该文件描述符Io操作
多路复用的高级之处在于:它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
IO 多路技术一般在下面这些情况中被使用:
1、当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字),I/O 多路复用技术将会有机会得到使用。
2、当程序需要同时进行多个套接字的操作的时候。
3、如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。
4、如果一个服务器程序同时使用 TCP 和 UDP 协议。
5、如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetd就是这样的)。
四、信号驱动IO(SIGIO):
内核提供一种异步数据处理方式,让内核在文件描述符就绪后产生SIGIO信号,通知用户数据或空间准备好,信号驱动式IO模型不需要轮询检查底层IO数据是否就绪,而是被动接收信号,然后再调用recvfrom执行IO操作。
比起多路复用IO模型来说,信号驱动IO模型针对的是一个IO的完成过程, 而多路复用IO模型针对的是多个IO同时进行时候的场景
五、异步IO:
异步IO模型的工作机制是,将整个IO操作(包括等待数据就绪,复制数据到应用程序工作空间)全都交给操作系统完成,操作系统完成整个过程之后,再通知应用程序。
异步IO模型跟信号驱动IO模型很相似,但是区别是信号驱动模型是在数据就绪的时候通知应用程序,应用程序再调用系统函数recvfrom进行IO操作。
而异步IO模型则是数据就绪且操作系统已经将数据拷贝进应用程序运行空间之后,操作系统再通知应用程序,这个过程中应用程序不需要阻塞。异步IO可以如下图表示
Java中的BIO,NIO,AIO:
netty中比较从权威的说法
BIO:b有两说,一为base,jdk中最早抽象出的io体系;一为block,jdk 1.0 中的io体系是阻塞的。所以两说皆有道理,一般我们认为b取block之意 。
NIO:n也有两说,一为new,针对base而言;一为non-block,针对block而言 jdk1.4后推出,传统io是面向字节流的,而nio是面向缓冲区的。
AIO:异步IO也有NI0.2之说。
BIO(同步阻塞)通信模型图:
每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况。
项目实例:我这里放github里面了自己down下来理解
伪异步IO模型:
为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,就相当于加了个缓冲区,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。
其实并没有解决前面提到的资源等待问题,依旧会等待Io操作完毕.
下面祭出心心念念的NIO
NIO 模型:
JavaNIO中引入了几个概念,chanel,buffer,selector
传统的Io是面向字节流的,NIO是面向缓冲区的(即buffer)
Buffer:
Java NIO中的Buffer用于和NIO通道进行交互,缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Chanel:
Java NIO的通道类似流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的(InputStream,OutputStream分开的)。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
如上图所示,从通道读取数据到缓冲区,从缓冲区写入数据到通道。
Selector:
JavaNIO编程的基础是Selector,也就是多路复用,Selector会不断的轮询注册在Selector上的Channel,如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取已经准备就绪的Channel集合,进行后续的I/O操作。
jdk用epoll()取代了之前的selector,所以他并没有最大连接句柄的限制。也就是说,一个线程可以负责处理成千上万的客户端
在NIO类库中加入Buffer对象,体现了新库与原IO的一个重要的区别。在面向流的IO中,可以将数据直接写入或读取到Stream对象中。在NIO库中,所有数据都是用缓冲区处理的(读写)。
AIO 模型:
AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。
但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。