分布式通信之Java IO

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();
        }
    }
    

    在这里插入图片描述
    在这里插入图片描述

Selector

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东山富哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值