zoukankan      html  css  js  c++  java
  • Java文件之NIO核心组件之二通道

    一 Channel(通道)介绍

    通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

    • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

    • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

    数据读取和写入操作图示:

    Java NIO Channel通道和流非常相似,主要有以下几点区别:

    • 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。

    • 通道可以异步读写。

    • 通道总是基于缓冲区Buffer来读写

    在Java NIO中,主要使用的通道如下(都是抽象类,涵盖了UDP 和 TCP 网络IO,以及文件IO):

    • FileChannel: 用于文件的数据读写

    • DatagramChannel: 用于UDP的数据读写

    • SocketChannel: 用于TCP的数据读写,一般是客户端实现

    • ServerSocketChannel: 允许我们监听TCP连接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

    二 FileChannel的使用

    使用FileChannel读取数据到Buffer(缓冲区)以及利用Buffer(缓冲区)写入数据到FileChannel:

    package filechannel;
      import java.io.IOException;
      import java.io.RandomAccessFile;
      import java.nio.ByteBuffer;
      import java.nio.channels.FileChannel;
      public class FileChannelTxt{
          public static void main(String args[]) throws IOException{
              // 1.创建一个RandomAccessFile(随机访问文件)对象,
              RandomAccessFile raf = new RandomAccessFile("D:\niodata.txt","rw");
              // 通过RandomAccessFile对象的getChannel()方法,获取FileChannel
              FileChannel inChannel = raf.getChannel();
              // 2.创建一个读数据缓冲区对象,allocate表示分配非直接缓冲区,建立在JVM的内存
              ByteBuffer buf = ByteBuffer.allocate(48);
              // 3.从通道中读取缓冲区的字节数据
              int bytesRead = inChannel.read(buf);
              // 创建一个写数据缓冲区对象
              ByteBuffer buf2 = ByteBuffer.allocate(48);
              // 写入数据
              buf2.put("filechannel test".getBytes());
              
              //flip()方法可以把Buffer从写模式切换到读模式。调用flip方法会把position归零,并设置limit为之前的position的值。 也就是说,现在position代表的是读取位置,limit标示的是已写入的数据位置,将读写的一些东西互换
              buf2.flip();
              inChannel.write(buf);
              //从同道中读取的数据不为1的时候
              while(bytesRead !=-1){
                  System.out.println("Read "+ bytesRead);
          // Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。
                  buf.flip();
                  // 如果还有未读内容
                  while(buf.hasRemaining()){
                      System.out.print((char) buf.get());
                  }
                  // 清空缓存区
                  buf.clear();
                  bytesRead = inChannel.read(buf);
              }
              // 关闭RandomAccessFile(随机访问文件)对象
              raf.close();
          }
      }

    通过上述实例代码,我们可以大概总结出FileChannel的一般使用规则:

    1. 开启FileChannel

    使用之前,FileChannel必须被打开 ,但是你无法直接打开FileChannel(FileChannel是抽象类)。需要通过 FileInputStreamFileOutputStreamRandomAccessFile的getChannel方法 获取FileChannel。

    上面的例子是通过RandomAccessFile打开FileChannel的:

     //1.创建一个RandomAccessFile(随机访问文件)对象,        
              RandomAccessFile raf=new RandomAccessFile("D:\niodata.txt", "rw");        
      //通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。       
              FileChannel inChannel=raf.getChannel();

    2. 从FileChannel读取数据/写入数据

    从FileChannel中读取数据/写入数据之前首先要创建一个Buffer(缓冲区)对象

    使用FileChannel的read()方法读取数据:

             //2.创建一个读数据缓冲区对象        
              ByteBuffer buf=ByteBuffer.allocate(48);        
              //3.从通道中读取数据        
              int bytesRead = inChannel.read(buf);

    使用FileChannel的write()方法写入数据:

              //创建一个写数据缓冲区对象        
              ByteBuffer buf2=ByteBuffer.allocate(48);        
              //写入数据        
              buf2.put("filechannel test".getBytes());        
              buf2.flip();        
              inChannel.write(buf);

    3. 关闭FileChannel

    完成使用后,FileChannel您必须关闭它。

      channel.close();    
    

    三 SocketChannel和ServerSocketChannel的使用

    利用SocketChannel和ServerSocketChannel实现客户端与服务器端简单通信:一个是客户端,一个是服务器端

    SocketChannel 用于创建基于tcp协议的客户端对象,因为SocketChannel中不存在accept()方法,所以,它不能成为一个服务端程序。通过 connect()方法 ,SocketChannel对象可以连接到其他tcp服务器程序。

    客户端:

    package socketchannel;
      import java.io.IOException;
      import java.net.InetSocketAddress;
      import java.nio.ByteBuffer;
      import java.nio.channels.SocketChannel;
      public class WebClient{
          public static void main(String[] args) throws IOException{
              // 1.通过SocketChannel的open()方法创建一个SocketChannel对象
              SocketChannel socketChannel = SocketChannel.open();
              // 2.连接到远程服务器(连接此通道的socket)
              socketChannel.connect(new InetSocketAddress("127.0.0.1",3333));
              // 3.创建写数据缓存区对象
              ByteBuffer writeBuffer = ByteBuffer.allocate(128);
              writeBuffer.put("hello WebServer this is from WebClient".getBytes());
              writeBuffer.flip();
              socketChannel.write(writeBuffer);
              // 创建读数据缓存区对象
              ByteBuffer readBuffer = ByteBuffer.allocate(128);
              socketChannel.read(readBuffer);
              // String 字符串常量,不可变;StringBuffer 字符串变量(线程安全);StringBuilder字符串变量(非线程安全)
              StringBuilder stringBuffer = new StringBuilder();
              // 4.将Buffer从写模式变为可读模式
              readBuffer.flip();
              while(readBuffer.hasRemaining()){
                  stringBuffer.append((char) readBuffer.get());
              }
              System.out.println("从服务端接收到的数据:" + stringBuffer);
              socketChannel.close();
          }
      }

    ServerSocketChannel 允许我们监听TCP链接请求,通过ServerSocketChannelImpl的 accept()方法 可以创建一个SocketChannel对象用户从客户端读/写数据。

    服务端:

    package socketchannel;
      import java.io.IOException;
      import java.net.InetSocketAddress;
      import java.nio.ByteBuffer;
      import java.nio.channels.ServerSocketChannel;
      import java.nio.channels.SocketChannel;
      public class WebServer{
          public static void main(String args[]) throws IOException{
              try{
                  // 1.通过ServerSocketChannel的open()方法创建一个ServerSocketChannel对象,open方法的作用:打开套接字通道
                  ServerSocketChannel ssc = ServerSocketChannel.open();
                  // 2.通过ServerSocketChannel绑定ip地址和port(端口号)
                  ssc.socket().bind(new InetSocketAddress("127.0.0.1",3333));
                  // 通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
                  SocketChannel socketChannel = ssc.accept();
                  // 3.创建写数据的缓存区对象
                  ByteBuffer writeBuffer = ByteBuffer.allocate(128);
                  writeBuffer.put("hello WebClient this is from WebServer".getBytes());
                  writeBuffer.flip();
                  socketChannel.write(writeBuffer);
                  // 创建读数据的缓存区对象
                  ByteBuffer readBuffer = ByteBuffer.allocate(128);
                  // 读取缓存区数据
                  socketChannel.read(readBuffer);
                  StringBuilder stringBuffer = new StringBuilder();
                  // 4.将Buffer从写模式变为可读模式
                  readBuffer.flip();
                  while(readBuffer.hasRemaining()){
                      stringBuffer.append((char) readBuffer.get());
                  }
                  System.out.println("从客户端接收到的数据:" + stringBuffer);
                  socketChannel.close();
                  ssc.close();
              }catch(IOException e){
                  e.printStackTrace();
              }
          }
      }

    通过上述实例代码,我们可以大概总结出SocketChannel和ServerSocketChannel的使用的一般使用规则:

    客户端:

    1.通过SocketChannel的open方法,连接到远程服务器,open方法的参数是IP和端口号

    2.创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据

    3.用close方法关闭SocketChannel

    服务端:

    1.通过ServerSocketChannel 绑定ip地址和端口号,与客户端的一致

    2.通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据

    3.创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据

    4. 关闭SocketChannel和ServerSocketChannel

    四 ️DatagramChannel的使用

    DataGramChannel,类似于java 网络编程的DatagramSocket类;使用UDP进行网络传输, UDP是无连接,面向数据报文段的协议,对传输的数据不保证安全与完整和上面介绍的SocketChannel和ServerSocketChannel的使用方法类似,所以这里就简单介绍一下如何使用。

    1.获取DataGramChannel

              //1.通过DatagramChannel的open()方法创建一个DatagramChannel对象
              DatagramChannel datagramChannel = DatagramChannel.open();        
              //绑定一个port(端口)        
              datagramChannel.bind(new InetSocketAddress(1234));

    上面代码表示程序可以在1234端口接收数据报。

    2.接收/发送消息

    接收消息:

    先创建一个缓存区对象,然后通过receive方法接收消息,这个方法返回一个SocketAddress对象,表示发送消息方的地址:

    ByteBuffer buf = ByteBuffer.allocate(48);
      buf.clear();
      channel.receive(buf);

    发送消息:

    由于UDP下,服务端和客户端通信并不需要建立连接,只需要知道对方地址即可发出消息,但是是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:

      ByteBuffer buf = ByteBuffer.allocate(48);
      buf.clear();
      buf.put("datagramchannel".getBytes());
      buf.flip();
      int send = channel.send(buffer, new InetSocketAddress("localhost",1234));

    这个例子发送一串字符:“datagramchannel”到主机名为”localhost”服务器的端口1234上。

    五 Scatter / Gather

    Channel 提供了一种被称为 Scatter/Gather 的新功能,也称为本地矢量 I/O。Scatter/Gather 是指在多个缓冲区上实现一个简单的 I/O 操作。正确使用 Scatter / Gather可以明显提高性能。

    大多数现代操作系统都支持本地矢量I/O(native vectored I/O)操作。当您在一个通道上请求一个Scatter/Gather操作时,该请求会被翻译为适当的本地调用来直接填充或抽取缓冲区,减少或避免了缓冲区拷贝和系统调用;

    Scatter/Gather应该使用直接的ByteBuffers以从本地I/O获取最大性能优势。

    Scatter/Gather功能是通道(Channel)提供的 并不是Buffer。

    • Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer).

    • Gather: 将N个Buffer里面内容按照顺序发送到一个Channel.

    Scattering Reads

    "scattering read"是把数据从单个Channel写入到多个buffer,如下图所示:

    示例代码:

    ByteBuffer header = ByteBuffer.allocate(128);
      ByteBuffer body   = ByteBuffer.allocate(1024);
      ByteBuffer[] bufferArray = { header, body };
      channel.read(bufferArray);

    read()方法内部会负责把数据按顺序写进传入的buffer数组内。一个buffer写满后,接着写到下一个buffer中。

    举个例子,假如通道中有200个字节数据,那么header会被写入128个字节数据,body会被写入72个字节数据;

    注意:

    无论是scatter还是gather操作,都是按照buffer在数组中的顺序来依次读取或写入的;

    Gathering Writes

    "gathering write"把多个buffer的数据写入到同一个channel中,下面是示意图:

    示例代码:

    ByteBuffer header = ByteBuffer.allocate(128);
      ByteBuffer body   = ByteBuffer.allocate(1024);//write data into buffers
      ByteBuffer[] bufferArray = { header, body };
      channel.write(bufferArray);

    write()方法内部会负责把数据按顺序写入到channel中

    注意:

    并不是所有数据都写入到通道,写入的数据要根据position和limit的值来判断,只有position和limit之间的数据才会被写入;

    举个例子,假如以上header缓冲区中有128个字节数据,但此时position=0,limit=58;那么只有下标索引为0-57的数据才会被写入到通道中。

    六 通道之间的数据传输

    在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。

    • transferFrom() :transferFrom方法把数据从通道源传输到FileChannel

    • transferTo() :transferTo方法把FileChannel数据传输到另一个channel

    参照:Java面试通关手册公众号

  • 相关阅读:
    android如何与asp.net服务端共享session
    WCF 套接字连接已中止。这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的
    PHP 实现单一入口 apache配置
    action func用法记记
    NETCore Bootstrap Admin 通用后台管理权限 [1]: 前后台分离系统简介
    开源 一套 Blazor Server 端精致套件
    NETCore Bootstrap Admin 通用后台管理权限 [3]: 精简版任务调度模块
    NETCore Bootstrap Admin 通用后台管理权限 [2]: Blazor 版本介绍
    C#服务器全面讲解与制作
    批量生成二维码
  • 原文地址:https://www.cnblogs.com/baichendongyang/p/13235479.html
Copyright © 2011-2022 走看看