NIO与网络编程系统化学习

1.背景

  数据在网络中传输,必然回遇到读写问题....

2.比较NIO与IO

  NIO与网络编程系统化学习

 

3.案例演示

3.1.缓冲区演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.nio.ByteBuffer;

public class BufferDemo {
    /**
     * 缓冲区(Buffer)
     * Buffer在Java NIO 中负责数据的存取,缓冲区就是数组,用于存储不同数据类型的数据。
     * <p>
     * 缓冲区类型
     * 根据数据类型的不同(boolean除外),提供了相应类型的缓冲区。
     * <p>
     * ByteBuffer
     * CharBuffer
     * ShortBuffer
     * IntBuffer
     * LongBuffer
     * FloatBuffer
     * DoubleBuffer
     * 上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区。
     * ByteBuffer最为常用
     * ————————————————
     * 缓冲区存取数据的两个核心方法
     * put():存入数据到缓冲区中
     * flip():切换到读取数据的模式
     * get():获取缓冲区中的数据
     * rewind():重复读,使position归0
     * clear():清空缓冲区,但是缓冲区中的数据依然存在,只是处于一种“被遗忘“的状态。只是不知道位置界限等,读取会有困难。
     * mark():标记。mark会记录当前的position,limit,capacity
     * reset():position,limit,capacity恢复到mark记录的位置
     * -------------------
     * 缓冲区的四个核心属性
     * capacity: 容量,表示缓冲区中最大存储数据的容量,一但声明不能改变。(因为底层是数组,数组一但被创建就不能被改变)
     * limit: 界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写)
     * position: 位置,表示缓冲区中正在操作数据的位置
     * position <= limit <= capacity
     * mark:标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置。
     * ———————————————
     */
    @Test
    public void test1() {
        //1.allocate():分配缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        //2.当前缓存区当前属性
        System.out.println("-----allocate(128)-------");
        print(byteBuffer);
        //3.放入数据
        String put = "123456";
        byteBuffer.put(put.getBytes());
        System.out.println("-----put()-------");
        print(byteBuffer);
        //4.flip():切换到读取数据的模式
        byteBuffer.flip();
        System.out.println("-----flip()-------");
        print(byteBuffer);
        //5.读取数据
        byte[] bytes = new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);
        System.out.println("读到的数据:" + new String(bytes));
        System.out.println("-----get()-------");
        print(byteBuffer);
        //6.rewind():重复读,使position归0
        byteBuffer.rewind();
        System.out.println("-----rewind()-------");
        print(byteBuffer);
        //7.clear():清空缓冲区,但是缓冲区中的数据依然存在,只是处于一种“被遗忘“的状态。只是不知道位置界限等,读取会有困难
        byteBuffer.clear();
        System.out.println("-----clear()-------");
        print(byteBuffer);
        //8.清理后仍然可以读取
        char aChar = byteBuffer.getChar();
        System.out.println("aChar=" + aChar);
        System.out.println("---清理后读取--getChar()-------");
        print(byteBuffer);
    }

    /**
     * 演示
     * mark:标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置。
     */
    @Test
    public void test2() {
        ByteBuffer buffer = ByteBuffer.allocate(128);
        buffer.put("123".getBytes());
        System.out.println("-----put()--1-----");
        print(buffer);
        //标记位置
        buffer.mark();
        System.out.println("-----mark()-------");
        print(buffer);
        //继续放入
        buffer.put("456".getBytes());
        System.out.println("-----put()--2-----");
        print(buffer);
        //返回到标记处
        buffer.reset();
        System.out.println("-----reset()-------");
        print(buffer);


    }

    /**
     * 直接缓冲区与非直接缓冲区
     * 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JVM内存开销,处理过程中有复制操作。
     * 非直接缓冲区的写入步骤:
     * 创建一个临时的ByteBuffer对象。
     * 将非直接缓冲区的内容复制到临时缓冲中。
     * 使用临时缓冲区执行低层次I/O操作。
     * 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
     * ————————————————
     * 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。
     * 直接缓冲区在JVM内存外开辟内存,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在物理内存内,会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销
     * ————————————————
     */
    @Test
    public void test3() {
        //直接缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(128);
        System.out.println(buffer.isDirect());
        //非直接缓冲区
        ByteBuffer buffer2 = ByteBuffer.allocate(128);
        System.out.println(buffer2.isDirect());
    }

    /**
     * // Invariants: mark <= position <= limit <= capacity
     * private int mark = -1;
     * private int position = 0;
     * private int limit;
     * private int capacity;
     *
     * @param byteBuffer
     */
    public void print(ByteBuffer byteBuffer) {
        System.out.println("capacity=" + byteBuffer.capacity());
        System.out.println("limit=" + byteBuffer.limit());
        System.out.println("position=" + byteBuffer.position());
    }
}
View Code

 

3.2.通道案例演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * 通道(Channel)表示IO源与目标打开的连接。Channel类似于传统的”流“,只不过Channel本身不能直接访问数据,
 * Channel只能与Buffer进行交互。
 * -------------------------
 * Channel是一个独立的处理器,专门用于IO操作,附属于CPU。
 * 在提出IO请求的时候,CPU不需要进行干预,也就提高了效率。
 * --------------------------
 * 作用
 * 用于源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。
 * Channel本身并不存储数据,因此需要配合Buffer一起使用
 * 主要实现类
 * java.nio.channels.Channel接口:
 * 用于本地数据传输:
 * ​    |-- FileChannel
 * 用于网络数据传输:
 * ​    |-- SocketChannel
 * ​    |-- ServerSocketChannel
 * ​    |-- DatagramChannel
 * <p>
 * 获取通道
 * 1.Java 针对支持通道的类提供了一个 getChannel() 方法。
 * 本地IO操作
 * <p>
 * FileInputStream/FileOutputStream
 * RandomAccessFile
 * 网络IO
 * <p>
 * Socket
 * ServerSocket
 * DatagramSocket
 * 2.在JDK1.7中的NIO.2 针对各个通道提供了静态方法 open();
 * 3.在JDK1.7中的NIO.2 的Files工具类的 newByteChannel();
 * ————————————————
 */
public class ChannelDemo {
    // String path = "E:\\test\\t.rar";
    // String path2 = "E:\\test\\t2.rar";
    String path = "E:\\test\\1.docx";
    String path2 = "E:\\test\\2.docx";

    /**
     * 如果存在者删除在创建,如果存在则直接创建
     * file.createNewFile();
     * ----------------------------
     * 利用通道完成文件复制(getChannel()下-非直接缓冲区与直接缓冲区)
     * 1.创建输入输出流FileInputStream\FileOutputStream
     * 2.利用输入输出流创建管道 getChannel()
     * 3.创建缓冲区
     * 4.利用管道读写缓存区的数据(循环读取)
     * 5.关闭资源
     */
    @Test
    public void test1() throws Exception {
        long start = System.currentTimeMillis();
        //1.创建输入输出流FileInputStream\FileOutputStream
        FileInputStream fileInputStream = new FileInputStream(path);
        File file = new File(path2);
        file.createNewFile();
        FileOutputStream fileOutputStream = new FileOutputStream(file);

        //2.利用输入输出流创建管道 getChannel()
        FileChannel channelIn = fileInputStream.getChannel();
        FileChannel channelOut = fileOutputStream.getChannel();

        //3.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);//耗时:55018-耗时:52585
        // ByteBuffer buffer = ByteBuffer.allocateDirect(1024);//耗时:65347-耗时:51880
        int read = channelIn.read(buffer);
        //4.利用管道循环读写缓存区的数据
        while (read != -1) {
            //取出
            buffer.flip();
            channelOut.write(buffer);
            buffer.clear();
            read = channelIn.read(buffer);
        }
        //5.关闭资源
        channelIn.close();
        channelOut.close();
        fileInputStream.close();
        fileOutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));
    }

    /**
     * 直接缓冲区读取(open()开启通道)
     * 1.利用静态方法创建输入输出通道(FileChannel)
     * 2.创建内存映射文件缓冲区(MapByteBuffer)
     * 3.利用get\put读写文件
     * 4.关闭资源
     * ---------
     * 耗时:25717-耗时:31151
     */
    @Test
    public void test2() throws Exception {
        long start = System.currentTimeMillis();
        //1. 利用静态方法创建输入输出通道(FileChannel)
        FileChannel channelIn = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
        FileChannel channelOut = FileChannel.open(Paths.get(path2), StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        //2. 创建内存映射文件缓冲区(map())
        MappedByteBuffer bufferIn = channelIn.map(FileChannel.MapMode.READ_ONLY, 0, channelIn.size());
        MappedByteBuffer bufferOut = channelOut.map(FileChannel.MapMode.READ_WRITE, 0, channelIn.size());
        //3. 利用get\put读写文件
        byte[] bytes = new byte[bufferIn.limit()];
        bufferIn.get(bytes);
        bufferOut.put(bytes);
        //4. 关闭资源
        channelIn.close();
        channelOut.close();
        //耗时统计
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));
    }

    /**
     * 通道之间的数据传输(也是利用的直接缓冲器的方式)
     * 1.利用静态方法创建输入输出通道(FileChannel)
     * 2.利用transferTo()或者 transferFrom()进行文件传输
     * 3.关闭资源
     * ---------------
     * 耗时:9812-耗时:10131
     */
    @Test
    public void test3() throws Exception {
        long start = System.currentTimeMillis();
        //1.利用静态方法创建输入输出通道(FileChannel)
        FileChannel channelIn = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
        FileChannel channelOut = FileChannel.open(Paths.get(path2), StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        //2.利用transferTo()或者 transferFrom()进行文件传输
        channelOut.transferFrom(channelIn, 0, channelIn.size());
        //channelIn.transferTo(0,channelIn.size(),channelOut);
        //3.关闭资源
        channelIn.close();
        channelOut.close();
        //统计耗时
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));

    }

    /**
     * 分散(Scatter)与聚集(Gather)
     * 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
     * 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
     * ----------------------------------
     * 1.创建写文件对象
     * 2.获取通道
     * 3.创建分散的缓冲区
     * 4.分散读取文件
     * 5.创建写入文件对象 和 写入通道
     * 5.聚集写入
     */
    @Test
    public void test4() throws Exception {
        System.out.println("---------开始-----------");
        //编码处理
        Charset charset = Charset.forName("gbk");
        //解码器
        CharsetDecoder decoder = charset.newDecoder();
        //编码器
        CharsetEncoder encoder = charset.newEncoder();

        //1.创建写文件对象
        RandomAccessFile randomAccessFileIn = new RandomAccessFile(path, "rw");
        //2.获取通道
        FileChannel channelIn = randomAccessFileIn.getChannel();
        //3.创建分散的缓冲区
        ByteBuffer buffer1 = ByteBuffer.allocate(128);
        ByteBuffer buffer2 = ByteBuffer.allocate(256);
        ByteBuffer[] buffers = {buffer1, buffer2};
        //4.分散读取文件
        channelIn.read(buffers);
        for (ByteBuffer buffer : buffers) {
            buffer.flip();
        }
        //5.创建写入文件对象 和 写入通道
        RandomAccessFile randomAccessFileOut = new RandomAccessFile(path2, "rw");
        FileChannel channelOut = randomAccessFileOut.getChannel();
        //5.聚集写入
        channelOut.write(buffers);
        //6.关闭资源
        channelIn.close();
        channelOut.close();
        randomAccessFileIn.close();
        randomAccessFileOut.close();
        System.out.println("---------完成----------");
    }

    /**
     * 字符集Charset
     * 设置字符集,解决乱码问题
     * 编码:字符串->字节数组
     * 解码:字节数组->字符串
     *---------------------------
     * 思路
     * 用Charset.forName(String)构造一个编码器或解码器,利用编码器和解码器来对CharBuffer编码,对ByteBuffer解码。
     * 需要注意的是,在对CharBuffer编码之前、对ByteBuffer解码之前,请记得对CharBuffer、ByteBuffer进行flip()切换到读模式。
     * 如果编码和解码的格式不同,则会出现乱码。
     * ————————————————
     */
    @Test
    public void test5() throws CharacterCodingException {
        //创建编码对象
        Charset charset1 = Charset.forName("gbk");
        //获取编码器
        CharsetEncoder encoder1 = charset1.newEncoder();
        //获取解码器
        CharsetDecoder decoder1 = charset1.newDecoder();
        //创建缓冲区,并放入数据
        CharBuffer buffer = CharBuffer.allocate(1024);
        buffer.put("你好世界我们");
        buffer.flip();
        //编码
        ByteBuffer byteBuffer = encoder1.encode(buffer);
        for (int i = 0; i < 4; i++) {
            System.out.println(" --"+byteBuffer.get());
        }
        System.out.println("-----------------");
        //解码
        byteBuffer.flip();
        CharBuffer charBuffer = decoder1.decode(byteBuffer);
        System.out.println("---"+charBuffer.toString());
    }
    @Test
    public void CharacterEncodingTest() throws CharacterCodingException {
        Charset charset = Charset.forName("utf-8");
        Charset charset1 = Charset.forName("gbk");

        // 获取编码器 utf-8
        CharsetEncoder encoder = charset.newEncoder();

        // 获得解码器 gbk
        CharsetDecoder decoder = charset1.newDecoder();

        CharBuffer buffer = CharBuffer.allocate(1024);
        buffer.put("绝不敷衍,从不懈怠!");
        buffer.flip();

        // 编码
        ByteBuffer byteBuffer = encoder.encode(buffer);
        for (int i = 0; i < 20; i++) {
            System.out.println(byteBuffer.get());
        }

        // 解码
        byteBuffer.flip();
        CharBuffer charBuffer = decoder.decode(byteBuffer);
        System.out.println(charBuffer.toString());
    }

}
View Code

 

3.3.阻塞NIO案例演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * 网络阻塞IO与非阻塞IO
 * 1.了解
 * 1.1.传统IO是阻塞式的,也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
 * 1.2.NIO是非阻塞式的,当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO操作,所以单独的线程可以管理多个输入和输出通道。因此, NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
 * 2.阻塞模式与非阻塞模式
 * 2.1.传统阻塞IO方式:客户端向服务器端发送请求,服务器端便开始进行监听客户端的数据是否传过来。这时候客户端在准备自己的数据,而服务器端就需要干等着。即使服务器端是多线程的,但有时一味增加线程数,只会让阻塞的线程越来越多。
 * <p>
 * 2.2.NIO的非阻塞方式:将用于传输的通道全部注册到选择器上。
 * <p>
 * 2.3.选择器的作用是监控这些通道的IO状况(读,写,连接,接收数据的情况等状况)。
 * <p>
 * 选择器与通道之间的联系:
 * <p>
 * 通道注册到选择器上
 * 选择器监控通道
 * 当某一通道,某一个事件就绪之后,选择器才会将这个通道分配到服务器端的一个或多个线程上,再继续运行。例如客户端需要发送数据给服务器端,只当客户端所有的数据都准备完毕后,选择器才会将这个注册的通道分配到服务器端的一个或多个线程上。而在客户端准备数据的这段时间,服务器端的线程可以执行别的任务。
 * <p>
 * ————————————————
 * 使用NIO完成网络通信的三个核心
 * 1.通道(Channel):负责连接
 * <p>
 * java.mio.channels.Channel 接口:
 * |-- SelectableChannel
 * |--SocketChannel
 * |--ServerSocketChannel
 * |--DatagramChannel
 * <p>
 * |--Pipe.SinkChannel
 * |--Pipe.sourceChannel
 * 2.缓冲区(Buffer):负责数据的存取
 * <p>
 * 3.选择器(Select):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
 * ————————————————
 * 需求:
 * 将文件a.txt从客户端传到服务端
 * 阻塞模式完成客户端向服务器端传输数据
 */
public class BlockNioDemo {
    String path = "E:\\test\\a.txt";
    String path2 = "E:\\test\\b.txt";
    String host = "192.168.0.103";
    Integer port = 1001;

    /**
     * 客户端:
     */
    @Test
    public void client() throws Exception {
        //1.创建本地文件读取管道
        FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
        //2.创建网络写管道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
        //3.创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //4.读取本地文件管道中的数据
        while (fileChannel.read(buffer) != -1) {
            //5.写入网络管道
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }
        //6.告知服务端已完成传输
        socketChannel.shutdownOutput();
        System.out.println("--------客户端传输完成-------------");
        //7.接收服务端接收结果
        int len = socketChannel.read(buffer);
        buffer.flip();
        System.out.println("<---" + new String(buffer.array(), 0, len));
        buffer.flip();
        //7.关闭资源
        socketChannel.close();
        fileChannel.close();
        System.out.println("--------客户端传输完成-------------");
    }

    /**
     * 塞模式完成客户端向服务器端传输数据
     * 服务端:
     */
    @Test
    public void server() throws Exception {
        //1.创建本地文件写入管道
        FileChannel fileChannel = FileChannel.open(Paths.get(path2), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        //2.创建网络读取管道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //绑定端口号
        ssChannel.bind(new InetSocketAddress(host, port));
        //3.监听链接
        System.out.println("--------服务端已开启等待连接------------");
        SocketChannel socketChannel = ssChannel.accept();
        //4.创建缓存区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //5.读取网络管道中的文件
        int len;
        while ((len = (socketChannel.read(buffer))) != -1) {
            //6.写入本地文件管道
            buffer.flip();
            System.out.println("-->" + new String(buffer.array(), 0, len));
            fileChannel.write(buffer);
            buffer.clear();
        }
        //7.关闭读取(可以不写这步)
        socketChannel.shutdownInput();
        System.out.println("-------------服务端接收完成--------------------");
        //8.响应客户端,文件传输完成
        buffer.put("文件接收完成,ok".getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        buffer.clear();
        //9.关闭资源
        fileChannel.close();
        socketChannel.close();
        ssChannel.close();
        System.out.println("-------------接收完成--------------------");
    }
}
View Code

 

3.4.非阻塞NIO案例演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;
import java.util.Set;

/**
 * 需求:
 * 以非阻塞的方式将文件从客户端传到服务端
 */
public class NoBlockNioDemo {
    String path = "E:\\test\\a.txt";
    String path2 = "E:\\test\\" + System.currentTimeMillis() + "-b.txt";
    String host = "192.168.0.103";
    Integer port = 1001;

    @Test
    public void client() throws Exception {
        //创建本地文件管道
        FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
        //创建网络传输管道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
        //将网络传输管道设置为非阻塞模式
        socketChannel.configureBlocking(false);
        //创建缓存区
        //文件管道读取文件
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (fileChannel.read(buffer) != -1) {
            //网络管道写文件
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }
        //关闭传输
        System.out.println("---------客户端传输完成---------");
        //关闭资源
        socketChannel.close();
        fileChannel.close();
    }

    @Test
    public void server() throws Exception {
        //创建网络服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(host, port));
        //设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //创建选择器
        Selector selector = Selector.open();
        //注册选择器(读,写,链接..)
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //轮询监听是否与准备就绪的请求
        SocketChannel socketChannel = null;
        System.out.println("-------服务端准备就绪等待连接----------");
        //统计接收次数 和 读取次数
        int sumAccept = 0;
        int sumRead = 0;
        while (selector.select() > 0) {
            //如果有,获取已就绪的监听事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            //遍历监听事件
            while (iterator.hasNext()) {
                SelectionKey next = iterator.next();
                //判断监听事件是什么类型,根据类型做响应的处理
                if (next.isAcceptable()) {
                    ++sumAccept;
                    System.out.println("--->连接操作-就绪-" + sumAccept);
                    //如果是接收
                    // 获取连接
                    socketChannel = serverSocketChannel.accept();
                    // 切换非阻塞模式
                    socketChannel.configureBlocking(false);
                    // 注册连接器
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("--->连接操作-注册完成");
                } else if (next.isReadable()) {
                    ++sumRead;
                    //如果是读取
                    System.out.println("--->读取操作-就绪-" + sumRead);
                    // 获取就绪通道
                    SocketChannel readChannel = (SocketChannel) next.channel();
                    // 切换非阻塞模式
                    readChannel.configureBlocking(false);
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //创建文件写入通道
                    path2 = "E:\\test\\" + System.currentTimeMillis() + "-b.txt";
                    FileChannel fileChannel = FileChannel.open(Paths.get(path2), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                    while (readChannel.read(buffer) != -1) {
                        buffer.flip();
                        fileChannel.write(buffer);
                        buffer.clear();
                    }
                    System.out.println("------文件接收完成----");
                    readChannel.close();
                    fileChannel.close();
                    System.out.println("------关闭文件写通道,关闭网络读通道----");
                } else {
                    System.out.println("-------其他非法操作-------");
                }
                //关闭就绪渠道
                System.out.println("--->移除已经处理了的");
                iterator.remove();
            }
        }
        //关闭资源
        serverSocketChannel.close();
        socketChannel.close();
        System.out.println("------服务端操作完成-----");
    }
}
View Code

 

3.5.简易聊天室

  服务端

NIO与网络编程系统化学习
package com.wfd360.nio.chat;

import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * 简易聊天室服务端
 */
public class ChatServer {
    static String host = "192.168.0.103";
    static Integer port = 1001;

    public static void main(String[] args) throws Exception {
        //建立服务端网络通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(host, port));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //创建选择器
        Selector selector = Selector.open();
        //注册选择器
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环检查是否有准备就绪的状态
        System.out.println("-------服务端准备就绪-----------");
        int sumAccept = 0;
        int sumRead = 0;
        while (selector.select() > 0) {
            //获取当前所有准备就绪的状态,并循环依次处理
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey selectionKey = keys.next();
                if (selectionKey.isAcceptable()) {
                    ++sumAccept;
                    //如果是接收状态  就收当前通道  设置为非阻塞模式  注册通道
                    SocketChannel acceptChannel = serverSocketChannel.accept();
                    Socket socket = acceptChannel.socket();
                    System.out.println("ip=" + socket.getInetAddress() + ",port=" + socket.getPort() + ",=" + sumAccept);
                    acceptChannel.configureBlocking(false);
                    acceptChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    ++sumRead;
                    //如果是读取状态  获取当前通道   设置非阻塞模式
                    SocketChannel readChannel = (SocketChannel) selectionKey.channel();
                    readChannel.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //读取数据
                    int len = readChannel.read(buffer);
                    buffer.flip();
                    String recive = new String(buffer.array(), 0, len);
                    System.out.println("<--" + recive + ",=" + sumRead);
                    buffer.clear();
                    //向客户端响应数据
                    String send = "服务端已收到数据[" + recive + "]";
                    buffer.put(send.getBytes());
                    buffer.flip();
                    readChannel.write(buffer);
                    buffer.clear();
                    //关闭已处理的当前 通道
                    System.out.println("==========关闭已处理的当前 通道 ");
                    readChannel.close();
                }
                //移除以处理的key
                System.out.println("==========移除以处理的key ");
                keys.remove();
            }
        }
        //关闭资源
        serverSocketChannel.close();
    }
}
View Code

 

  客户端

NIO与网络编程系统化学习
package com.wfd360.nio.chat;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class ChatClient {
    static String host = "192.168.0.103";
    static Integer port = 1001;

    public static void main(String[] args) throws Exception {
        while (true) {
            //创建网络客户端通道
            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
            //切换为非阻塞模式
            //socketChannel.configureBlocking(false);
            System.out.println("请输入要发送的数据:");
            Scanner scanner = new Scanner(System.in);

            String next = scanner.next();
            //发送数据
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put(next.getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
            //接收数据
            socketChannel.read(buffer);
            buffer.flip();
            System.out.println("<---" + new String(buffer.array()));
            buffer.clear();
            //关闭资源
            socketChannel.close();
        }
    }
}
View Code

 

3.6.UDP非阻塞NIO案例演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.io.IOException;
import java.net.DatagramSocket;
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.Iterator;

/**
 * DatagramChannel
 * 实现发送数据与接收数据
 */
public class UDPChannelDemo {
    String host = "192.168.0.103";
    Integer port = 1001;

    @Test
    public void send() throws IOException {
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.bind(new InetSocketAddress(host, 1002));
        datagramChannel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("测试发送".getBytes());
        buffer.flip();
        datagramChannel.send(buffer, new InetSocketAddress(host, port));
        buffer.clear();
        datagramChannel.close();
        System.out.println("===客户端发送完成==");
    }

    @Test
    public void receive() throws IOException {
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.configureBlocking(false);
        datagramChannel.bind(new InetSocketAddress(host, port));
        Selector selector = Selector.open();
        datagramChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("-----服务端准备就绪----------");
        while (selector.select() > 0) {
            DatagramSocket socket = datagramChannel.socket();
            System.out.println("==>" + socket.getInetAddress() + ",==" + socket.getPort());
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (key.isReadable()) {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    datagramChannel.receive(buffer);
                    buffer.flip();
                    System.out.println("<--" + new String(buffer.array()));
                    buffer.clear();
                } else {
                    System.out.println("-------未知类型-----");
                }
                keyIterator.remove();
            }
        }
        datagramChannel.close();
    }
}
View Code

 

3.7.管道案例演示

NIO与网络编程系统化学习
package com.wfd360.nio;

import org.junit.Test;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

/**
 * 管道(Pipe)
 * Java NIO 管道是两个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
 * ThreadA-->sink--->source--->ThreadB
 */
public class PipeDemo {
    @Test
    public void test() throws IOException {
        //创建管道
        Pipe pipe = Pipe.open();
        //获取写入管道
        Pipe.SinkChannel sinkChannel = pipe.sink();
        //写入数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("管道测试".getBytes());
        buffer.flip();
        sinkChannel.write(buffer);
        buffer.clear();
        //获取读管道
        Pipe.SourceChannel sourceChannel = pipe.source();
        //读取数据
        ByteBuffer buffer1 = ByteBuffer.allocate(1024);
        sourceChannel.read(buffer1);
        buffer1.flip();
        System.out.println("====>" + new String(buffer1.array()));
        //关闭资源
        sourceChannel.close();
        sinkChannel.close();
    }
}
View Code

 

  完美!

上一篇:java – 从ByteBuffer读取字符串而不进行双缓冲


下一篇:Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector