1)BIO-Blocking IO:同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理;
2)原理图如下:
【图解】
【代码实现】
- /**
- * @Description 阻塞式IO服务器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月13日
- */
- public class BIOServer {
- public static void main(String[] args) throws IOException {
- // 创建一个线程池
- ExecutorService threadPool = Executors.newCachedThreadPool();
- int order = 0;
-
- // 创建 服务器 套接字
- ServerSocket serverSocket = new ServerSocket(6666);
- System.out.println("服务器启动成功.");
- while(true) {
- System.out.println("等待客户端请求");
- Socket socket = serverSocket.accept(); // 没有客户端请求,accept阻塞
- System.out.printf("客户端[%d]请求建立连接\n", ++order);
- final int orderCopy = order;
- threadPool.execute(()->{
- handler(socket, orderCopy);
- });
- }
- }
-
- /**
- * @description 与客户端通讯
- * @param socket 连接套接字
- * @author xiao tang
- * @date 2022/8/13
- */
- public static void handler(Socket socket, int order) {
- byte[] byteArr = new byte[1024];
-
- try {
- // 读取客户端发送的数据
- InputStream inputStream = socket.getInputStream();
- int length = 0;
- // 客户端没有数据,read阻塞
- // while( ( length = inputStream.read(byteArr))!= -1) {
- // System.out.println(new String(byteArr, 0, length));
- // }
- while(true) {
- System.out.printf("线程id[%s],等待客户端[%d]发送数据\n", Thread.currentThread().getId(), order);
- length = inputStream.read(byteArr);
- if (length == -1) break;
- System.out.printf("线程id[%s],客户端[%d]:" + new String(byteArr, 0, length) + "\n", Thread.currentThread().getId(), order);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 关闭与客户端的连接
- try {
- socket.close();
- System.out.printf("关闭与客户端[%d]的连接\n", order);
- } catch (IOException e) {
- }
- }
- }
- }
5)NIO非阻塞IO处理架构:
【图解】
【小结】
【比较】NIO 与 BIO的比较
补充:NIO中,Selector(选择器) 用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道;
1)NIO 有三大核心部分: Buffer(缓冲区), Channel(通道),Selector(选择器) ;
2)3个模块关联关系如下:
【图解】
1)缓冲区(Buffer):
2)Channel 提供读写文件的接口、都必须经由Buffer,如下图所示。
参见 FileChannel读写文件代码( NIOFileChannel015.java ,下文有)
【代码】NIO中缓冲区的使用
- /**
- * @Description 缓冲区使用
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月13日
- */
- public class BasicBuffer {
-
- /**
- * @description Buffer的使用
- */
- public static void main(String[] args) {
- // 创建一个buffer
- IntBuffer intBuffer = IntBuffer.allocate(5);
- // 向buffer存放数据
- for (int i = 0; i < intBuffer.capacity(); i++) {
- intBuffer.put(i*2);
- }
- // 从buffer 读取数据
- // 需要先把 buffer 转换,读写切换(写模式切换到读模式)
- intBuffer.flip();
-
- intBuffer.position(1);
- while(intBuffer.hasRemaining()) {
- System.out.println(intBuffer.get());
- }
-
- }
- }
1)子类
【图解】
2)buffer的4个属性
- public abstract class Buffer {
- private int mark = -1;
- private int position = 0;
- private int limit;
- private int capacity;
序号 | 属性 | 描述 |
1 | Capacity | 容量,即缓冲区大小,定长不能改变。 |
2 | Position | 当前位置;表示下一个要被读写的元素的下标(索引) |
3 | limit | 表示缓冲区最大可用位置,读写时不能超过极限位置。 |
4 | Mark | 标记,不修改。 |
- /**
- * @Description 只读 buffer
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOReadOnly017 {
- public static void main(String[] args) {
- ByteBuffer buffer = ByteBuffer.allocate(64);
-
- for (int i = 0; i < 4; i++) {
- buffer.put((byte) i);
- }
- // 读取
- buffer.flip();
- // 得到一个只读buffer
- ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
- System.out.println(readOnlyBuffer.getClass()); // java.nio.HeapByteBufferR
-
- // 读取
- while (readOnlyBuffer.hasRemaining()) {
- System.out.println(readOnlyBuffer.get());
- }
- readOnlyBuffer.flip();
- readOnlyBuffer.put((byte)0); // 抛出异常-ReadOnlyBufferException
- }
- }
buffer缓冲中 get与put() 方法操作 数据类型要一致 :
- /**
- * @Description buffer put 与 get 操作的数据类型要一致
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOByteBufferPugGet017 {
- public static void main(String[] args) {
- ByteBuffer buffer = ByteBuffer.allocate(64);
-
- // 类型化方式放入数据
- buffer.putInt(100);
- buffer.putLong(4);
- buffer.putChar('中');
- buffer.putShort((short)4);
-
- // 取出
- buffer.flip();
-
- System.out.println();
- System.out.println(buffer.getInt());
- System.out.println(buffer.getChar());
- System.out.println(buffer.getLong());
- System.out.println(buffer.getLong()); // 抛出异常
- }
- }
前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO还支持通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和Gathering;
2)buffer的分散与聚集( Scattering 与 Gathering )
- /**
- * @Description buffer的分散与聚集( Scattering 与 Gathering )
- * Scattering: 将数据写入到buffer时,可以采用buffer数组,依次写入。
- * Gathering: 从buffer读取数据时, 可以采用buffer数组,依次读取。
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class ScatterAndGatherBuffer019 {
- public static void main(String[] args) throws Exception {
- // 使用 ServerSocketChannel 和 SocketChannel
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
-
- // 绑定端口到socket 并启动
- serverSocketChannel.socket().bind(inetSocketAddress);
-
- // 创建buffer数组
- ByteBuffer[] byteBuffers = new ByteBuffer[2];
- byteBuffers[0] = ByteBuffer.allocate(5);
- byteBuffers[1] = ByteBuffer.allocate(3);
- // 等待客户端连接(telnet)
- SocketChannel socketChannel = serverSocketChannel.accept();
-
- // 循环读取
- int msgLength = 8; // 假定从客户端接收8个字节
- int byteRead = 0;
- while (byteRead < msgLength) {
- long singleLength = socketChannel.read(byteBuffers);
- byteRead += singleLength;
- System.out.println("byteRead = " + byteRead);
- // 流打印,查看当前buffer的position 和 limit
- Arrays.asList(byteBuffers).stream()
- .map(buffer -> "position = " + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);
- }
- // 将所有buffer反转-flip
- Arrays.asList(byteBuffers).forEach(x -> x.flip());
- // 将数据读出显示到客户端
- long byteWrite = 0;
- while (byteWrite < msgLength) {
- long length = socketChannel.write(byteBuffers);
- byteWrite += length;
- }
- // 将所有buffer 清空
- Arrays.asList(byteBuffers).forEach(x -> x.clear());
- System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWrite);
- }
- }
1)写出到文件
- /**
- * @Description nio文件通道读文件
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOFileChannel013 {
- public static void main(String[] args) throws Exception {
- String text = "hello 世界";
- // 创建一个输出流
- try (FileOutputStream fos = new FileOutputStream(
- new File("D://temp/netty/nio_file_channel01.txt"))) {
- // 获取对应 FileChannel,FileChannel 是抽象类,具体类型是FileChannelImpl
- FileChannel fileChannel = fos.getChannel();
- // 创建一个缓冲区
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- // 把文本写入 ByteBuffer, 并把 ByteBuffer 反转-flip
- byteBuffer.put(text.getBytes(StandardCharsets.UTF_8));
- byteBuffer.flip();
- // 把 byteBuffer 写入到 FileChannel
- fileChannel.write(byteBuffer);
- } catch (Exception e) {
- System.out.println("异常");
- throw e;
- }
- }
- }
2)从文件读入数据
- /**
- * @Description FileChannel读文件
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOFileChannel014 {
- public static void main(String[] args) throws Exception {
- // 创建一个输入流
- try (FileInputStream fis = new FileInputStream(
- new File("D://temp/netty/nio_file_channel01.txt"))) {
- // 获取对应 FileChannel,FileChannel 是抽象类,具体类型是FileChannelImpl
- FileChannel fileChannel = fis.getChannel();
- // 创建一个缓冲区
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- // 从文件读入内容到缓冲区
- fileChannel.read(byteBuffer);
- // 显示
- System.out.println(new String(byteBuffer.array(), StandardCharsets.UTF_8));
- } catch (Exception e) {
- System.out.println("异常");
- throw e;
- }
- }
- }
3)读写文件(拷贝)
- /**
- * @Description FileChannel读写文件
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOFileChannel015 {
- public static void main(String[] args) throws Exception {
- // 创建一个输入流
- try (FileInputStream fis = new FileInputStream("D://temp/netty/nio_file_channel01.txt");
- FileOutputStream fos = new FileOutputStream("D://temp/netty/nio_file_channel02.txt")) {
- // 获取对应 FileChannel
- FileChannel fileChannel01 = fis.getChannel();
- FileChannel fileChannel02 = fos.getChannel();
-
- // 创建一个缓冲区
- ByteBuffer byteBuffer = ByteBuffer.allocate(4);
- // 循环读取
- while(fileChannel01.read(byteBuffer) != -1) {
- // 反转
- byteBuffer.flip();
- // 将buffer 中的数据写入到 02.txt
- fileChannel02.write(byteBuffer);
- // 清空 buffer
- byteBuffer.clear();
- }
- // 显示
- System.out.println("拷贝成功.");
- } catch (Exception e) {
- System.out.println("异常");
- throw e;
- }
- }
- }
补充: Buffer.flip() 切换读写模式(如写模式切换为读模式)
4)用 transferFrom 拷贝文件
- /**
- * @Description 采用 transferFrom 拷贝图片
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月16日
- */
- public class NIOFileChannel016 {
- public static void main(String[] args) throws Exception {
- // 创建一个输入流
- try (FileInputStream fis = new FileInputStream("D://temp/netty/image/1.jpg");
- FileOutputStream fos = new FileOutputStream("D://temp/netty/image/2.jpg")) {
- // 获取对应 FileChannel
- FileChannel srcChannel = fis.getChannel();
- FileChannel destChannel = fos.getChannel();
-
- // 创建一个缓冲区
- ByteBuffer byteBuffer = ByteBuffer.allocate(4);
- // 使用 transferFrom 完成拷贝
- destChannel.transferFrom(srcChannel, 0, srcChannel.size());
- // 显示
- System.out.println("拷贝成功.");
- } catch (Exception e) {
- System.out.println("异常");
- throw e;
- }
- }
- }
refer2