zoukankan      html  css  js  c++  java
  • Java基础——NIO(二)非阻塞式网络通信与NIO2新增类库

    一、NIO非阻塞式网络通信

      1.阻塞与非阻塞的概念

     传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write()
    时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不
    能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会
    阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,
    当服务器端需要处理大量客户端时,性能急剧下降。
     Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数
    据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时
    间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入
    和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同
    时处理连接到服务器端的所有客户端。
    View Code

      2.选择器

      大致的图解如下:

      

      // 通道注册到选择器上之后,选择器将会对通道进行监控,直到通道传输完全准备就绪了,才分配给服务端一个或多个线程进行处理

       3.使用NIO完成非阻塞式网络通信

      非阻塞核心有三个:(只能用于网络通信)

        通道(channel)  缓冲区(buffer)  选择器(selector)

      NIO完成的网络通信:

        // 客户端
        @Test
        public void client() {
            SocketChannel sChannel = null;
            FileChannel inChannel = null;
            try {
                // 获取通道(采用1.7之后的open方式)
                sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
                // 分配缓冲区
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                // 读取本地文件(使用FileChannel),发送到服务端
                inChannel = FileChannel.open(Paths.get("D:\test\1.jpg"), StandardOpenOption.READ);
                while ((inChannel.read(byteBuffer)) != -1) {
                    // 通过socketChannel写入数据
                    byteBuffer.flip();// 切换读模式
                    sChannel.write(byteBuffer);
                    byteBuffer.clear();// 清空缓冲区
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 务必进行通道的关闭
                if (inChannel != null) {
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
    
        }
        // 服务端
        @Test
        public void server() {
            ServerSocketChannel ssChannel = null;
            FileChannel outChannel = null;
            SocketChannel sChannel = null;
            try {
                // 开启通道
                ssChannel = ServerSocketChannel.open();
                // 绑定端口
                ssChannel.bind(new InetSocketAddress(9898));
                // 获取客户端连接的通道
                sChannel = ssChannel.accept();
                outChannel = FileChannel.open(Paths.get("D:\test\2.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE) ;
                // 接收客户端数据,保存到本地
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                while ((sChannel.read(byteBuffer)) != -1){
                    // 通道读写操作
                    byteBuffer.flip();
                    outChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outChannel != null) {
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (ssChannel != null) {
                    try {
                        ssChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    View Code

      阻塞式NIO网络通信:

        @Test
        public void client() {
            SocketChannel sChannel = null;
            FileChannel inChannel = null;
            try {
                // 方式与阻塞式基本类似
                sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                inChannel = FileChannel.open(Paths.get("D:\test\2.jpg"), StandardOpenOption.READ);
                while ((inChannel.read(byteBuffer)) != -1) {
                    byteBuffer.flip();
                    sChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
                // 接收服务端的反馈
                int len;
                while ((len = sChannel.read(byteBuffer)) != -1) {
                    byteBuffer.flip();
                    System.out.println(new String(byteBuffer.array(), 0, len));
                    byteBuffer.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 流的关闭
                if (inChannel != null) {
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        @Test
        public void server() {
            ServerSocketChannel ssChannel = null;
            SocketChannel sChannel = null;
            FileChannel outChannel = null;
            try {
                ssChannel = ServerSocketChannel.open();
                // 绑定客户端连接
                ssChannel.bind(new InetSocketAddress(9898));
                // 获取连接
                sChannel = ssChannel.accept();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                outChannel = FileChannel.open(Paths.get("D:\test\3.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
                while ((sChannel.read(byteBuffer)) != -1) {
                    // 保存到本地
                    byteBuffer.flip();
                    outChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
                // 发送反馈
                String str = "服务端接收成功!";
                byteBuffer.put(str.getBytes());
                byteBuffer.flip();
                sChannel.write(byteBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outChannel != null) {
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (ssChannel != null) {
                    try {
                        ssChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    View Code

      结果:无法接收有效结束的反馈,不知道客户端是否发送完成

      

      目前可以通过显式的将客户端接收反馈之前发送状态设置为shutdowm:

    sChannel.shutdownOutput();
     @Test
        public void client() {
            SocketChannel sChannel = null;
            FileChannel inChannel = null;
            try {
                // 方式与阻塞式基本类似
                sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                inChannel = FileChannel.open(Paths.get("D:\test\2.jpg"), StandardOpenOption.READ);
                while ((inChannel.read(byteBuffer)) != -1) {
                    byteBuffer.flip();
                    sChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
                sChannel.shutdownOutput();
                // 接收服务端的反馈
                int len;
                while ((len = sChannel.read(byteBuffer)) != -1) {
                    byteBuffer.flip();
                    System.out.println(new String(byteBuffer.array(), 0, len));
                    byteBuffer.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 流的关闭
                if (inChannel != null) {
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        @Test
        public void server() {
            ServerSocketChannel ssChannel = null;
            SocketChannel sChannel = null;
            FileChannel outChannel = null;
            try {
                ssChannel = ServerSocketChannel.open();
                // 绑定客户端连接
                ssChannel.bind(new InetSocketAddress(9898));
                // 获取连接
                sChannel = ssChannel.accept();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                outChannel = FileChannel.open(Paths.get("D:\test\3.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
                while ((sChannel.read(byteBuffer)) != -1) {
                    // 保存到本地
                    byteBuffer.flip();
                    outChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
                // 发送反馈
                String str = "服务端接收成功!";
                byteBuffer.put(str.getBytes());
                byteBuffer.flip();
                sChannel.write(byteBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outChannel != null) {
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (ssChannel != null) {
                    try {
                        ssChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    View Code

      非阻塞式网络通信

      // 客户端
        @Test
        public void client() {
            SocketChannel sChannel = null;
            try {
                // 开启通道
                sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
                // 切换成非阻塞模式
                sChannel.configureBlocking(false);
                // 分配缓冲区
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                // 发送数据给服务端(发送时间),数据是存放在缓冲区中
                byteBuffer.put(LocalDateTime.now().toString().getBytes());
                // 将缓冲区传送给服务端
                byteBuffer.flip();
                sChannel.write(byteBuffer);
                byteBuffer.clear();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (sChannel != null) {
                    try {
                        sChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // 服务端
        @Test
        public void server() {
            ServerSocketChannel ssChannel = null;
            SocketChannel sChannel = null;
            try {
                // 开启通道
                ssChannel = ServerSocketChannel.open();
                // 切换非阻塞模式
                ssChannel.configureBlocking(false);
                // 绑定连接
                ssChannel.bind(new InetSocketAddress(9898));
                // 获取选择器,同样的open方法
                Selector selector = Selector.open();
                // 将通道注册到选择器上,后一个参数表示选择键,也就是监控通道的什么状态
                ssChannel.register(selector, SelectionKey.OP_ACCEPT);// 监听接收
                // 通过选择器轮巡式选择已经“准备就绪“的事件
                while (selector.select() > 0) {
                    // 获取当前选择器中所有注册的选择键
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    // 迭代器迭代
                    while (iterator.hasNext()) {
                        // 获取准备就绪的事件
                        SelectionKey selectionKey = iterator.next();
                        // 判断就绪的事件
                        if (selectionKey.isAcceptable()) {
                            // 若接收就绪,则获取客户端连接
                            sChannel = ssChannel.accept();
                            // 切换为非阻塞模式
                            sChannel.configureBlocking(false);
                            // 将该通道注册到选择器上,监控读就绪状态
                            sChannel.register(selector, SelectionKey.OP_READ);
                        } else if (selectionKey.isReadable()) {
                            // 获取选择器上读就绪状态的通道(需要强转)
                            sChannel = (SocketChannel) selectionKey.channel();
                            // 读取数据
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            int len;
                            while ((len = sChannel.read(byteBuffer)) > 0) {
                                byteBuffer.flip();
                                System.out.println(new String(byteBuffer.array(), 0, len));
                                byteBuffer.clear();
                            }
                        }
                        // 取消选择键(防止出现传输完成了应该重置而还显示完成的情况)
                        iterator.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    View Code

      结果:可以轮巡接收客户端返回的时间

      

      // UDP的DataGramChannel类似,暂不展开

    package com.atguigu.nio;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.DatagramChannel;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.util.Date;
    import java.util.Iterator;
    import java.util.Scanner;
    
    import org.junit.Test;
    
    public class TestNonBlockingNIO2 {
        
        @Test
        public void send() 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() + ":
    " + str).getBytes());
                buf.flip();
                dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
                buf.clear();
            }
            
            dc.close();
        }
        
        @Test
        public void receive() throws IOException{
            DatagramChannel dc = DatagramChannel.open();
            
            dc.configureBlocking(false);
            
            dc.bind(new InetSocketAddress(9898));
            
            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();
            }
        }
    
    }
    View Code

      注册选择器时的选择键:多个请使用或 | 连接

     读 : SelectionKey.OP_READ (1)
     写 : SelectionKey.OP_WRITE (4)
     连接 : SelectionKey.OP_CONNECT (8)
     接收 : SelectionKey.OP_ACCEPT (16)

       4.管道

      

      示例:

      @Test
        public void test1() {
            Pipe pipe = null;
            Pipe.SinkChannel sinkChannel = null;
            Pipe.SourceChannel sourceChannel = null;
            try {
                // 获取管道,经典的open方法获取
                pipe = Pipe.open();
                // 将缓冲区数据写入管道
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                byteBuffer.put("通过单向管道发送数据".getBytes());
                sinkChannel = pipe.sink();
                byteBuffer.flip();
                sinkChannel.write(byteBuffer);
    
                // 读取缓冲区中的数据
                sourceChannel = pipe.source();
                int len;
                byteBuffer.flip();
                len = sourceChannel.read(byteBuffer);
                System.out.println(new String(byteBuffer.array(), 0, len));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (sinkChannel != null) {
                    try {
                        sinkChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (sourceChannel != null) {
                    try {
                        sourceChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    View Code

      //实际使用时可以两个线程公用一个pipe进行数据的传输

    二、NIO2——Path Paths Files

      JDK7新增Path接口,Paths工具类,Files工具类。 这些接口和工具类对NIO中的功能进行了高度封装,大大简化了文件系统的IO编程。

      在传统java.io中, 文件和目录都被抽象成File对象, 即 File file = new File(".");

      NIO.2中则引入接口Path代表与平台无关的路径,文件和目录都用Path对象表示。比起传统File类,它更高效,更方便。

      作为与平台无关的系统访问支持;它的出现,就是为了替代 java.io.File

      Paths工具类——提供获取Path的两个静态方法:

       

     @Test
        public void test1() {
            Path path1 = Paths.get("D:\test\nio");
            Path path2 = Paths.get("D:\test", "nio2", "hello.txt");
            System.out.println(path1);
            System.out.println(path2);
        }

      

      //实现了 toString方法,直接输出即可看到根据当前平台产生的路径

      Path接口

        getNameCount()——返回路径中的节点数量

      @Test
        public void test1() {
            Path path1 = Paths.get("D:\test\nio");
            Path path2 = Paths.get("D:\test", "nio2", "hello.txt");
            System.out.println(path1);
            System.out.println(path2);
            System.out.println(path1.getNameCount());
            System.out.println(path2.getNameCount());
        }

      

        getRoot()——返回根节点,win中是盘符 C D,Linux中是 /,返回的新的Path只包含根节点

     @Test
        public void test1() {
            Path path1 = Paths.get("D:\test\nio");
            Path path2 = Paths.get("D:\test", "nio2", "hello.txt");
            System.out.println(path1);
            System.out.println(path2);
            System.out.println(path1.getRoot());
            System.out.println(path2.getRoot());
        }

      

        toAbsolutePath()——返回绝对路径,若Path初始化时为绝对路径,则返回原路径;若是相对路径,则返回与上级路径合成的绝对路径

     @Test
        public void test1() {
            Path path1 = Paths.get("person.txt");
            Path path2 = Paths.get("D:\test", "nio2", "hello.txt");
            System.out.println(path1);
            System.out.println(path2);
            System.out.println(path1.toAbsolutePath());
            System.out.println(path2.toAbsolutePath());
        }

        

      更多常见方法:

    boolean endsWith(String path) : 判断是否以 path 路径结束
     boolean startsWith(String path) : 判断是否以 path 路径开始
     boolean isAbsolute() : 判断是否是绝对路径
     Path getFileName() : 返回与调用 Path 对象关联的文件名
     Path getName(int idx) : 返回的指定索引位置 idx 的路径名称
     int getNameCount() : 返回Path 根目录后面元素的数量
     Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
     Path getRoot() :返回调用 Path 对象的根路径
     Path resolve(Path p) :将相对路径解析为绝对路径
     Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
     String toString() : 返回调用 Path 对象的字符串表示形式
    View Code

      深入讲解参见:http://blog.csdn.net/lirx_tech/article/details/51416672

       Files工具类——完善的OS文件系统支持

        copy——复制文件

    @Test
        public void test1() {
            Path src = Paths.get("D:\test\1.jpg");
            Path dst = Paths.get("D:\test", "2.jpg");
            try {
                Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }

        BufferedWriter——写入字符串

     @Test
        public void test1() {
            Path src = Paths.get("D:\test\hello.txt");
            BufferedWriter writer = null;
            try {
                writer = Files.newBufferedWriter(src, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
                writer.write("Hello Files!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }

      其它诸如返回文件的大小,删除一个文件,以及与File的转换等等请参见以下随笔、常用方法列表或API:

      几个示例:http://www.cnblogs.com/digdeep/p/4478734.html

      常用方法:

     Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
     Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
     Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
     void delete(Path path) : 删除一个文件
     Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
     long size(Path path) : 返回 path 指定文件的大小
     Files常用方法:用于判断
     boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
     boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
     boolean isExecutable(Path path) : 判断是否是可执行文件
     boolean isHidden(Path path) : 判断是否是隐藏文件
     boolean isReadable(Path path) : 判断文件是否可读
     boolean isWritable(Path path) : 判断文件是否可写
     boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
     public static <A extends BasicFileAttributes> A readAttributes(Path path,Class<A> type,LinkOption...
    options) : 获取与 path 指定的文件相关联的属性。
     Files常用方法:用于操作内容
     SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,
    how 指定打开方式。
     DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
     InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
     OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
    View Code
  • 相关阅读:
    Editor HYSBZ
    MooFest POJ
    Monkey King HDU
    Haruna’s Breakfast HYSBZ
    数颜色 HYSBZ
    Mato的文件管理 HYSBZ
    小Z的袜子(hose) HYSBZ
    javascript类的简单定义
    json格式
    javascript call apply
  • 原文地址:https://www.cnblogs.com/jiangbei/p/7503502.html
Copyright © 2011-2022 走看看