02 Java网络编程和BIO、NIO、AIO网络编程三种模型

1 什么是socket

Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,一般由操作系统提供。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议处理和通信缓存管理等等都隐藏在Socket 接口后面,对用户来说,使用一组简单的接口就能进行网络应用编程,让Socket 去组织数据,以符合指定的协议。主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接。客户端连接上一个服务端,就会在客户端中产生一个 socket 接口实例,服务端每接受一个客户端连接,就会产生一个 socket 接口实例和客户端的 socket 进行通信,有多个客户端连接自然就有多个 socket 接口实例。

2 三种I/O模型概述

BIO:同步阻塞

NIO:同步非阻塞

AIO:异步非阻塞

3 JAVA BIO基本介绍

1) Java BIO 就是传统的java io 编程,其相关的类和接口在 java.io

2) BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。

3) BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解

工作原理

代码示例server端

public class BioClientSocketDemo {
    public static void main(String[] args) throws Exception{
        //创建一个Socket,跟服务器的9999端口链接
        Socket socket = new Socket("127.0.0.1",9999);
        //使用PrintWriter和BufferedReader进行读写数据
        PrintWriter pw = new PrintWriter(socket.getOutputStream());
        BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //发送数据
        pw.println("hello bio");
        pw.flush();
        /************************************这里其实堵塞了****************************/
        //接收数据
        String line = is.readLine();
        System.out.println("received from server:" + line);
        //关闭资源
        pw.close();
        is.close();
        socket.close();
    }
}

代码示例client端

public class BioServerSocketDemo {
      public static void main(String[] args) throws Exception{
          //创建一个线程池
          ExecutorService executorService = Executors.newCachedThreadPool();
          //创建ServerSocket
          ServerSocket serverSocket = new ServerSocket(9999);
          System.out.println("服务启动了");
          //持续监听
          while (true){
              System.out.println("主线程信息"+Thread.currentThread().getName());
              //阻塞了
              final Socket socket = serverSocket.accept();
              System.out.println("连接到一个客户端了");
              //如果有客户端连接就创建一个线程与之通讯
              executorService.execute(new Runnable() {
                  @Override
                  public void run() {
                      //通讯的方法
                      handler(socket);
                  }
              });
          }
      }

      public static void handler(Socket socket){
          try {
              System.out.println("任务线程信息"+Thread.currentThread().getName());
              BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
              String line = is.readLine();
              System.out.println("received frome client:" + line);
              //创建PrintWriter,用于发送数据
              PrintWriter pw = new PrintWriter(socket.getOutputStream());
              pw.println("hello bio");
              pw.flush();
          }catch (Exception e){
              e.printStackTrace();
          }finally {
              try {
                  socket.close();
              }catch (Exception e){
                  e.printStackTrace();
              }
          }
      }
}

4 JAVA NIO基本介绍

1) Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞

2) NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io包中的很多类进行改写。

3) NIO 有三大核心部分:Channel(通道)Buffer(缓冲区), Selector(选择器)

4) NIO是 面向缓冲区 ,或者面向块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络

5) Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。【后面有案例说明

6) 通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来, 根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。

7) HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求

的数量比HTTP1.1大了好几个数量级。

NIO和BIO区别

1) BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多

2) BIO 是阻塞的,NIO 则是非阻塞的

3) BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

工作原理

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 , 底层的操作系统

通道就是双向的.

代码示例server端

public class NioServer  {
    public static void main(String[] args) throws Exception{
        //得到ServerSocketChannel对象,可以理解为serverSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到一个Selector
        Selector selector = Selector.open();
        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //注册selector,设置关心事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            if(selector.select(20000)==0){
                System.out.println("服务器等待20S");
                continue;
            }
            //关注事件的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if(key.isAcceptable()){
                    //如果是OP_ACCEPT,有新的客户端进来,获得一个SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置为非阻塞模式
                    socketChannel.configureBlocking(false);
                    //关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if(key.isReadable()){ //发生OP_READ
                    //通过key反向获取对应channel
                    SocketChannel channel = (SocketChannel)key.channel();
                    //获得该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.println("从客户端读取到了消息"+new String(buffer.array()));
                }
                //手动从集合中移动当前的selectionKey,防止重复操作
                iterator.remove();
            }
        }
    }
}

代码示例client端

public class NioClient {
    public static void main(String[] args) throws Exception{
        //获得一个SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //提供服务端ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
        //连接服务器
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                Thread.sleep(1000);
                System.out.println("因为连接需要事件,客户端不会阻塞,可以做其他工作");
            }
        }
        //如果连接成功就发送数据
        String str = "hello nioServer";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将buffer数据写入channel
        socketChannel.write(buffer);
        System.in.read();
    }
}

5 JAVA NIO基本使用

因为笔者前面写过详细的NIO教程,在这里简单提供几份代码示例

NIO读

public class NioReadTest {
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("D:\\test-document\\1.txt");
        FileChannel channel = fileInputStream.getChannel();
        ByteBuffer bytebuffer = ByteBuffer.allocate(1024);
        channel.read(bytebuffer);
        System.out.println(new String(bytebuffer.array()));
        fileInputStream.close();
    }
}

NIO写

public class NioWriteTest {
    public static void main(String[] args) throws Exception{
        String str = "hello nio";
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test-document\\1.txt");
        FileChannel channel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(str.getBytes());
        byteBuffer.flip();
        channel.write(byteBuffer);
        fileOutputStream.close();
    }
}

NIO拷贝文件

public class NioCopyTest {
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("D:\\test-document\\1.webp");
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test-document\\2.webp");
        FileChannel inputChannel = fileInputStream.getChannel();
        FileChannel outputChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while (inputChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            outputChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

NIO直接内存读取(mmap)

public class NioDirectTest {
    public static void main(String[] args) throws Exception{
        long start = System.currentTimeMillis();
        //获取通道
        FileChannel inChannel = FileChannel.open(Paths.get("d:\\test-document\\1.webp"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("d:\\test-document\\4.webp"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
        //内存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
        //直接对缓冲区进行数据的读写操作
        byte[] dst = new byte[inMappedBuf.limit()];
        //文件写入到dst中
        inMappedBuf.get(dst);
        //dst写进到文件里
        outMappedBuf.put(dst);
        //关闭通道
        inChannel.close();
        outChannel.close();

        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start));
    }
}

NIO零拷贝

public class NioTransferTest {
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("D:\\test-document\\1.webp");
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test-document\\3.webp");
        FileChannel inputChannel = fileInputStream.getChannel();
        FileChannel outputChannel = fileOutputStream.getChannel();
        outputChannel.transferFrom(inputChannel,0,inputChannel.size());
        fileInputStream.close();
        fileOutputStream.close();
    }
}

6 直接内存深入辨析

在所有的网络通信和应用程序中,每个 TCP 的 Socket 的内核中都有一个发送缓冲区 (SO_SNDBUF)和一个接收缓冲区(SO_RECVBUF),可以使用相关套接字选项来更改该缓冲区大小。

当某个应用进程调用 write 时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),假设该套接字是阻塞的,则该应用进程将被投入睡眠。

内核将不从 write 系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个 TCP 套接字的 write 调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的 TCP 或应用进程已接收到数据。

Java 程序自然也要遵守上述的规则。但在 Java 中存在着堆、垃圾回收等特性,所以在实际的 IO 中,在 JVM 内部的存在着这样一种机制:

在 IO 读写上,如果是使用堆内存,JDK 会先创建一个 DirectBuffer,再去执行真正的写操作。这是因为,当我们把一个地址通过 JNI 传递给底层的 C 库的时候,有一个基本的要求,就是这个地址上的内容不能失效。然而,在 GC 管理下的对象是会在 Java 堆中移动的。也就是说,有可能我把一个地址传给底层的 write,但是这段内存却因为 GC 整理内存而失效了。所以必须要把待发送的数据放到一个 GC 管不着的地方。这就是调用 native 方法之前,数据—定要在堆外内存的原因。

可见,站在网络通信的角DirectBuffer 并没有节省什么内存拷贝,只是 Java 网络通信里因为 HeapBuffer 必须多做一次拷贝,使用DirectBuffer 就会少一次内存拷贝。相比没有使用堆内存的 Java 程序,使用直接内存的 Java 程序当然更快一点。

从垃圾回收的角度而言,直接内存不受 GC(新生代的 Minor GC) 影响,只有当执行老年代的 Full GC 时候才会顺便回收直接内存,整理内存的压力也比数据放到 HeapBuffer 要小。

堆外内存的优点和缺点

堆外内存相比于堆内内存有几个优势:

1 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间

片的方式,根本感觉不到)

2 加快了复制的速度。因为堆内在 flush 到远程时,会先复制到直接内存(非堆内存),

然后在发送;而堆外内存相当于省略掉了这个工作。

而福之祸所依,自然也有不好的一面:

1 堆外内存难以控制,如果内存泄漏,那么很难排查

2 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。

7 零拷贝

什么是零拷贝?

零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU 不需要先将数据从某处内存

复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。

➢零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率

➢零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上:下文切换而带来的开销

可以看出没有说不需要拷贝,只是说减少冗余[不必要]的拷贝。

下面这些组件、框架中均使用了零拷贝技术:Kafka、Netty、Rocketmq、Nginx、Apache。

Linux 的 I/O 机制与 DMA

在早期计算机中,用户进程需要读取磁盘数据,需要 CPU 中断和 CPU 参与,因此效率比较低,发起 IO 请求,每次的 IO 中断,都带来 CPU 的上下文切换。因此出现了——DMA。

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。

DMA 控制器,接管了数据读写请求,减少 CPU 的负担。这样一来,CPU 能高效工作了。 现代硬盘基本都支持 DMA。

实际因此 IO 读取,涉及两个过程:

1、DMA 等待数据准备好,把磁盘数据读取到操作系统内核缓冲区;

2、用户进程,将内核缓冲区的数据 copy 到用户空间。

传统数据传送机制

比如:读取文件,再用 socket 发送出去,实际经过四次 copy。

伪码实现如下:

buffer = File.read()

Socket.send(buffer)

1、第一次:将磁盘文件,读取到操作系统内核缓冲区;

2、第二次:将内核缓冲区的数据,copy 到应用程序的 buffer;

3、第三步:将 application 应用程序 buffer 中的数据,copy 到 socket 网络发送缓冲区(属

于操作系统内核的缓冲区);

4、第四次:将 socket buffer 的数据,copy 到网卡,由网卡进行网络传输。

分析上述的过程,虽然引入 DMA 来接管 CPU 的中断请求,但四次 copy 是存在“不必要的拷贝”的。实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。显然,第二次和第三次数据 copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的背景和意义。

同时,read 和 send 都属于系统调用,每次调用都牵涉到两次上下文切换:

总结下,传统的数据传送所消耗的成本:4 次拷贝,4 次上下文切换。

4 次拷贝,其中两次是 DMA copy,两次是 CPU copy。

Linux 支持的(常见)零拷贝

目的:减少 IO 流程中不必要的拷贝,当然零拷贝需要 OS 支持,也就是需要 kernel 暴露 api。

mmap 内存映射

硬盘上文件的位置和应用程序缓冲区(application buffers)进行映射(建立一种一一对应关系),由于 mmap()将文件直接映射到用户空间,所以实际文件读取时根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝,不再有文件内容从硬盘拷贝到内核空间的一个缓冲区。

mmap 内存映射将会经历:3 次拷贝: 1 次 cpu copy,2 次 DMA copy;

以及 4 次上下文切换,调用 mmap 函数 2 次,write 函数 2 次。

sendfile

linux 2.1 支持的 sendfile

当调用 sendfile()时,DMA 将磁盘数据复制到 kernel buffer,然后将内核中的 kernel buffer

直接拷贝到 socket buffer;但是数据并未被真正复制到 socket 关联的缓冲区内。取而代之的

是,只有记录数据位置和长度的描述符被加入到 socket 缓冲区中。DMA 模块将数据直接从

内核缓冲区传递给协议引擎,从而消除了遗留的最后一次复制。但是要注意,这个需要 DMA

硬件设备支持,如果不支持,CPU 就必须介入进行拷贝。

一旦数据全都拷贝到 socket buffer,sendfile()系统调用将会 return、代表数据转化的完

成。socket buffer 里的数据就能在网络传输了。

sendfile 会经历:3(2,如果硬件设备支持)次拷贝,1(0,,如果硬件设备支持)次

CPU copy, 2 次 DMA copy;

以及 2 次上下文切换

splice

Linux 从 2.6.17 支持 splice

数据从磁盘读取到 OS 内核缓冲区后,在内核缓冲区直接可将其转成内核空间其他数据

buffer,而不需要拷贝到用户空间。

如下图所示,从磁盘读取到内核 buffer 后,在内核空间直接与 socket buffer 建立 pipe

管道。

和 sendfile()不同的是,splice()不需要硬件支持。

注意 splice 和 sendfile 的不同,sendfile 是 DMA 硬件设备不支持的情况下将磁盘数据加

载到 kernel buffer 后,需要一次 CPU copy,拷贝到 socket buffer。而 splice 是更进一步,连

这个 CPU copy 也不需要了,直接将两个内核空间的 buffer 进行 pipe。

splice 会经历 2 次拷贝: 0 次 cpu copy 2 次 DMA copy;

以及 2 次上下文切换

总结 Linux 中零拷贝

最早的零拷贝定义,来源于

Linux 2.4 内核新增 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA 拷贝到内核

Buffer 后,直接通过 DMA 拷贝到 NIO Buffer(socket buffer),无需 CPU 拷贝。这也是零

拷贝这一说法的来源。这是真正操作系统 意义上的零拷贝(也就是狭义零拷贝)

随着发展,零拷贝的概念得到了延伸,就是目前的减少不必要的数据拷贝都算作零拷贝

的范畴。

Java 生态圈中的零拷贝

Linux 提供的零拷贝技术 Java 并不是全支持,支持 2 种(内存映射 mmap、sendfile);

NIO 提供的内存映射 MappedByteBuffer

NIO 中的 FileChannel.map()方法其实就是采用了操作系统中的内存映射方式,底层就是

调用 Linux mmap()实现的。

将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射。这种方式适合读取大文件,

同时也能对文件内容进行更改,但是如果其后要通过 SocketChannel 发送,还是需要 CPU 进

行数据的拷贝。

NIO 提供的 sendfile

Java NIO 中提供的 FileChannel 拥有 transferTo 和 transferFrom 两个方法,可直接把

FileChannel 中的数据拷贝到另外一个 Channel,或者直接把另外一个 Channel 中的数据拷

贝到 FileChannel。该接口常被用于高效的网络 / 文件的数据传输和大文件拷贝。在操作系

统支持的情况下,通过该方法传输数据并不需要将源数据从内核态拷贝到用户态,再从用户

态拷贝到目标通道的内核态,同时也避免了两次用户态和内核态间的上下文切换,也即使用

了“零拷贝”,所以其性能一般高于 Java IO 中提供的方法。

Kafka 中的零拷贝

Kafka 两个重要过程都使用了零拷贝技术,且都是操作系统层面的狭义零拷贝,一是

Producer 生产的数据存到 broker,二是 Consumer 从 broker 读取数据。

Producer 生产的数据持久化到 broker,broker 里采用 mmap 文件映射,实现顺序的快速

写入;

Customer 从 broker 读取数据,broker 里采用 sendfile,将磁盘文件读到 OS 内核缓冲区

后,直接转到 socket buffer 进行网络发送。

Netty 的零拷贝实现

Netty 的零拷贝主要包含三个方面:

在网络通信上,Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接

内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP

BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写

入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

在缓存操作上,Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个

逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。

通过 wrap 操作,我们可以将 byte[]数组、ByteBuf、 ByteBuffer 等包装成一个 Netty

ByteBuf 对象,进而避免了拷贝操作。

ByteBuf支持slice 操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,

避免了内存的拷贝。

在文件传输上,Netty 的通过 FileRegion 包装的 FileChannel.tranferTo 实现文件传输,它

可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的

内存拷贝问题。

8 网络中文件的普通拷贝和零拷贝

需求 客户端发送图片服务端接收图片并保存在服务端本地

普通代码示例server端

public class S {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(true);
        serverSocketChannel.socket().bind(new InetSocketAddress(12345));
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test-document\\666.webp");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            while (socketChannel.read(byteBuffer)!=-1){
                byteBuffer.flip();
                outputStreamChannel.write(byteBuffer);
                byteBuffer.clear();
            }
        }
    }
}

普通代码示例client端

public class C {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1",12345));
        FileInputStream fileInputStream = new FileInputStream("D:\\test-document\\1.webp");
        FileChannel channel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while(channel.read(byteBuffer) != -1){
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }
    }
}

零拷贝代码示例server端

public class ZoneCopyServer {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(true);
        serverSocketChannel.socket().bind(new InetSocketAddress(7777));
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            System.out.println("有链接进来了");
            int readCount = 0;
            while (-1 != readCount){
                try {
                    readCount = socketChannel.read(byteBuffer);
                    System.out.println(new String(byteBuffer.array()));
                }catch (Exception e){
                    e.printStackTrace();
                }
                byteBuffer.rewind();
            }
        }
    }
}

零拷贝代码示例client端

public class ZoneCopyClient {
    public static void main(String[] args) throws Exception{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(true);
        socketChannel.connect(new InetSocketAddress("127.0.0.1",7777));
        FileInputStream fileInputStream = new FileInputStream("D:\\test-document\\1.webp");
        FileChannel channel = fileInputStream.getChannel();
        channel.transferTo(0,channel.size(),socketChannel);
        fileInputStream.close();
        channel.close();
        socketChannel.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值