• 盘一盘 NIO (二)—— Channel解析


    Channel是个啥?

    Channel,顾名思义,它就是一个通道。NIO中的所有IO都是从 Channel 开始的。
    Channel通道和流非常类似,主要有以下几点区别:
    1、流是单向的,通道是双向的,可读可写。
    2、流读写是阻塞的,通道可以异步读写。
    3、流中的数据可以选择性的先读到缓存中,通道的数据总是要先读到一个缓存Buffer中,或从缓存Buffer中写入。
     

    继承关系图

    Channel有两种分类方式。一种是按同步Channel和异步Channel划分,还有一种是按功能划分。
    后面我们会主要讲解Channel的几个重要实现,如下所示:
    FileChannel: 从文件中读写数据
    DatagramChannel: 通过UDP读写网络中的
    SocketChannel: 通过TCP读写网络中的,一般是客户端实现
    ServerSocketChannel: 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现
     
     

    接口方法

    public interface Channel extends Closeable {
        
        // 判断Channel的开关状态
        public boolean isOpen();
    
        // 关闭此Channel
        public void close() throws IOException;
    
    }

    FileChanel

    FileChannel是一个连接到文件的通道,可以通过文件通道读写文件。众所周知文件通道总是阻塞式的,因此FileChannel无法设置为非阻塞模式。
    FileChannel中重要方法,read、write通过其实现类FileChannelImpl实现
        // 从这个通道读入一个字节序列到给定的缓冲区
        public abstract int read(ByteBuffer dst) throws IOException;
    
        // 从这个通道读入指定开始位置和长度的字节序列到给定的缓冲区
        public abstract long read(ByteBuffer[] dsts, int offset, int length)
            throws IOException;
    
        // 从这个通道读入一个字节序列到给定的缓冲区
        public final long read(ByteBuffer[] dsts) throws IOException {
            return read(dsts, 0, dsts.length);
        }
    
    
        // 从给定的缓冲区写入字节序列到这个通道
        public abstract int write(ByteBuffer src) throws IOException;
    
           // 从给定缓冲区的子序列向该信道写入字节序列
        public abstract long write(ByteBuffer[] srcs, int offset, int length)
            throws IOException;
    
           // 从给定的缓冲区写入字节序列到这个通道
        public final long write(ByteBuffer[] srcs) throws IOException {
            return write(srcs, 0, srcs.length);
        }

    FileChannelImpl中read、write方法实现

    // read 方法实现
    public int read(ByteBuffer var1) throws IOException {
        this.ensureOpen();
        if (!this.readable) {
            throw new NonReadableChannelException();
        } else {
            synchronized(this.positionLock) {
                int var3 = 0;
                int var4 = -1;
    
                try {
                    this.begin();
                    var4 = this.threads.add();
                    if (!this.isOpen()) {
                        byte var12 = 0;
                        return var12;
                    } else {
                        do {
                            var3 = IOUtil.read(this.fd, var1, -1L, this.nd);
                        } while(var3 == -3 && this.isOpen());
    
                        int var5 = IOStatus.normalize(var3);
                        return var5;
                    }
                } finally {
                    this.threads.remove(var4);
                    this.end(var3 > 0);
    
                    assert IOStatus.check(var3);
                }
            }
        }
    }
    
    // write方法实现
    public int write(ByteBuffer var1) throws IOException {
        this.ensureOpen();
        if (!this.writable) {
            throw new NonWritableChannelException();
        } else {
            synchronized(this.positionLock) {
                int var3 = 0;
                int var4 = -1;
    
                byte var5;
                try {
                    this.begin();
                    var4 = this.threads.add();
                    if (this.isOpen()) {
                        do {
                            var3 = IOUtil.write(this.fd, var1, -1L, this.nd);
                        } while(var3 == -3 && this.isOpen());
    
    
                        int var12 = IOStatus.normalize(var3);
                        return var12;
                    }
                    var5 = 0;
                } finally {
                    this.threads.remove(var4);
                    this.end(var3 > 0);
    
                    assert IOStatus.check(var3);
                }
                return var5;
            }
        }
    }

    IOUtil中read、write实现:

    //  read方法实现
    static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
        if (var1.isReadOnly()) {
            throw new IllegalArgumentException("Read-only buffer");
        } else if (var1 instanceof DirectBuffer) {
            return readIntoNativeBuffer(var0, var1, var2, var4);
        } else {
            // 申请一块和缓存同大小的ByteBuffer var5
            ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
    
            int var7;
            try {
                // 读取数据到缓存,底层由NativeDispatcher的read实现。
                int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
                var5.flip();
                if (var6 > 0) {
                    // 把数据读取到var1(用户定义的缓存,在jvm中分配内存)
                    var1.put(var5);
                }
    
                var7 = var6;
            } finally {
                Util.offerFirstTemporaryDirectBuffer(var5);
            }
    
            return var7;
        }
    }
    
    // write 方法实现
    static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
        if (var1 instanceof DirectBuffer) {
            return writeFromNativeBuffer(var0, var1, var2, var4);
        } else {
            int var5 = var1.position();
            int var6 = var1.limit();
            assert var5 <= var6;
    
            int var7 = var5 <= var6 ? var6 - var5 : 0;
            // 申请一块ByteBuffer,大小为byteBuffer中的limit - position
            ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7);
    
            int var10;
            try {
                // 复制byteBuffer中的数据
                var8.put(var1);
                var8.flip();
                var1.position(var5);
    
                // 把数据写入到文件,底层由NativeDispatcher的write实现
                int var9 = writeFromNativeBuffer(var0, var8, var2, var4);
                if (var9 > 0) {
                    var1.position(var5 + var9);
                }
                var10 = var9;
            } finally {
                Util.offerFirstTemporaryDirectBuffer(var8);
            }
            return var10;
        }
    }
     

    小结

    1、文件通道不能直接创建,只能通过InputStream、OutputStream或RandomAccessFile等创建对应的文件通道
    2、文件通道FileChannel从缓冲区中读取数据,使用read方法
    3、文件通道FileChannel的read方法只能读ByteBuffer缓冲区
     

    DatagramChannel

    DatagramChannel,使用UDP协议来进行传输。由于不需要建立连接,其实没有客户端服务端的概念,为了便于理解,我们定义其中一端为客户端,一端为服务端

    客户端

    public static void main(String[] args) throws Exception {
        // 打开DatagramChannel
        DatagramChannel datagramChannel = DatagramChannel.open();
        // 绑定一个端口发送数据
        ByteBuffer byteBuffer = ByteBuffer.wrap("A".getBytes());
        int byteSent = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 8000));
        System.out.println("Byte sent is: " + byteSent);
    }

    服务端

    public static void main(String[] args) throws Exception { 
        // 打开DatagramChannel,绑定一个端口
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.socket().bind(new InetSocketAddress(8000));
        
        while (true) {
            // 接收数据并输出
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            datagramChannel.receive(byteBuffer);
            byteBuffer.flip();
            if(byteBuffer.hasRemaining()) {
                System.out.print((char) byteBuffer.get());
            }
        }
    }

    ServerSocketChannel和SocketChannel

    ServerSocketChannel是一个可以监听新进来的TCP连接的通道。ServerSocketChannel本身不具备传输数据的能力,而只是负责监听传入的连接和创建新的SocketChannel。
    SocketChannel是一个连接到TCP网络套接字的通道。通常SocketChannel在客户端向服务器发起连接请求,每个SocketChannel对象创建时都关联一个对等的Socket对象。同样SocketChannel也可以运行在非阻塞模式下。
    可以通过以下2种方式创建SocketChannel:
    1、打开一个SocketChannel并连接到互联网上的某台服务器
    2、一个新连接到达ServerSocketChannel时,会创建一个SocketChannel
     

    服务端

    public static void main(String[] args) throws Exception {
        // 服务端首先打开ServerSocketChannel,然后绑定一个端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8000));
    
        // 服务端ServerSocketChannel收到连接请求时,返回一个SocketChannel对象
        SocketChannel socketChannel = serverSocketChannel.accept();
        while(true) {
            // 把数据从channel中读出来,然后写入到buffer中然后打印
            ByteBuffer buffer = ByteBuffer.allocate(128);
            socketChannel.read(buffer);
            buffer.flip();
            if(buffer.hasRemaining()) {
                System.out.println((char) buffer.get());
            }
        }
    }

    客户端

    public static void main(String[] args) throws Exception {
        // 客户端建立连接的过程,首先打开SocketChannel,然后连接到服务端
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));
    
        //连接是否建立成功 
        boolean isConnect = socketChannel.isConnected();
        
        while (true) {
            // 通过buffer,向channel中写入数据
            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.clear();
            buffer.put(("A").getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            Thread.sleep(1000);
        }
    }
  • 相关阅读:
    dubbo
    maven
    vue
    SSM框架整合
    MyBatis数据表注解开发
    MyBatis多表操作xml方式
    MyBatis映射配置文件
    Mybatis核心配置文件,传统开发和代理开发(主流)
    SpringMVC高级
    SpringMVC基础
  • 原文地址:https://www.cnblogs.com/LemonFive/p/11406408.html
Copyright © 2020-2023  润新知