NIO之完成网络通信

简介: NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。对于网络通信是还有如下几个Channel


 NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。对于网络通信是还有如下几个Channel

 java.nio.channels.Channel 接口
  |-- SelectableChannel
    |-- SocketChannel
    |-- ServerSocketChannel
    |-- DatagramChannel
    |-- Pipe.SinkChannel
    |-- Pipe.SourceChannel

 我们通常使用NIO是在网络中使用的,网上大部分讨论NIO都是在网络通信的基础之上的!说NIO是非阻塞的NIO也是网络中体现的!

nio的核心要素有:

Buffer缓冲区
Channel通道
Selector选择器

我们在网络中使用NIO往往是I/O模型的多路复用模型!

NIO阻塞形态

 为了更好地理解,我们先来写一下NIO在网络中是阻塞的状态代码,随后看看非阻塞是怎么写的就更容易理解了。是阻塞的就没有Selector选择器了,就直接使用Channel和Buffer就完事了。

客户端代码

/**
 * 使用NIO完成网络通信的三个核心
 * 
 * 1.通道(Channel):负责连接
 *    java.nio.channels.Channel 接口
 *      |-- SelectableChannel
 *        |-- SocketChannel
 *        |-- ServerSocketChannel
 *        |-- DatagramChannel
 *        
 *        |-- Pipe.SinkChannel
 *        |-- Pipe.SourceChannel
 * 
 *  2.缓冲区(Buffer):负责数据的存取
 *  
 *  3.选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
 * 
 * 
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class BlockClient {
  /**
   * 传递图片
   * @param args
   * @throws Exception 
   */
  public static void main(String[] args) throws Exception {
    // 1.获取通道
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
    // 2.发送一张图片给服务器
    FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"), StandardOpenOption.READ);
    // 3.要使用NIO,有了Channel必然要使用Buffer,Buffer是与数据打交道的
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    // 4.读取本地文件,发送给服务器
    while(fileChannel.read(byteBuffer)!=-1){
      byteBuffer.flip();//读取之前切换成读模式
      socketChannel.write(byteBuffer);
      byteBuffer.clear();
    }
    // 显示的告诉服务器数据写完了
    socketChannel.shutdownOutput();
    int num = 0 ;
    byteBuffer.clear();
    while((num = socketChannel.read(byteBuffer)) !=-1){
      // 切回读模式
      byteBuffer.flip();
      System.out.println(new String(byteBuffer.array(),0,num));
      byteBuffer.clear();
    }
    /*// 5.关闭流
    fileChannel.close();
    socketChannel.close();*/
  }
}

服务器代码

public class BlockServer {
  /**
   * IO阻塞 服务端
   * @param args
   * @throws Exception 
   */
  public static void main(String[] args) throws Exception {
    //1.获取通道
    ServerSocketChannel server = ServerSocketChannel.open();
    //2.得到文件通道,将客户端传递的图片写入到本地
    FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/bb.jpg"), 
        StandardOpenOption.WRITE
        ,StandardOpenOption.CREATE);// 如果不存在就创建
    // 3.绑定连接
    server.bind(new InetSocketAddress(9999));
    // 4.获取客户端的连接(阻塞的)
    SocketChannel client = server.accept();
    // 5.声明Buffer存储图片
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    // 6.将客户端传递的数据保存在本地
    while(client.read(byteBuffer)!=-1){
      byteBuffer.flip();
      fileChannel.write(byteBuffer);
      byteBuffer.clear();
    }
    byteBuffer.put("图片接收成功!!".getBytes());
    byteBuffer.flip();
    client.write(byteBuffer);
    byteBuffer.clear();
    // 显示的告诉客户端信息输完了
    client.shutdownOutput();
    /*fileChannel.close();
    client.close();
    server.close();*/
  }
}

NIO非阻塞形态

 如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了,但是需要显示的指定是非阻塞的

客户端:

public class NoBlockClient {
  public static void main(String[] args) throws Exception {
    // 1. 获取通道
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));
    // 1.1切换成非阻塞模式
    socketChannel.configureBlocking(false);
    // 2. 发送一张图片给服务端吧
    FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"),
        StandardOpenOption.READ);
    // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 4.读取本地文件(图片),发送到服务器
    while (fileChannel.read(buffer) != -1) {
      // 在读之前都要切换成读模式
      buffer.flip();
      socketChannel.write(buffer);
      // 读完切换成写模式,能让管道继续读取文件的数据
      buffer.clear();
    }
    // 5. 关闭流
    fileChannel.close();
    socketChannel.close();
  }
}

服务器:

public class NoBlockServer {
  public static void main(String[] args) throws Exception {
    // 1.获取通道
    ServerSocketChannel server = ServerSocketChannel.open();
    // 2.切换成非阻塞模式
    server.configureBlocking(false);
    // 3. 绑定连接
    server.bind(new InetSocketAddress(6666));
    // 4. 获取选择器
    Selector selector = Selector.open();
    // 4.1将通道注册到选择器上,指定接收“监听通道”事件
    server.register(selector, SelectionKey.OP_ACCEPT);
    // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
    while (selector.select() > 0) {
      // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
      Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
      // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
      while (iterator.hasNext()) {
        SelectionKey selectionKey = iterator.next();
        // 接收事件就绪
        if (selectionKey.isAcceptable()) {
          // 8. 获取客户端的链接
          SocketChannel client = server.accept();
          // 8.1 切换成非阻塞状态
          client.configureBlocking(false);
          // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
          client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) { // 读事件就绪
          // 9. 获取当前选择器读就绪状态的通道
          SocketChannel client = (SocketChannel) selectionKey.channel();
          // 9.1读取数据
          ByteBuffer buffer = ByteBuffer.allocate(1024);
          // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
          FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,
              StandardOpenOption.CREATE);
          while (client.read(buffer) > 0) {
            // 在读之前都要切换成读模式
            buffer.flip();
            outChannel.write(buffer);
            // 读完切换成写模式,能让管道继续读取文件的数据
            buffer.clear();
          }
        }
        // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
        iterator.remove();
      }
    }
  }
}

简单总结一下使用NIO时的要点:

   将Socket通道注册到Selector中,监听感兴趣的事件

   当感兴趣的时间就绪时,则会进去我们处理的方法进行处理

   每处理完一次就绪事件,删除该选择键(因为我们已经处理完了)

DatagramChannel

发送方

public static void main(String[] args) throws IOException {
  // 获取通道
  DatagramChannel dc = DatagramChannel.open();
  // 非阻塞的
  dc.configureBlocking(false);
  ByteBuffer buf = ByteBuffer.allocate(1024);
  Scanner scan = new Scanner(System.in);
  while(scan.hasNext()){
    String str = scan.next();
    buf.put((new Date().toString()+"\n"+str).getBytes());   
    buf.flip();
    dc.send(buf, new InetSocketAddress("127.0.0.1", 6666));
    buf.clear();
  }
  dc.close();
}

接收方

public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub
  DatagramChannel dc = DatagramChannel.open();
  dc.bind(new InetSocketAddress(6666));
  dc.configureBlocking(false);
  // 获取选择器
  Selector selector = Selector.open();
  dc.register(selector, SelectionKey.OP_READ);
  while(selector.select() > 0){
    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
    while(it.hasNext()){
      SelectionKey sk = it.next();
      if(sk.isReadable()){
        ByteBuffer buf = ByteBuffer.allocate(1024);
        dc.receive(buf);
        buf.flip();
        System.out.println(new String(buf.array(),0,buf.limit()));
        buf.clear();
      }
    }
    it.remove();
  }
}

管道(Pipe)

 Java NIO 管道是2个线程之间的单向数据连接,Pipe有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取。

image.png

public static void main(String[] args) throws Exception {
  // 1.获取管道
  Pipe pipe = Pipe.open();
  // 2.将缓冲区数据写入管道
  ByteBuffer buf = ByteBuffer.allocate(1024);
  // 3.获取SinkChannel对象
  SinkChannel sinkChannel = pipe.sink();
  buf.put("hello pipe".getBytes());
  buf.flip();
  sinkChannel.write(buf);
  // 4.读取缓冲区中的数据
  SourceChannel source = pipe.source();
  buf.flip();
  int num = source.read(buf);
  System.out.println(new String(buf.array(),0,num));
  source.close();
  sinkChannel.close();
}


相关文章
|
监控 Java Linux
由浅入深Netty基础知识NIO网络编程1
由浅入深Netty基础知识NIO网络编程
67 0
|
3月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
121 0
|
3月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
74 0
|
存储 Java Docker
由浅入深Netty基础知识NIO网络编程 2
由浅入深Netty基础知识NIO网络编程
67 0
|
6月前
|
设计模式 网络协议 Java
Java NIO 网络编程 | Netty前期知识(二)
Java NIO 网络编程 | Netty前期知识(二)
116 0
|
存储 Java API
使用Java NIO进行文件操作、网络通信和多路复用的案例
使用Java NIO进行文件操作、网络通信和多路复用的案例
|
存储 Java Linux
BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程
上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题,从而造成服务端崩溃的现象。怎么解决这一问题呢?优化呗,所以后面就有了NIO、AIO、IO多路复用。本文将对这几个模型详细说明并基于 Java 编写 NIO。
312 0
|
存储 Web App开发 缓存
网络通信_BIO和NIO
网络通信三要素 1、确定发给哪个接收端(IP地址) 2、确定发给接收端中的哪个应用程序(端口号) 3、确定网络中传输数据的规则(协议)
81 1
|
JSON 网络协议 关系型数据库
计网 - 网络 I/O 模型:BIO、NIO 和 AIO 有什么区别?
计网 - 网络 I/O 模型:BIO、NIO 和 AIO 有什么区别?
116 0
|
存储 缓存 网络协议
架构解密从分布式到微服务:深入理解网络,NIO
我们知道,分布式系统的基础是网络。因此,网络编程是分布式软件工程师和架构师的必备技能之一,而且随着当前大数据和实时计算技术的兴起,高性能RPC架构与网络编程技术再次成为焦点
下一篇
无影云桌面