【NIO】(2) — Channel

                                                                 【箴3:3不可使慈爱、诚实离开你,要系在你颈项上,刻在你心版上。 


  • 应用

在 nio 中数据基本都是通过 Channel 操作 Buffer 实现的,不会直接将数据写入到 Channel。

Channel ---read---> Buffer      从通道读到缓冲区

Channel <--write--- Buffer      向通道写入数据

  •  Channel 接口

package java.nio.channels;
import java.io.IOException;
import java.io.Closeable;
public interface Channel extends Closeable {

    /**
     * 判断通道是否打开
     */
    public boolean isOpen();

    /**
     * 关闭通道
     */
    public void close() throws IOException;

}
  • Channel 实现


  • ServerSocketChannel

服务端用于监听新进来的TCP连接的通道,每一个监听到的链接都会以 SocketChannel 对象返回

  • SocketChannel

客户端发起TCP连接的网络套接字通道

  • DatagramChannel

可用于UDP 连接发送数据包的通道

  • FileChannel

进行文件数据处理的通道

  • ServerSocketChannel

/**
 *  用于面向流的侦听套接字的可选通道。
 *  可供多个并发线程安全使用。
 */
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{

    protected ServerSocketChannel(SelectorProvider provider) {
        super(provider);
    }
    public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }

    /**
     * 返回标识此通道支持的操作的操作集
     */
    public final int validOps() {
        return SelectionKey.OP_ACCEPT;
    }

    // -- ServerSocket-specific operations --

    /**
     * 将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
     */
    public final ServerSocketChannel bind(SocketAddress local) throws IOException{
        return bind(local, 0);
    }

    /**
     * 将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
     * 用于在套接字和本地地址建立链接。一旦建立了关联,那么套接字将保持绑定状态,直到通道关闭。 
     * @param   backlog 套接字上挂起连接的最大数目
     */
    public abstract ServerSocketChannel bind(SocketAddress local, int backlog)
        throws IOException;

    /**
     * 设置操作项
     */
    public abstract <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
        throws IOException;

    /**
     * 检索与此通道关联的服务器套接字。
     */
    public abstract ServerSocket socket();

    /**
     * 接受与此频道的套接字建立的连接。
     * [1]如果此通道处于非阻塞模式,则如果没有挂起的连接,则此方法将立即返回<tt>null<tt>
     * [2]否则,它将无限期阻塞,直到新连接可用或发生I/O错误。
     * 无论此通道的阻塞模式如何,此方法返回的 SocketChannel(如果有)都将处于阻塞模式。
     */
    public abstract SocketChannel accept() throws IOException;

    @Override
    public abstract SocketAddress getLocalAddress() throws IOException;

}
  • 阻塞模式 demo
public class ServerSocketChannelDemo {
    public static void main(String[] args) throws Exception{
        
    // 打开(创建)ServerSocketChannel
        ServerSocketChannel server = ServerSocketChannel.open();
    // 绑定监听地址:ip 默认是本机
        server.bind(new InetSocketAddress(8080));
        while (true) {
    // 开始监听,默认是阻塞模式
            SocketChannel accept = server.accept();
    // do something .......
        }
    }
}
  • 非阻塞模式 demo
public class ServerSocketChannelDemo {
    public static void main(String[] args) throws Exception{

        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(8080));
    // 设置为非阻塞模式
        server.configureBlocking(false);
        while (true) {
    // 开始监听,非阻塞模式下会不管存不存在 socket,都是直接返回,可能会为 null
            SocketChannel accept = server.accept();
            // 所以在这里要做非空判断,才继续业务逻辑
            if (null != accept) {
                // do something .......
            }
        }
    }
}
  • SocketChannel

/**
 * 用于面向流的连接套接字的可选通道。
 *【1】可通过 #open 方法创建。新创建的套接字通道已打开,但尚未连接。
 *    尝试对未连接的通道调用I/O操作将导致抛出@link notyetconnectedException。
 *    通过调用 #connect 方法可以连接套接字通道;一旦连接,套接字通道将保持连接状态,直到关闭为止。
 *    可以通过调用其 #isconnected方法来确定套接字通道是否已连接。
 *
 * 【2】套接字通道支持非阻塞连接:
 *      可以创建套接字通道,并通过 #connect 启动建立到远程套接字的链接的过程,以便稍后通过#finishConnect 完成。
 *      连接操作是否正在进行可以通过调用 #isconnectionPending 来确定。
 *
 * 【3】套接字通道支持异步关闭,这类似于@link channel类中指定的异步关闭操作。
 *    如果一个线程关闭了一个套接字的输入端,而另一个线程在套接字通道上的读取操作中被阻塞,则被阻塞线程中的读取操作将在不读取任何字节的情况下完成,并将返回 -1
 *    如果套接字的输出端被一个线程关闭,而另一个线程在套接字的写入操作中被阻塞,被阻塞的线程将收到一个@link asynchronicuscloseexception。
 *
 * 
 *
 *    套接字通道可供多个并发线程安全使用。
 *    它们支持并发读写,尽管在任何给定的时间内,至多有一个线程在读,至多有一个线程在写。
 *    #connect 和 #finishConnect 相互同步,当调用其中一个方法时,试图启动读或写操作将被阻止,直到调用完成。
 *
 */

public abstract class SocketChannel extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {

        protected SocketChannel(SelectorProvider provider) {
        super(provider);
    }

    /**
     * Opens a socket channel.
     */
    public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }

    /**
     * Opens a socket channel and connects it to a remote address.
     * @param  remote
     *         The remote address to which the new channel is to be connected
     */
    public static SocketChannel open(SocketAddress remote) throws IOException {
        SocketChannel sc = open();
        try {
            sc.connect(remote);
        } catch (Throwable x) {
            try {
                sc.close();
            } catch (Throwable suppressed) {
                x.addSuppressed(suppressed);
            }
            throw x;
        }
        assert sc.isConnected();
        return sc;
    }

    /**
     * Returns an operation set identifying this channel's supported operations.
     * <p> Socket channels support connecting, reading, and writing
     */
    public final int validOps() {
        return (SelectionKey.OP_READ
                | SelectionKey.OP_WRITE
                | SelectionKey.OP_CONNECT);
    }


    // -- Socket-specific operations --

    @Override
    public abstract SocketChannel bind(SocketAddress local)
        throws IOException;

    @Override
    public abstract <T> SocketChannel setOption(SocketOption<T> name, T value)
        throws IOException;

    /**
     * 关闭读取连接,而不关闭通道。一旦关闭读取,通道上的进一步读取将返回@code-1,即流结束指示。
     */
    public abstract SocketChannel shutdownInput() throws IOException;

    /**
     * 关闭写入连接而不关闭通道。
     */
    public abstract SocketChannel shutdownOutput() throws IOException;

    /**
     * 检索与此通道关联的套接字。
     */
    public abstract Socket socket();

    /**
     * 判断此通道的网络套接字是否已连接。
     */
    public abstract boolean isConnected();

    /**
     * 判断此通道上是否正在进行连接操作。
     */
    public abstract boolean isConnectionPending();

    /**
     * 连接此通道的套接字
     * 如果此通道处于非阻塞模式,则此方法的调用将启动非阻塞连接操作。如果立即建立连接(本地连接可能会发生这种情况),则此方法返回 true。否则,此方法返回 false,稍后必须通过调用 #finishConnect完成连接操作。
     * 如果此通道处于阻塞模式,则此方法的调用将阻塞,直到建立连接或发生I/O错误。
     */
    public abstract boolean connect(SocketAddress remote) throws IOException;

    public abstract boolean finishConnect() throws IOException;

    /**
     * Returns the remote address to which this channel's socket is connected.
     */
    public abstract SocketAddress getRemoteAddress() throws IOException;


    // -- ByteChannel operations --
    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);
    }
    @Override
    public abstract SocketAddress getLocalAddress() throws IOException;

}
  • SocketChannel 读写

打开 -> 连接到服务器

        // 打开
        SocketChannel socketChannel = SocketChannel.open();
        // 连接到服务器
        socketChannel.connect(new InetSocketAddress(“192.168.3.172”,8080));

#read -> 从该通道读取数据

        // socketChannel 只能通过 Buffer 操作数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 把数据从 socketChannel 读到 buffer,返回值是读取的字节数
        int read = socketChannel.read(buffer);

#write -> 向该通道写入数据

        // 准备缓冲区数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();
        buffer.put("hello nio socket channel".getBytes());
        buffer.flip();//切换模式
        // 写入数据到 socketChannel
        while (buffer.hasRemaining()) {// 放在 while 里面,知道把bufffer 中的数据写完
            socketChannel.write(buffer);
        }
  • 非阻塞 SocketChannel 读写

 #connect

非阻塞模式下的 connect 方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。

        // 打开
        SocketChannel socketChannel = SocketChannel.open();
        // 设置非阻塞模式
        socketChannel.configureBlocking(false);
        // 连接到服务器
        socketChannel.connect(new InetSocketAddress(8080));
        // 要判断是否完成连接
        if (socketChannel.finishConnect()) {
            
        }

#read 和 #write  

同样的非阻塞模式的下的 #read 和 #write 都有可能在数据尚未处理完成时就返回了。所以:

#read 读出数据时可判断返回值是否为 -1 ,确定数据流是否已经读完。

#write 同上放在 while() 里面处理。

SocketChannel & Selector

非阻塞模式通常配合 Selector 使用,将多个 SocketChannel 注册到 Selector,然后Selector 轮询处理准备好数据的通道。

  • DatagramChannel

一个用于接收发送 UDP 数据包的通道。UDP是无连接状态的协议,所以只能以数据包的形式用于数据的网络传输。

#bind 

// 打开
DatagramChannel channel = DatagramChannel.open();
// 接口UDP下8080端口的数据包
channel.socket().bind(new InetSocketAddress(8080));

#receive

通过此通道接收数据报。
如果此通道是阻塞模式,那么数据报只有在可用时,才会复制到给定的字节缓冲区,并返回其源地址。
如果此通道处于非阻塞模式,而数据报不是立即可用,然后此方法立即返回空。

ByteBuffer buf = ByteBuffer.allocate(1024);
buf.clear();
channel.receive(buf);

#send

通过此通道发送数据包。

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(1024);
buf.clear();
buf.put(“hello nio DatagramSocketChannel”.getBytes());
buf.flip();
// 向指定服务器指定的UDP端口,发送数据包
int bytesSent = channel.send(buf, new InetSocketAddress("192.168.7.176", 8080));
  • FileChannel

操作文件的读写通道。

        RandomAccessFile file = new RandomAccessFile("/Users/***/test.txt","rw");
        // 获取文件通道
        FileChannel channel = file.getChannel();

#read 

        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 从文件通道读取数据到缓冲区
        channel.read(buffer);

#write 

        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.clear();
        buffer.put("hello file channel".getBytes());
        // buffer切换模式
        buffer.flip();
        // 向 fileChannel 写数据
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }

                                                                                                      左手圣经,右手Java

                                                                                                灵魂指导思想,思想更新技术

                                                                                        一个对圣经感兴趣的Java菜鸟 ? -> 留言指导哦?亲
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值