java NIO(二)

本节通过几个实例来简单介绍一下四个channel的用法

参考链接: http://ifeve.com/java-nio-all/

FileChannel

传统IO的处理方式

public class Test {

    public static void main(String[] args) {

        FileInputStream fis = null;

        try {

            fis = new FileInputStream("/Users/alibaba/vipsrv-logs/vipclient.log");

            byte[] buffer = new byte[1024];

            int length = fis.read(buffer);

            while (length != -1) {

                for (int i = 0; i < length; i++) {

                    System.out.print((char) buffer[i]);

                }

                length = fis.read(buffer);

            }

        } catch (Exception e) {

        } finally {

            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {

            }
        }
    }

}

NIO的处理方式

public class Test {

    public static void main(String[] args) {

        FileInputStream fis = null;

        try {

            fis = new FileInputStream("/Users/alibaba/vipsrv-logs/vipclient.log");

            FileChannel fileChannel = fis.getChannel();

            ByteBuffer  byteBuffer = ByteBuffer.allocate(1024);

            int length = fileChannel.read(byteBuffer);

            System.out.println(length);

            while (length != -1) {

                byteBuffer.flip();

                while(byteBuffer.hasRemaining()){

                    System.out.print((char)byteBuffer.get());
                }

                byteBuffer.compact();

                length = fileChannel.read(byteBuffer);
            }

        } catch (Exception e) {

        } finally {

            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {

            }
        }
    }

}

注意此处要说明的是FileChannel是阻塞式的

SocketChannel

NIO的强大之处就是对channel的非阻塞的控制,套接字是经常会被阻塞的,比如accept就会一直等待连接而阻塞,read也会一直等待读取数据而阻塞。NIO正是通过对channel的阻塞控制的动态设置来实现channel的非阻塞模式。

channel.configureBlocking(false)

将通道设置为非阻塞式的,非阻塞式的通道上调用一个方法会立刻返回,返回的结果就是调用的请求处理的完成度。

比如我们在ServerSocketChannel上调用accept方法会立刻得到返回结果,如果有链接则返回一个客户端的SocketChannel,如果没有结果则返回一个null

下面以TCP为例,客户端采用NIO,服务端采用IO实现

public class Client {

    public static void client() {

        System.out.println("启动客户端!");

        SocketChannel socketChannel = null;

        try {

            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));

            ByteBuffer buffer = ByteBuffer.allocate(1024);

            if (socketChannel.finishConnect()) {

                while (true) {

                    String string = "I am client server!";
                    buffer.clear();
                    buffer.put(string.getBytes());

                    buffer.flip();

                    while (buffer.hasRemaining()) {

                        socketChannel.write(buffer);
                        System.out.println(buffer);

                    }

                }

            }

        } catch (Exception e) {

        } finally {

            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                }
            }
        }

    }

    /**
     * 
     * @param args
     */
    public static void main(String[] args) {

        client();
    }

}

````

服务器






<div class="se-preview-section-delimiter"></div>

public class Server {

public static void server() {

    System.out.println("启动服务器!");

    ServerSocket socket = null;
    InputStream in = null;

    try {

        socket = new ServerSocket(8000);
        byte[] resvBuf = new byte[1024];

        while (true) {

            Socket ss = socket.accept();

            SocketAddress address = ss.getRemoteSocketAddress();

            System.out.println("接收到客户端请求:" + address);

            in = ss.getInputStream();
            int size = 0;
            while ((size = in.read(resvBuf)) != -1) {

                System.out.print(resvBuf.toString());
            }
        }

    } catch (Exception e) {

    } finally {

        try {

            if (socket != null) {
                socket.close();
            }

            if (in != null) {
                in.close();
            }

        } catch (IOException e) {
        }
    }

}

/**
 * 
 * @param args
 */
public static void main(String[] args) {

    server();
}

}


TCP服务器端的NIO

比如像QQ、微信这种聊天工具,每天登陆的人很多,但是真正聊天有IO操作的很少。很早之前我们的做法是逐个轮询客户端,哪个客户端有IO操作就创建一个线程去执行操作,这种方式资源开销过大,且很多时候轮询程序都是在空跑,毕竟有操作的占少数。

而对于select来说对于这种连接多而操作少的场景好处就很多,首先我们创建一个线程去执行select,一个select会绑定或者轮询一组通道,这些通道首先要注册到select上。然后当通道有IO操作请求时,操作系统会将通道的状态置为就绪状态,这个时候select就会拿到这个通道,然后将这个通道塞进线程池去执行。

这种方式大大减少了系统开销,首先一个线程就可以轮询一批通道,而通道是否就绪由底层操作系统来做,select只需要判断是否由就绪的通道,有则将通道塞进对应的线程池开始执行。





<div class="se-preview-section-delimiter"></div>

public class ServerSocketTest {

public static void selector() {

    Selector selector = null;
    ServerSocketChannel ssc = null;

    try {

        selector = Selector.open();
        ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        ssc.socket().bind(new InetSocketAddress(8000));
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {

            if (selector.select(1000) == 0) {

                System.out.println("轮询等待!");
                continue;
            }

            Iterator<SelectionKey> sIterator = selector.selectedKeys().iterator();

            while (sIterator.hasNext()) {

                SelectionKey key = sIterator.next();
                if (key.isAcceptable()) {

                    handleAccept(key);
                } else if (key.isConnectable()) {

                } else if (key.isReadable()) {

                    handleRead(key);
                } else if (key.isWritable()) {

                    handleWrite(key);
                }

                sIterator.remove();
            }

        }

    } catch (Exception e) {

    } finally {

        if (selector != null) {
            try {
                selector.close();

                if (ssc != null) {

                    ssc.close();
                }
            } catch (IOException e) {
            }
        }
    }

}

/**
 * @throws IOException 
 * 
 */
private static void handleWrite(SelectionKey key) throws IOException {

    SocketChannel socketChannel = (SocketChannel) key.channel();
    ByteBuffer buffer = (ByteBuffer) key.attachment();

    buffer.flip();
    while (buffer.hasRemaining()) {

        socketChannel.write(buffer);
    }

    buffer.compact();

}

/**
 * @throws IOException 
 * 
 */
private static void handleRead(SelectionKey key) throws IOException {

    SocketChannel socketChannel = (SocketChannel) key.channel();
    ByteBuffer buffer = (ByteBuffer) key.attachment();

    long size = socketChannel.read(buffer);
    while (size > 0) {

        buffer.flip();

        while (buffer.hasRemaining()) {

            System.out.println((char) buffer.get());
        }

        buffer.compact();
        size = socketChannel.read(buffer);
    }

    if (socketChannel != null) {

        socketChannel.close();
    }

}

/**
 * @throws IOException 
 * 
 */
private static void handleAccept(SelectionKey key) throws IOException {

    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    SocketChannel socketChannel = ssc.accept();
    socketChannel.configureBlocking(false);
    socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
}

/**
 * 
 * @param args
 */
public static void main(String[] args) {

    selector();
}

}

“`

Selector的创建:Selector selector = Selector.open();

为了将Channel和Selector配合使用,必须将Channel注册到Selector上
SelectableChannel.register()

与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

  1. Connect
  2. Accept
  3. Read
  4. Write

通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

这四种事件用SelectionKey的四个常量来表示:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

interest集合
ready集合
Channel
Selector
附加的对象(可选)
interest集合:就像向Selector注册通道一节中所描述的,interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合。

ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。可以这样访问ready集合:

int readySet = selectionKey.readyOps();

可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

从SelectionKey访问Channel和Selector很简单。如下:

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();

可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

还可以在用register()方法向Selector注册Channel的时候附加对象。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

通过Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

下面是select()方法:

int select()
int select(long timeout)
int selectNow()

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。

select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值