04、Netty入门 - Channel介绍及应用

Channel(通道)介绍

1、 NIO的通道类似于流,但有些区别如下;

通道可以同时进行读写,而流只能读或者只能写;

通道可以实现异步读写数据;

通道可以从缓冲读数据,也可以写数据到缓冲;

 

2、 BIO中的stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作;

3、 Channel在NIO中是一个接口publicinterfaceChannelextendsCloseable{}

4、 常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel和SocketChannel;

5、 FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写;

FileChannel类

FileChannel主要用来对本地文件进行IO操作,常见的方法有

1、 publicintread(ByteBufferdst)//从通道读取数据并放到缓冲区中;

2、 publicintwrite(ByteBuffersrc)//把缓冲区的数据写到通道中;

3、 publiclongtransferFrom(ReadableByteChannelvar1,longvar2,longvar4)//从目标通道中复制数据到当前通道;

4、 publiclongtransferTo(longvar1,longvar3,WritableByteChannelvar5)//把数据从当前通道复制给目标通道;

通道(Channel)案例1

需求:

1、使用前面学习的ByteBuffer(缓冲)和FileChannel(通道),将“hello,everybody”写入到text01.txt文件中。

2、文件不存在就创建。

 

public class NIOFileChannel01 {
    public static void main(String[] args) throws IOException {
        String str = "hello, everybody";
        // 创建一个输出流 -> channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\text01.txt");
        // 通过输出流fileOutputStream获取对应的文件channel
        FileChannel fileChannel = fileOutputStream.getChannel();
        // 创建一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 将str放入byteBuffer
        byteBuffer.put(str.getBytes());
        // 对byteBuffer进行flip
        byteBuffer.flip();
        // 将byteBuffer 数据写入到channel中
        fileChannel.write(byteBuffer);
        // 关闭流
        fileOutputStream.close();
    }
}

通道(Channel)案例2

需求:

1、使用前面学习的ByteBuffer(缓冲)和FileChannel(通道),将text01.txt文件中的数据读入到程序中,并显示在控制台屏幕。

2、假定文件已经存在。

 

public class NIOFileChannel02 {
    public static void main(String[] args) throws IOException {
        // 创建文件的输入流
        File file = new File("d:\\text01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        // 通过fileInputStream 获取对应的FileChannel -> 实际类型 FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();
        // 创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        // 将通道的数据读入到buffer中
        fileChannel.read(byteBuffer);
        // 将 byteBuffer的字节数据转成String
        System.out.println(new String(byteBuffer.array()));
        //关闭流
        fileInputStream.close();
    }
}

通道(Channel)案例3-使用一个Buffer完成文件读取

需求:

1、使用FileChannel(通道)和方法read、write,完成文件的拷贝。

2、拷贝一个文本文件1.txt,放在项目下即可。

 

public class NIOFileChannel03 {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();
        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        // 循环读取
        while (true) {
            // 重要操作, byteBuffer重置
            byteBuffer.clear();
            int read = fileChannel01.read(byteBuffer);
            if (read == -1) {// 读取结束
                break;
            }
            // 将buffer中的数据写入到fileChannel02 --> 2.txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
        // 关闭通道和流
        fileChannel01.close();
        fileChannel02.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

通道(Channel)案例4-拷贝文件tranferFrom方法

需求:

1、使用FileCHannel(通道)和方法transferFrom,完成文件的拷贝。

2、拷贝一张图片。

public class NIOFileChannel04 {
    public static void main(String[] args) throws IOException {
        // 创建相关流
        FileInputStream fileInputStream = new FileInputStream("d:\\a.png");
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.png");
        // 获取各个流对应的fileChannel
        FileChannel sourceChannel = fileInputStream.getChannel();
        FileChannel destChannel = fileOutputStream.getChannel();
        // 使用trandferFrom完成拷贝
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
        // 关闭相关通道和流
        sourceChannel.close();
        destChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}

关于Buffer和Channel的注意事项和细节

1、 ByteBuffer支持类型化的put和get,put放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有BufferUnderflowException异常;

public class NIOByteBufferPutGet {
    public static void main(String[] args) {
        // 创建一个Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);
        // 类型化方式放入数据
        byteBuffer.putInt(100);
        byteBuffer.putLong(9);
        byteBuffer.putChar('飞');
        byteBuffer.putShort((short) 4);
        // 取出
        byteBuffer.flip();
        System.out.println(byteBuffer.getShort());
        System.out.println(byteBuffer.getInt());
        System.out.println(byteBuffer.getLong());
        System.out.println(byteBuffer.getLong());
    }
}

 

1、 可以将一个普通Buffer转成只读Buffer;

public class ReadOnlyBuffer {
    public static void main(String[] args) {
        // 创建一个Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);
        for (int i = 0; i < 64; i++) {
            byteBuffer.put((byte) i);
        }
        // 读取
        byteBuffer.flip();
        // 得到一个只读的Buffer
        ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());
        while (readOnlyBuffer.hasRemaining()){
            System.out.println(readOnlyBuffer.get());
        }
        // 此处报ReadOnlyBufferException异常 
        readOnlyBuffer.put((byte) 500);
    }
}

1、 NIO还提供了MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成;

/**
 * 说明:
 * 1.MappedByteBuffer可以让文件直接在内存(堆外内存)中修改,操作系统不需要拷贝一次
 */
public class MappedByteBufferTest {
    public static void main(String[] args) throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        // 获取对应的通道
        FileChannel channel = randomAccessFile.getChannel();
        /**
         * 参数1:FileChannel.MapMode.READ_WRITE,使用的是读写模式
         * 参数2: 0,代表可以修改的起始位置
         * 参数3: 5,是映射到内存的大小,即将文件1.txt的多少个字节映射到内存
         * 可以直接修改的范围就是 0-5
         * 实际类型 DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
        mappedByteBuffer.put(0, (byte) 'H');
        mappedByteBuffer.put(3, (byte) '9');
        // 关闭
        randomAccessFile.close();
        System.out.println("修改成功");
    }
}

1、 NIO还支持通过多个Buffer(即Buffer数组)完成读写操作,即Scattering和Gatering;

/**
 * Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入
 * Gathering:从buffer读取数据时,可以采用buffer数组,依次读
 */
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws IOException {
        // 使用ServerSocketChannel 和 SocketChannel网络
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
        // 绑定端口到Socket,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);
        // 创建Buffer数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);
        // 等待客户端连接(telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 假定接收从客户端接收8个字节
        int messageLength = 8;
        // 循环的读取
        while (true) {
            int byteRead = 0;
            while (byteRead < messageLength) {
                long readCount = socketChannel.read(byteBuffers);
                // 累计读取的字节数
                byteRead += readCount;
                System.out.println("byteRead=" + byteRead);
                // 使用流打印, 看看当前的这个buffer的position和limit
                Arrays.asList(byteBuffers).stream()
                        .map(buffer -> "position=" + buffer.position() + " ,limit=" + buffer.limit())
                        .forEach(System.out::println);
                // 将所有的buffer进行反转
                Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
                // 将数据读出,显示到客户端
                long byteWrite = 0;
                while (byteWrite < messageLength) {
                    long writeCount = socketChannel.write(byteBuffers);
                    byteWrite += writeCount;
                }
                // 将所有的buffer进行clear操作
                Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());
                System.out.println("byteRead=" + byteRead + " ,byteWrite=" + byteWrite + ", messageLength=" + messageLength);
            }
        }
    }
}

使用telnet进行测试,打开cmd命令行窗口,连接命令:telnet 127.0.0.1 7000

发送消息快捷键 ctrl+]