1.BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发有局限性,JDK1.4之前是惟一的选择,好处是编码实现方式简单,且也容易理解。2.NIO方式适用于链接数目多且链接比较短的架构,好比聊天服务器,弹幕系统等,相比BIO编码较复杂,JDK1.4之后开始支持。3.AIO方式适用于链接数据多且链接较长的场景,好比相册服务器等,编程较复杂,JDK1.7才开始支持。目前好像并未获得普遍使用。java
Java BIO是传统的Java io编程,相关的类和接口在http://java.io包中编程
Java BIO:同步阻塞,一个链接为一个线程,链接一个客户端就须要启动一个线程进行处理,若是链接未断开且未作任何事,会形成没必要要的开销。能够经过线程池优化。windows
Java BIO:适用于链接数目较小且相对固定的架构,对服务器的要求比较高,对并发有局限性。JDK1.4之前惟一的选择,简单易理解。api
1.服务器启动ServerSoket。2.客户端启动Socket与服务器通讯,默认状况下服务器须要对每一个客户端创建一个线程与之通讯。3.客户端发出请求与服务器通讯。4.若是请求成功,客户端会等待请求结束后继续执行。数组
实例要求:缓存
1.使用NIO编写服务端,监听8888端口号,当有客户端链接时,启动一个线程与之通讯。2.使用线程池改进,能够链接多个客户端。服务器
package com.crazy.io.bio;import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** @author shiyi* @version 1.0* @date 2020-7-7 23:48*/public class BIOServer { public static void main(String[] args) throws IOException { /** * 1.建立一个线程池 * 若是有客户端链接了,就建立一个线程与之通讯。 */ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); // 建立ServerSocket ServerSocket serverSocket = new ServerSocket(6668); System.out.println("服务器启动了"); while (true) { // 监听,等待客户端链接 final Socket socket = serverSocket.accept(); System.out.println("链接到了一个客户端"); newCachedThreadPool.execute(new Runnable() { @Override public void run() { // 能够和客户端通信 handler(socket); } }); } } // 编写一个handler方法,与客户端通信 public static void handler(Socket socket) { // 经过socket获取输入流 try { System.out.println("线程信息 id=" + Thread.currentThread().getId() + "线程名字=" + Thread.currentThread().getName()); InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; // 循环的读取客户端发送的数据 while (true) { System.out.println("线程信息 id=" + Thread.currentThread().getId() + "线程名字=" + Thread.currentThread().getName()); int read = inputStream.read(bytes); if (read != -1) { // 输出客户端发送的数据 System.out.println(new String(bytes, 0, read)); } else { break; } } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭和客户端的链接 try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } }}
客户端网络
package com.crazy.io.zerocopy;importjava.io.DataOutputStream;importjava.io.FileInputStream;importjava.io.InputStream;importjava.net.Socket; public class OldIOClient { public static void main(String[] args) throws Exception { Socket socket = new Socket("localhost", 6668); String fileName = "1.txt"; InputStream inputStream = new FileInputStream(fileName); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); byte[] buffer = new byte[4096]; long readCount; long total = 0; long startTime = System.currentTimeMillis(); while ((readCount = inputStream.read(buffer)) >= 0) { total += readCount; dataOutputStream.write(buffer); } System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime)); dataOutputStream.close(); socket.close(); inputStream.close(); }}
有小伙伴说,我就只想写服务器,不想写客户端,能不能测试。能,我都给大家准备好了。可使用windows的命令行telnet命令来测试。多线程
telnet 127.0.0.1 6668
链接成功后经过按 Ctrl+] 符号进入发送数据界面架构
send HelloWorld
查看服务端收到的消息
完成了简单的以BIO实现的客户端与服务器之间的交互。
1.每一个请求都须要建立独立的线程。
2.当并发量大时,须要建立大量线程,占用系统资源。
3.链接创建后,若是当前线程暂时没有数据可读,则线程就阻塞在 Read 操做上,形成线程资源浪费
1.Java NIO全称(java non-blocking io),从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的。
2.NIO 相关类都被放在 java.nio 包及子包下,而且对原 http://java.io 包中的不少类进行了改写。
3.NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器) 。
4.NIO是 面向缓冲区。数据读取到一个它稍后处理的缓冲区中,须要时可在缓冲区中先后移动,这就增长了处理过程当中的灵活性,使用它能够提供非阻塞式的高伸缩性网络。
5.Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,可是它仅能获得目前可用的数据,若是目前没有数据可用,就什么都不获取,会继续保持线程阻塞,直至数据变的能够读取以前,该线程能够继续作其余的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不须要等待它彻底写入,这个线程同时能够去作别的事情。
6.通俗理解:NIO是能够作到用一个线程来处理多个操做。假设有500个请求过来,根据实际状况,能够分配50或者100个线程来处理。而BIO可能须要建立500个线程来处理数据。
7.NIO的并发请求要远远大于BIO。
1.每一个channel 都会对应一个Buffer。
2.Selector 对应一个线程, 一个线程对应多个channel(链接)。
3.该图反应了有三个channel 注册到 该selector 程序。
4.程序切换到哪一个channel 是有事件决定的, Event 就是一个重要的概念。
5.Selector 会根据不一样的事件,在各个通道上切换。
6.Buffer 就是一个内存块 , 底层是有一个数组。
7.数据的读取写入是经过Buffer, 这个和BIO , BIO 中要么是输入流,或者是输出流, 不能双向,可是 NIO的Buffer 是能够读也能够写, 须要 flip 方法切换。
8.channel 是双向的, 能够返回底层操做系统的状况, 好比Linux , 底层的操做系统通道就是双向的。
缓冲区本质上是一个能够读写数据的内存块,能够理解成是一个容器对象(含数
组),该对象提供了一组方法,能够更轻松地使用内存块,缓冲区对象内置了一
些机制,可以跟踪和记录缓冲区的状态变化状况。Channel 提供从文件、网络读
取数据的渠道,可是读取或写入的数据都必须经由 Buffer。
经常使用的ByteBuffer是一个抽象类,继承Buffer,实现了Comparable接口。
1. mark:标记。
2. Position:位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写做准备。
3. Limit:表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操做。且极限是能够修改的。
4. Capacity:容量,便可以容纳的最大数据量;在缓冲区建立时被设定而且不能改变。
ByteBuffer中经常使用的方法:
// 缓冲区建立相关apipublic static ByteBuffer allocateDirect(int capacity)//建立直接缓冲区public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用//构造初始化位置offset和上界length的缓冲区public static ByteBuffer wrap(byte[] array,int offset, int length)//缓存区存取相关APIpublic abstract byte get( );//从当前位置position上get,get以后,position会自动+1public abstract byte get (int index);//从绝对位置getpublic abstract ByteBuffer put (byte b);//从当前位置上添加,put以后,position会自动+1public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
1.通道能够同时进行读写,而流只能读或者只能写。
2.通道能够实现异步读写数据。
3.通道能够从缓冲读数据,也能够写数据到缓冲。
Channel是一个接口。
1.经常使用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。
2.ServerSocketChanne 相似 ServerSocket 。
3.SocketChannel 相似 Socket。
实例要求:
1.把1.txt中的文件读取到2.txt中。
public class NIOFileChannel03 { public static void main(String[] args) throws IOException { FileInputStream fileInputStream = newFileInputStream("1.txt"); FileChannel inputStreamChannel =fileInputStream.getChannel(); FileOutputStream fileOutputStream = newFileOutputStream("2.txt"); FileChannel outputStreamChannel =fileOutputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(10); while (true) { byteBuffer.clear(); int read = inputStreamChannel.read(byteBuffer); System.out.println("read =" + read); if (read == -1) { break; } byteBuffer.flip(); outputStreamChannel.write(byteBuffer); } fileInputStream.close(); inputStreamChannel.close(); outputStreamChannel.close(); fileOutputStream.close(); }}
1.ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,不然可能有 BufferUnderflowException 异常。
2.能够将一个普通Buffer 转成只读Buffer。
3.NIO 还提供了 MappedByteBuffer, 可让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成。
1.Java 的 NIO,用非阻塞的 IO 方式。能够用一个线程,处理多个的客户端链接,就会使用到Selector(选择器)。2.Selector 可以检测多个注册的通道上是否有事件发生,若是有事件发生,便获取事件而后针对每一个事件进行相应的处理。这样就能够只用一个单线程去管理多个通道,也就是管理多个链接和请求。3.只有在 链接/通道 真正有读写事件发生时,才会进行读写,就大大地减小了系统开销,而且没必要为每一个链接都建立一个线程,不用去维护多个线程。4.避免了多线程之间的上下文切换致使的开销。
1.线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程能够进行其余任务。
2.线程一般将非阻塞 IO 的空闲时间用于在其余通道上执行 IO 操做,因此单独的线程能够管理多个输入和输出通道。
3.因为读写操做都是非阻塞的,这就能够充分提高 IO 线程的运行效率,避免因为频繁 I/O 阻塞致使的线程挂起。
4.一个 I/O 线程能够并发处理 多个客户端链接和读写操做,这从根本上解决了传统同步阻塞 I/O 一个链接一个线程模型,架构的性能、弹性伸缩能力和可靠性都获得了极大的提高。
5.客户端链接时,会经过ServerSocketChannel 获得 SocketChannel Selector 进行监听 select 方法, 返回有事件发生的通道的个数。
6.将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上能够注册多个SocketChannel。
7.注册后返回一个 SelectionKey, 会和该Selector 关联(集合) 进一步获得各个 SelectionKey。
8.在经过 SelectionKey 反向获取 SocketChannel , 方法 channel() 能够经过 获得的 channel , 完成业务处理。
1.编写一个 NIO 入门案例,实现服务器端和客户端之间的数据简单通信(非阻塞)。
package com.crazy.io.nio.buffer; 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.Iterator; import java.util.Set; /** * @author shiyi * @version 1.0 * @date 2020-7-8 22:32 */ public class NIOServer { public static void main(String[] args) throws IOException { // 建立serverSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 建立selector Selector selector = Selector.open(); // 绑定一个端口6666.在服务器监听 serverSocketChannel.socket().bind(new InetSocketAddress(6666)); // 设置为非阻塞 serverSocketChannel.configureBlocking(false); // 把serverSocketChannel注册到selector 关心事件为OP_ACCEPT serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 循环等待客户端链接 while (true) { // 一秒没有事件发生,没有事件发生 if (selector.select(1000) == 0) { System.out.println("等待了一秒,无链接"); continue; } // 若是返回的大于0,拿到selectionkey集合 // 若是大于0,表示已回去到关注的事件 // 经过selectionkeys反向获取通道 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); // 根据key对应的通道发生的事件作相应处理 // 有新的客户端来链接了 if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); // 将socketChannel设置为非阻塞 socketChannel.configureBlocking(false); System.out.println("客户端链接成功======"); // 注册到selector上, 关注事件为读,给socketChannel关联一个Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (key.isReadable()) { // 经过key 反向获取对应的channel SocketChannel channel = (SocketChannel) key.channel(); // 获取到管理的Buffer ByteBuffer buffer = (ByteBuffer) key.attachment(); channel.read(buffer); System.out.println("form客户端" + new String(buffer.array())); } keyIterator.remove(); } } } }
客户端
package com.crazy.io.nio.buffer;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;/** * @author shiyi * @version 1.0 * @date 2020-7-8 23:00 */public class NIOClient { public static void main(String[] args) throws IOException { // 等到通道 SocketChannel socketChannel = SocketChannel.open(); // 设置非阻塞 socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666); // 链接服务器 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { System.out.println("没有链接上,能够作其它事情"); } } // 链接成功 String hello = "失忆老幺"; ByteBuffer buffer = ByteBuffer.wrap(hello.getBytes()); // 发送数据,将buffer数据写到channel socketChannel.write(buffer); System.in.read(); }}
1.目前 AIO 尚未普遍应用。实际我也不懂。
原做者:失忆老幺
原文连接: BIO,NIO,AIO_失忆老幺-CSDN博客
原出处:CSDN博客
侵删