NIO笔记(二)之Channel

JDK版本

  1. jdk8

Channel

  1. Channel概念

    • 数据的源头或者数据的目的地
    • 用于向 buffer 写入数据或者读取 buffer 数据
    • 异步I/O支持–包含socketfilepipe三种管道,都是全双工的通道
  2. 如果一个Channel实现了Interrupted接口,那么当他被阻塞,并且发生中断的时候,那么该Channel将会被中断,线程会抛出一个ClosedByInterruptException异常,如果一个线程的状态是中断,他试图访问一个Channel,那么Channel将立即被关闭。

  3. Channel可以以阻塞(blocking)或者非阻塞(nonblocking)模式运行,非阻塞模式的Channel永远不会让调用的线程休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作,不是所有通道都可以使用非阻塞模式

  4. Channel分为两类:FileChannelSocket通道,包括SocketChannelServerSocketChannelDatagramChannel(UDP)Socket通道可以直接通过工厂方法创建,但是一个FileChannel只能通过一个打开的RandomAccessFileFileInputStreamFileOutputStream对象上调用getChannel()方法来获取

    //Socket通道
    SocketChannel sc = SocketChannel.open( );
    ServerSocketChannel ssc = ServerSocketChannel.open( );
    DatagramChannel dc = DatagramChannel.open( ); 
    //文件通道
    RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
    FileChannel fc = raf.getChannel( );
    

Channel读写

  1. 正常情况下,一个写操作会在全部数据写入后返回,但是在某些特殊的通道(Socket的非阻塞模式)中,因为缓冲区满,数据有可能部分写入或者没有写入。所以从这里来说,通道的写不是线程安全的,因为有可能想写入10B的数据,但是只写入了5B,那下次线程调度的时候,就有可能写乱数据
  2. 因为读/写本身就是阻塞式的,同时只有一个线程可以操作,但是读/写都不是线程安全的,所以一般一个通道也都只有一个线程读,一个线程写,或者一个线程把读/写都做了
  3. readwrite方法并不一定会操作和ByteBuffer容量相等的数据,这两个方法会返回这期间操作的字节数,因此在应用端需要判断,如果仅仅进行部分传输,需要重新进行传输,使用hasReminding()方法判断是否还有数据

Channel关闭

  1. Channel不能被重复利用,一个打开的Channel即代表与一个特定的I/O服务的特定连接,并封装该连接的状态。当Channel关闭时,那个连接会丢失,然后Channel将不再连接任何东西。
  2. Channel调用通道的close()方法时,可能会导致在Channel关闭底层I/O服务的过程中线程暂时阻塞(即使Channel处于非阻塞模式)。通道关闭的阻塞行为(如果有的话)是高度取决于操作系统或文件系统的(Socket通道关闭会花费较长时间,具体时耗取决于操作系统的网络实现。在输出内容被提取时,一些网络协议堆栈可能会阻塞通道的关闭,在通道关闭之后,正在进行的read或者write操作会收到一个AsynchronousCloseException)。在一个通道上多次调用close()方法是没有坏处的,但是如果第一个线程在close()方法中阻塞,那么在它完成关闭通道之前,任何其他调用close()方法都会阻塞,后续在已关闭的Channel上调close()方法不会产生任何操作,只会立即返回
  3. 关闭一个已经注册的SelectableChannel需要两个步骤
    • 取消注册的key,这个可以通过SelectionKey.cancel()方法,也可以通过SelectableChannel.close()方法,或者中断阻塞在该channel上的IO操作的线程来做到。

    • 后续的Selector.selectXXX()方法的调用才真正地关闭本地Socket。

    • 因而,如果在取消SelectionKey后没有调用到selectorselect()方法(因为Client一般在取消key后, 我们都会终止调用select的循环,当然,server关闭一个注册的Channel我们是不会终止select循环的),那么本地socket将进入CLOSE-WAIT 状态(等待本地Socket关闭)。简单的解决办法是在 SelectableChannel.close()方法之后调用Selector.selectNow()方法,类似:

      Selector sel;
      SocketChannel sch;
      // …
      sch.close();
      sel.selectNow();
      Netty在超过256连接关闭的时候主动调用一次selectNow
      

SocketChannel

  1. SocketSocketChannel类封装点对点、有序的网络连接。每个SocketChannel对象创建时都是和一个对等的Socket对象关联的。

  2. 连接过程

    • connect还未被调用,此时抛出NoConnectionPendingException
    • 正在连接,但是连接未完成,finishConnect会立即返回false
    • 非阻塞模式下,调用connect之后,SocketChannel可以调用configureBlocking()来切换回阻塞模式,这时候调用finishConnect()会阻塞直到连接建立完成
    • 如果连接已经建立,那么调用finishConnect()方法会返回true,什么也不发生
  3. OP_CONNECT事件,表示连接通道连接就绪或者发生了错误,会被加到ready中.即这个事件发生时不能简单的认为连接成功了,要使用finishConnect()判断

    //新创建的Channel都是未连接的
    SocketChannel channel = SocketChannel.open();
    //设置该SocketChannel以非阻塞方式工作
    channel.configureBlocking(false);
    /**
    调用connect进行连接
        * 在阻塞模式下,线程在连接建立好或超时之前会保持阻塞
        * 非阻塞模式下,没有超时参数,会发起连接请求,并且立即返回,如果是true,则说明连接已经建立(本地环回连接);如果不能连接,立即返回false,并且异步的继续尝试连接,这时候isConnectPending()会返回true(连接等待状态),
    **/
    channel.connect(addr);
    while(!sc.finishConnect()){
        System.out.println("正在尝试连接");
    }
    //注册连接事件,连接就会就绪
    channel.register(selector, SelectionKey.OP_CONNECT);
    
    //连接成功finishConnect()返回true
    if (key.isValid() && key.isConnectable()) {
        SocketChannel ch = (SocketChannel) key.channel();
        if (ch.finishConnect()) {
            // Connect successfully
            // key.interestOps(SelectionKey.OP_READ);
        } else {
            // Connect failed
        }
    }
    

ServerSocketChannel

创建

  1. ServerSocketChannel是一个面向流的监听SocketSelectableChannelServerSocketChannel是一个抽象类,即不能直接实例化,可以通过自身的静态方法open()创建实例,open()方法作用是打开服务端Socket通道,新创建的服务端Socket通道最初是未绑定的,在接收连接之前,必须通过它的bind()方法将其绑定到具体的地址和端口

  2. 静态open方法

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

阻塞模式

  1. accept()方法的作用是接收通道Socket的连接。如果ServerSocketChannel是非阻塞模式,那么accept()方法将直接返回null。如果是阻塞模式,在新的连接可用或者发生I/O错误之前会无限地阻塞。
    @Test
    public void testBlockServer() throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        int backlog = 50;
        int port = 8888;
        //true 默认是阻塞模式
        System.out.println(serverSocketChannel.isBlocking());
        serverSocketChannel.bind(new InetSocketAddress("localhost", port), backlog);
        System.out.println("start:" + System.currentTimeMillis());
        //阻塞模式下:在新的连接可用或者发生I/O错误之前会无限期地阻塞它
        //使用telnet 127.0.0.1 8888 ,此时accept()立即返回
        SocketChannel socketChannel = serverSocketChannel.accept();
        System.out.println("end:" + System.currentTimeMillis());
        socketChannel.close();
        serverSocketChannel.close();
    }
    
    启动输出:
    true
    start:1551490993132
    使用telnet 127.0.0.1 8888 后输出:
    end:1551490999952
    
    @Test
    public void testNotBlockServer() throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        int backlog = 50;
        int port = 8888;
        //true
        System.out.println(serverSocketChannel.isBlocking());
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //false
        System.out.println(serverSocketChannel.isBlocking());
        serverSocketChannel.bind(new InetSocketAddress("localhost", port), backlog);
        System.out.println("start:" + System.currentTimeMillis());
        //非阻塞模式下:在不存在挂起的连接时,accept方法将直接返回 null
        SocketChannel socketChannel = serverSocketChannel.accept();
        System.out.println("end:" + System.currentTimeMillis());
        if (socketChannel != null) {
            socketChannel.close();
        } else {
            // null
            System.out.println(socketChannel);
        }
        serverSocketChannel.close();
    }
    
    输出:
    true
    false
    begin:1551452354670
    end:1551452354673
    null
    

选项

  1. Socket Option

    @Test
    public void testChannelOption() throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        SocketChannel socketChannel = SocketChannel.open();
        //获取支持的Socket Option
        Set<SocketOption<?>> socketOptions = serverSocketChannel.supportedOptions();
        //[SO_RCVBUF, IP_TOS, SO_REUSEADDR]
        System.out.println(socketOptions);
        //[IP_TOS, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, SO_OOBINLINE, SO_KEEPALIVE, SO_LINGER, SO_REUSEADDR]
        socketOptions = socketChannel.supportedOptions();
        System.out.println(socketOptions);
    
        serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 65535);
        socketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 65535);
        //65535
        System.out.println(serverSocketChannel.getOption(StandardSocketOptions.SO_RCVBUF));
        //65535
        System.out.println(socketChannel.getOption(StandardSocketOptions.SO_RCVBUF));
    
    }
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值