inputStream,outputStream
(2)面向字符操做的接口:Reader,Writer
File
(2)面向网络操做的I/O接口:Socket
以网络IO为例:html
当客户端发送的网络包通过路由器和交换器的转发后到达对应服务端的网络适配器(网卡),并存储在对应网络I/O的套接字文件中,而后操做系统会将该文件中的数据通常经过DMA
复制到内存中供应用程序使用;java
在Unix网络编程
这本书中概述了完成上述操做的几种模型:编程
(1)阻塞与非阻塞: 阻塞与非阻塞主要是从 CPU 的消耗上来讲的,阻塞就是 CPU 停下来等待一个慢的操做完成后CPU 才接着完成其它的事。非阻塞就是在这个慢的操做在执行时 CPU 去干其它别的事,等这个慢的操做完成时,CPU 再接着完成后续的操做。bash
(2)同步与非同步: 同步与非同步主要是从程序方面来讲的,同步指程序发出一个功能调用后,没有结果前不会返回。非同步是指程序发出一个功能调用后,程序便会返回,而后再经过回调机制通知程序进行处理。服务器
recvfrom方法
等待客户端发送的数据发送到内存并返回;
这个模型最大的问题就是操做系统中最典型的CPU速度与外设速度不匹配的问题,网络适配器的速度相对于CPU的速度是极慢的,而且此时CPU却一直在阻塞。网络
recvfrom 方法
后,若是此时套接字文件尚未准备好,则直接返回一个错误信息,而后CPU就会去作其余事情,而该线程会不断获取CPU时间片进行轮询,因此该模式下虽然是非阻塞,但其线程切换确实很频繁的,因此经过该方式增长的CPU使用时间与线程切换的成本仍是须要好好评估的;
而且当数据准备好后,而且线程获取到时间片再次调用recvfrom
时,线程仍是须要等待数据拷贝至内存的。异步
多路复用IO:(Java NIO原理) socket
select
,该方法一直会阻塞到IO事件的到来(即套接字文件准备好)再返回,这个时候咱们再调用recvfrom方法
就只须要等待数据拷贝至内存便可;而且select方法
能够监听多个事件,因此联系到Java NIO中时,就是多个线程能够向同一个Selector
注册多个事件,从而达到了多路复用的效果。 异步IO(AIO):post
aio_read
,应用程序调用后便直接返回,而且不须要像前几种模型同样须要等待数据拷贝至内存;
但其内在的实现仍是很复杂的,底层仍是使用BIO实现的,就不展开描述了,由于对编程人员好像并无太大的做用。性能
网络适配器(网卡)的数据准备好的这个过程当中
,而都是通发出种信号进行通知应用程序,虽然信号的实现方式或是用
select
或是用更底层的方式,但本质上仍是很类似的;但信号驱动IO也是须要线程等待数据拷贝至用户空间的。
Java中的Socket是对进行通讯的两端的抽象,其封装了一系列TCP/IP层面的底层操做; 代码以下:
//经过一个IP:PORT套接字新建一个Socket对象,肯定要链接的服务器的位置和端口
Socket socket = new Socket("127.0.0.1", 8089);
//经过Socket对象拿到OutputStream,能够将其理解经过其向服务器端对应的套接字文件写入数据
OutputStream outputStream = socket.getOutputStream();
//使用默认的字符集去解析outputStream的字节流
PrintWriter printWriter = new PrintWriter(outputStream, true);
/*向服务器发送一个HTTP1.1的请求*/
printWriter.println("GET /index.html HTTP/1.1");
printWriter.println("Host: localhost:8080");
printWriter.println("Connection Close");
printWriter.println();
复制代码
//ServerSocket在该套接字上监听链接事件
ServerSocket serverSocket = new ServerSocket(8089, 1, InetAddress.getByName("127.0.0.1"));
//服务端阻塞在accept()方法上,直到客户端的connect()请求,并返回一个Socket对象
socket = serverSocket.accept();
//从返回的Socket对象中获取该Socket对应的套接字文件的内容并进行读取
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
int i = 0;
while (i != -1) {
i = bufferedReader.read();
System.out.println("拿到的数据为:"+(char)i);
}
socket.close();
复制代码
其实Java BIO 即为对系统提供的网络I/O方法的封装;
咱们通常都是适用Acceptor模型来进行BIO服务端的建立
,即经过一个ServerSocket()
监听来自客户端的链接,而后经过三次握手创建链接后便会建立一个子线程并经过线程池进行相应的逻辑处理;
backlog
这个参数来代表在服务端拒绝链接请求以前,能够排队的请求数量,因此这样的模型注定了BIO性能的局限性(排队的通讯线程可能要阻塞一段时间),处理量的局限性;Java NIO
经过多路复用IO的模型实现了单个Selector线程
管理了多个链接,解决了BIO最致命的一个问题;
不管是In/OutputStream
仍是Java NIO中的通道channel
本质上都是对网络I/O文件的抽象,与前者不一样,channel
是双通道的,既能够读又能够写。
因此按照I/O多路复用 的模型,当channel
中的数据准备好了的时候会返回一个可读的事件,而且经过selector进行处理,安排相应的Socket进行相应数据的读取,这是一个数据可读的事件,而Selector可监听的事件有四种:
SelectionKey.OP_CONNECT // 链接事件
SelectionKey.OP_ACCEPT //接收事件
SelectionKey.OP_READ //数据可读事件
SelectionKey.OP_WRITE //可写事件
复制代码
socket.getInputStream.write()
方法来直接进行读写的,而NIO中向channel
中写入数据必须从buffer中获取,而channel
也只能向buffer写入数据,这样使得这样的操做更为接近操做系统执行I/O的方式;细一点讲,是由于在向OutputStream中write()
数据即为向接收方Socket对象中的InputStream
中的RecvQ队列中,而若是write()
的数据大于队列中每一个数据对象限定的长度,就须要进行拆分,而这个过程,咱们是不能够控制的,并且涉及到用户空间与内核空间地址的转换;可是当咱们使用Buffer后,咱们能够控制Buffer的长度,是否扩容以及如何扩容咱们均可以掌握。 参考文章:www.ibm.com/developerwo…/**
* @CreatedBy:CVNot
* @Date:2020/2/21/15:30
* @Description:
*/
public class NIOServer {
public static void main(String[] args) {
try {
//建立一个多路复用选择器
Selector selector = Selector.open();
//建立一个ServerSocket通道,并监听8080端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(8080));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//监听接收数据的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
//拿到Selector关心的已经到达事件的SelectionKey集合
Set keys = selector.selectedKeys();
Iterator iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = (SelectionKey)iterator.next();
iterator.remove();
//由于咱们只注册了ACCEPT事件,因此这里只写了当链接处于这个状态时的处理程序
if(selectionKey.isAcceptable()){
//拿到产生这个事件的通道
ServerSocketChannel serverChannel = (ServerSocketChannel)selectionKey.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
//并为这个通道注册一个读事件
clientChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
}
else if(selectionKey.isReadable()){
SocketChannel clientChannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
long bytesRead = clientChannel.read(byteBuffer);
while(bytesRead > 0){
byteBuffer.flip();
System.out.printf("来自客户端的数据" + new String(byteBuffer.array()));
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
byteBuffer.clear();
byteBuffer.put("客户端你好".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
客户端:
/**
* @CreatedBy:CVNot
* @Date:2020/2/21/16:06
* @Description:
*/
public class NIOClient {
public static void main(String[] args) {
try {
Selector selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(8080));
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
//若是事件没到达就一直阻塞着
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()) {
/**
* 链接服务器端成功
*
* 首先获取到clientChannel,而后经过Buffer写入数据,而后为clientChannel注册OP_READ事件
*/
clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()) {
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.clear();
byteBuffer.put("服务端你好,我是客户端".getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
clientChannel.register(key.selector(), SelectionKey.OP_READ);
} else if (key.isReadable()) {
//通道能够读数据
clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
long bytesRead = clientChannel.read(byteBuffer);
while (bytesRead > 0) {
byteBuffer.flip();
System.out.println("server data :" + new String(byteBuffer.array()));
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
} else if (key.isWritable() && key.isValid()) {
//通道能够写数据
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码