文章目录
IO
IO概述
1.首先注意IO是InputStream(输入流)和OutputStream(输出流)的缩写
2.这里的InputStream实际上是一个集合,很多集成InputStream的子类都是;
3.我们这里的InputStream(输入流)和OutputStream(输出流)是针对的系统内存来讲的;
将数据(本地磁盘或者网络中的数据)读取写入内存,叫输入;
将内存中的数据写入到文件或者网络叫读取;
IO操作类型
read()
1.read是建立在InputStream之上的;
2.从内存中读取数据;
write()
1.write是建立在OutputStream之上的;
2.向硬盘或者网络Socket中写数据;
阻塞(Block)与非阻塞(Non-Block)的区别:针对的IO操作
IO阻塞模型
1.往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
1.上面试TCP协议通信的原理图,从上面,我们可以看到 客户端send()发送请求(暂且叫线程A),请求数据进入内核发送缓冲区;
2.通过TCP的滑动窗口协议,将内核发送缓冲区的数据会移动到接收缓冲区(前提是,接收缓冲区当前有多余的空间),
然后服务端serverSocket.accept(),知道服务器端rec()获取到线程A发送的数据,并处理完,返回线程A响应数据,
线程A才会算是结束本次请求,否则会一直阻塞,这样随着请求数量的增多,资源大量被耗费;
3.TCP滑动窗口协议参考
https://blog.csdn.net/u014636209/article/details/91345975#534_TCP_500)
IO非阻塞模型
1.当我们send()之后,会同时返回当前处理状态,如果阻塞,也告知客户端,这样客户端可以做其他的事情,节省资源;
2.如果数据已经准备好,也直接返回。
3.实际上对于非阻塞模型,实际上是一个状态的监控,这里会有一个单独的线程selector监控多个线程客户端数据处理的状态
同步与异步:针对IO处理的时间
1.同步:主要是指同一个时间点,只能做一件事,这里在处理IO的时候,如果阻塞会一直等待;
2.异步:主要是指同一个时间点,客户端发送请求之后,如果阻塞,可以做其他事情,异步通知处理结果,无需阻塞等待;
IO模型
BIO-同步阻塞IO:Block IO
服务端样例
BioServer.java
package com.gaoxinfu.demo.socket.bio;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class BioServer {
public static void main(String[] args) throws IOException {
//对于服务端来讲,ServerSocket监听的事肯定是自己服务器的端口,所以这里构造方法中无需IP,只需要填写端口即可
ServerSocket serverSocket=new ServerSocket(8080);
System.out.println("BIO服务已启动,监听端口是8080");
//这里接收到的是客户端的请求
Socket clientSocket=serverSocket.accept();
//客户端请求进程所属应用的端口
System.out.println(clientSocket.getPort());
//从内存缓冲区中获取 客户端发送过来的数据,这里要注意,如果当前客户端进程请求数据在接收缓冲区的数据还没有准备好,那么会一直阻塞在accept()方法上
InputStream inputStream=clientSocket.getInputStream();
byte[] buff=new byte[1024];
int len=inputStream.read(buff);
if (len>0) {
String msg = new String(buff,0,len);
System.out.println("收到 len= "+len+",content = " + msg);
}
}
}
BioClient.java
package com.gaoxinfu.demo.socket.bio;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class BioClient {
public static void main(String[] args) throws UnknownHostException, IOException {
//new Socket("localhost",8080) 服务端的IP和端口
Socket clientSocket=new Socket("localhost",8080);
//从客户端Sockect中获取输出流,将要写出的到网络Sockect中的内容写进去
OutputStream outputStream=clientSocket.getOutputStream();
String content="大家好,我是 clientSocket";
System.out.println("大家好,我是 clientSocket");
//将字符串内容转换为二进制字节
outputStream.write(content.getBytes());
outputStream.close();
clientSocket.close();
}
}
NIO-同步非阻塞IO:NEW IO (利用线程池)
1.我们这里将的IO模型,实际上是侧重于服务端,所以我们重点讲解服务端的代码;
服务端样例
NioServerSocketDemo.java
package com.gaoxinfu.demo.socket.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
/**
*
* @Description: 我们这里以肯德基点餐为例,每次点餐之后会有一个号
* 1. Selector 我们可以理解为肯德基的点餐控台,
* 一旦客户下单,即进入selector控台
* 2.一旦服务员准备好,点餐控台会显示号
* @Author:gaoxinfu
* @Time:2019年6月22日 下午5:44:04
*/
public class NioServerSocketDemo {
//首先定义下我们这个ServerSocket服务监听的那个端口的请求数据
private int port =8080;
//点餐控台
private Selector selector;
//分配字节缓冲区, 用于接收
private ByteBuffer buffer=ByteBuffer.allocate(1024);
/**
* 构造方法
* @param port
*/
public NioServerSocketDemo(int port) {
try {
this.port = port;
/**
* Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,
* 就像标准IO中的ServerSocket一样
*
* 这一步,我们可以认为,肯德基餐厅开始准备上班了(但是还没有接单)
* 初始化:创建一个可以接客的环境
*/
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//我们开那个门进行接客
serverSocketChannel.bind(new InetSocketAddress(this.port));
// BIO 升级版本 NIO,为了兼容BIO,NIO模型默认是采用阻塞式
serverSocketChannel.configureBlocking(false);//非阻塞
//点餐控台,准备好,开始接客
selector=Selector.open();
//将新接入的单子注册到点单控台中,开始进入准备餐食的队列
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void listen(){
System.out.println("监听开始,Port = " +this.port);
while (true) {
//Selects a set of keys whose corresponding channels are ready for I/O operations.
//选择一个已经准备好的可以进行IO操作的响应的通道请求
try {
//叫好,这个时候,可以认为是通知,通知可以继续传递哪些可以处理了
selector.select();
//返回可以进行处理的数据
Set<SelectionKey> selectionKeys= selector.selectedKeys();
//然后迭代(也称之为轮询)
for (SelectionKey selectionKey:selectionKeys) {
deal(selectionKey);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*
* @Title: deal
* @Description: 具体处理某个请求(相当于柜台柜员开始处理每个客户的请求)
* @param selectionKey
* @return: void
* @throws IOException
* @throws
* @Exception:
* @Author: gaoxinfu
* @Time:2019年6月22日 下午8:59:06
*/
private void deal(SelectionKey selectionKey) throws IOException{
/*
* 每一次轮询就是调用一次process方法,而每一次调用,只能干一件事
* 这里要注意在同一时间点,只能干一件事
*
*目前我们根据数据状态去处理,数据有三种状态(事件),分别是
* SelectionKey.OP_ACCEPT; 一个channel成功连接到另一个服务器称为”连接就绪“。
* SelectionKey.OP_CONNECT; 一个server socket channel准备号接收新进入的连接称为”接收就绪“。
* SelectionKey.OP_READ; 一个等待写数据的通道可以说是”写就绪“。
* SelectionKey.OP_WRITE; 一个有数据可读的通道可以说是”读就绪“。
*/
if (selectionKey.isAcceptable()) {
//获取selectionKey的对应的ServerSocketChannel
ServerSocketChannel serverSocketChannel=(ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel=serverSocketChannel.accept();
//一定一定要记得设置为非阻塞,默认阻塞
socketChannel.configureBlocking(false);
//这个当前是连接就绪的状态,然后改变状态,注册为可读状态
selectionKey=socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("当前selectionKey"+selectionKey+"isAcceptable");
}else if (selectionKey.isReadable()) {
SocketChannel socketChannel=(SocketChannel) selectionKey.channel();
int length=socketChannel.read(buffer);
if (length>0) {
buffer.flip();
String content=new String(buffer.array(),0,length);
//这个当前是可读的状态,然后改变状态,注册为可写状态
selectionKey=socketChannel.register(selector, SelectionKey.OP_WRITE);
selectionKey.attach(content);
System.out.println("当前selectionKey"+selectionKey+"isReadable,读取内容: "+content);
}
}else if (selectionKey.isWritable()) {
SocketChannel socketChannel= (SocketChannel) selectionKey.channel();
String content=(String) selectionKey.attachment();
socketChannel.write(ByteBuffer.wrap(("输出 : "+content).getBytes()));
socketChannel.close();
}
}
/**
*
* @Title: main
* @Description: 启动监听测试
* @param args
* @return: void
* @throws
* @Exception:
* @Author: gaoxinfu
* @Time:2019年6月22日 下午9:38:11
*/
public static void main(String[] args) {
NioServerSocketDemo nioServerSocketDemo=new NioServerSocketDemo(8080);
nioServerSocketDemo.listen();
}
}
客户端样例
package com.gaoxinfu.demo.socket.nio;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class NioClientSocketDemo {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket=new Socket("localhost", 8080);
//new Socket("localhost",8080) 服务端的IP和端口
//从客户端Sockect中获取输出流,将要写出的到网络Sockect中的内容写进去
OutputStream outputStream=socket.getOutputStream();
String content="大家好,我是 clientSocket";
System.out.println("大家好,我是 clientSocket");
//将字符串内容转换为二进制字节
outputStream.write(content.getBytes());
outputStream.close();
socket.close();
}
}
上面这样看的话,写的代码还是有点麻烦的
AIO-异步非阻塞IO-Asyn IO (利用事件驱动回调)
服务端样例
package com.gaoxinfu.demo.socket.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* AIO服务端
*/
public class AioServer {
private final int port;
public static void main(String args[]) {
int port = 8000;
new AioServer(port);
}
public AioServer(int port) {
this.port = port;
listen();
}
private void listen() {
try {
ExecutorService executorService = Executors.newCachedThreadPool();
AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup
.withCachedThreadPool(executorService, 1);
// 开门营业
// 工作线程,用来侦听回调的,事件响应的时候需要回调
final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(threadGroup);
server.bind(new InetSocketAddress(port));
System.out.println("服务已启动,监听端口" + port);
// 准备接受数据
server.accept(null,
new CompletionHandler<AsynchronousSocketChannel, Object>() {
final ByteBuffer buffer = ByteBuffer
.allocateDirect(1024);
// 实现completed方法来回调
// 由操作系统来触发
// 回调有两个状态,成功
public void completed(AsynchronousSocketChannel result,
Object attachment) {
System.out.println("IO操作成功,开始获取数据");
try {
buffer.clear();
result.read(buffer).get();
buffer.flip();
result.write(buffer);
buffer.flip();
} catch (Exception e) {
System.out.println(e.toString());
} finally {
try {
result.close();
server.accept(null, this);
} catch (Exception e) {
System.out.println(e.toString());
}
}
System.out.println("操作完成");
}
@Override
// 回调有两个状态,失败
public void failed(Throwable exc, Object attachment) {
System.out.println("IO操作是失败: " + exc);
}
});
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ex) {
System.out.println(ex);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
客户端样例
package com.gaoxinfu.demo.socket.aio;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* AIO客户端
*/
public class AioClient {
private final AsynchronousSocketChannel client;
public AioClient() throws Exception{
client = AsynchronousSocketChannel.open();
}
public void connect(String host,int port)throws Exception{
client.connect(new InetSocketAddress(host,port),null,new CompletionHandler<Void,Void>() {
@Override
public void completed(Void result, Void attachment) {
try {
client.write(ByteBuffer.wrap("这是一条测试数据".getBytes())).get();
System.out.println("已发送至服务器");
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
final ByteBuffer bb = ByteBuffer.allocate(1024);
client.read(bb, null, new CompletionHandler<Integer,Object>(){
@Override
public void completed(Integer result, Object attachment) {
System.out.println("IO操作完成" + result);
System.out.println("获取反馈结果" + new String(bb.array()));
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
}
);
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
public static void main(String args[])throws Exception{
new AioClient().connect("localhost",8000);
}
}
IO模型总结
1.jdk1.4之前都是BIO
2.jdk1.4之后 NIO出现,IO性能得到大幅度提高,Netty默认使用NIO
3.jdk1.7开始出现NIO2(也就是AIO),注意操作系统决定这IO的性能(这里存在兼容问题,目前不是主流)
NIO重点讲解
缓冲区buffer
三要素:position,limit,capacity
-
position
被写入或者读取的元素索引,值由get()/put()自动更新,被初始为0。
-
limit
指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
-
capacity
缓冲区中的最大数据容量;
BufferDemo.java
package com.gaoxinfu.demo.socket.nio; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class BufferDemo { public static void main(String args[]) throws Exception { //这用用的是文件IO处理 FileInputStream fileInputStream = new FileInputStream("E://test.txt"); //创建文件的操作管道 FileChannel fileChannel = fileInputStream.getChannel(); //分配一个10个大小缓冲区,说白了就是分配一个10个大小的byte数组 ByteBuffer byteBuffer = ByteBuffer.allocate(10); output("初始化", byteBuffer); //先读一下 fileChannel.read(byteBuffer); output("调用read()", byteBuffer); //准备操作之前,先锁定操作范围 byteBuffer.flip(); output("调用flip()", byteBuffer); //判断有没有可读数据 while (byteBuffer.remaining() > 0) { byte b = byteBuffer.get(); // System.out.print(((char)b)); } output("调用get()", byteBuffer); //可以理解为解锁 byteBuffer.clear(); output("调用clear()", byteBuffer); //最后把管道关闭 fileInputStream.close(); } //把这个缓冲里面实时状态给答应出来 public static void output(String step, ByteBuffer buffer) { System.out.println(step + " : "); //容量,数组大小 System.out.print("capacity: " + buffer.capacity() + ", "); //当前操作数据所在的位置,也可以叫做游标 System.out.print("position: " + buffer.position() + ", "); //锁定值,flip,数据操作范围索引只能在position - limit 之间 System.out.println("limit: " + buffer.limit()); System.out.println(); } }