Java NIO

Java NIO

  以前写过一篇Java Socket的用法,不过觉得介绍的不够细致也不够全面,因此今天想在细谈一下Java NIO,也算是对上一篇博客的补充吧。在以前的博客中提到Java NIO的三个核心部分Buffers、Channels、Selectors,这里不再赘述三者之间的关系,接下来我们重点看看这三个核心部分。

Buffer

  该区域本质是一块可以读写的数据的内存区,这组内存区被包装成NIO Buffer对象,并提供了一组方法,方便访问该块内存。为了更清楚的理解Buffer的工作原理,需要熟悉它的三个属性capacity、position、limit。capacity表示缓冲区大小。而position和limit的含义取决于Buffer处在读模式还是写模式下。在读模式下,position表示开始读的位置,limit表示最后能读的数据位置。在写模式下,position表示当前数据需要写入的位置,最大值为capacity-1。当由写模式切换到读模式时,position=0,limit=position。

  抽象类Buffer具体实现类有ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。接下来我们以ByteBuffer为例来了解一下Buffer的具体用法。

public class TestByteBuffer {
    public static void test(){
        readFromChannel();   //从channel读取数据到Buffer中
        readFromPut();   //put方法放入数据
    }
    public static void readFromChannel(){
        try {
            RandomAccessFile aFile = new RandomAccessFile("data/byte.txt","rw");
            FileChannel channel = aFile.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(64);  //设置buffer缓冲区的大小
            int bytesRead = channel.read(buffer);   //read to buffer
            while(bytesRead != -1){
                System.out.println("write mode position is " + buffer.position());
                System.out.println("write mode limit is " + buffer.limit());
                buffer.flip();  //切换到读模式,limit=posit,position=0,
                System.out.println("Read mode position is " + buffer.position());
                System.out.println("Read mode limit is " + buffer.limit());
                while(buffer.hasRemaining()){
                    System.out.print((char)buffer.get());  //1byte的读数据
                }
                System.out.println();
                buffer.clear();  //将position设置为0,limit设置成capacity值
                bytesRead = channel.read(buffer);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void readFromPut(){
        ByteBuffer buffer = ByteBuffer.allocate(48);
        for(int i = 0; i < 12; i++){
            buffer.putInt(i);
        }
        buffer.flip();
        while(buffer.hasRemaining()){
            System.out.print(buffer.get());
        }
    }
}
View Code

Channels

  Channel充当的其实是搬运工的角色,它负责把数据搬运到Buffer中,也可以从Buffer中把数据搬运出去。具体的实现Channel接口的类有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。FileChannel从文件中读取数据到缓冲区(已经在Buffer中介绍过了),DatagramChannel能通过UDP读写网络中的数据,SocketChannel能通过TCP读写网络中的数据,ServerSocketChannel可以监听新进来的TCP连接,对每个新进来的连接都会创建一个SocketChannel。如下是利用SocketChannel和ServerSocketChannel实现客户端和服务器端(IP地址192.168.6.42)通信:

public class Server {
    public static void main(String[] args){
        try {
            //创建一个ServerSocketChannel
            ServerSocketChannel ssc = ServerSocketChannel.open();
            //监听8080端口
            ssc.socket().bind(new InetSocketAddress(8080));
            SocketChannel socketChannel = ssc.accept();  //阻塞,开始监听8080端口
            ByteBuffer buffer = ByteBuffer.allocate(100); //设置buffer的capacity为100
            int readBytes = socketChannel.read(buffer);  //利用channel将数据写入buffer
            while(readBytes != -1){
                buffer.flip();  //切换为读模式
                while(buffer.hasRemaining()){ //检查buffer是否读完
                    System.out.print((char)buffer.get());  //1byte的读数据
                }
                buffer.clear();  //清空buffer缓冲区
                readBytes = socketChannel.read(buffer);
            }
            socketChannel.close(); //关闭socketChannel
            ssc.close();  //关闭ServerSocketChannel
            System.out.println("It it over");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

 客户端程序如下:

public class TestSocketChannel {
    public static void main(String args){
        SocketChannel socketChannel;
        try {
            //创建SocketChannel
            socketChannel = SocketChannel.open();
            //连接到某台服务器的某个端口
            socketChannel.connect(new InetSocketAddress("192.168.6.42",8080));
            String sendString = "This is a message from client, Please read it carefully. Thanke you very much";
            ByteBuffer buffer = ByteBuffer.wrap(sendString.getBytes());
socketChannel.write(buffer);
//关闭通道 socketChannel.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

Selector

  Selector是Java NIO中能够检测到一到多个NIO通道,并能够知晓通道是否为诸如读写时间做好准备的组件。这样就可以实现一个单独的线程可以管理多个Channel,从而管理多个网络连接。我们从如下服务器端和客户端的程序介绍Selector吧。

客户端程序如下,首先是一个创建SocketChannel的类如下:

public class TestSocketChannel {
    /**
     * 创建一个SocketChannel,其中指定连接的IP地址和端口号
     */
    public static SocketChannel createSocketChannel(String ip, int port){
        SocketChannel socketChannel = null;
        try {
            //创建SocketChannel
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);  //设置为非阻塞模式
            //连接到某台服务器的某个端口
            socketChannel.connect(new InetSocketAddress(ip,port));
            //判断是否连接完成,若未完成则等待连接
            while(!socketChannel.finishConnect()){
                System.out.println("It is connecting>>>>>>>>>>>>>>>>>>>>>");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //连接完成返回该SocketChannel
        return socketChannel;
    }
}

客户端主程序通过调用该类的createSocketChannel()方法创建一个SocketChannel对象,主程序如下:

public class Main {
    public static void main(String[] args){
        try {
            //创建SocketChannel,连接192.168.6.42服务器的8080端口
            SocketChannel sc8080 = TestSocketChannel.createSocketChannel("192.168.6.42",8080);
            
            //创建SocketChannel,连接192.168.6.42服务器的8090端口
            SocketChannel sc8090 = TestSocketChannel.createSocketChannel("192.168.6.42",8090);
            
             //创建selector
            Selector selector = Selector.open();
            //向通道注册选择器,并设置selector监听Channel时对读操作感兴趣
            sc8080.register(selector, SelectionKey.OP_READ);
            sc8090.register(selector, SelectionKey.OP_READ);
            //启动线程,监听是否从服务器端有数据传过来
            Thread thread = new Thread(new MyRunnable(selector));
            thread.start();
            //分别向服务器的8080和8090端口发送数据
            sendString(sc8080,"This message is going to send to server 8080 port");
            sendString(sc8090,"This message is going to send to server 8090 port");
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    private static void sendString(SocketChannel sc, String str){
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        try {
            //将buffer中的数据写入sc通道
            sc.write(buffer);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
class MyRunnable implements Runnable{
    private Selector selector;
    public MyRunnable(Selector s){
        this.selector =s;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            while(true){
                //阻塞2000ms,判断是否有通道在注册的事件上就绪了,如果有则该返回值就绪通道的个数
                if(selector.select(2000) == 0){
                    System.out.println("please waiting.....");
                    continue;
                }else{
                    //当有通道就绪时,获取SelectionKey,并遍历
                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                    while(keys.hasNext()){
                        SelectionKey key = keys.next();
                        //判断通道中是否可读事件就绪了,如果是则isReadable()方法返回TRUE
                        if(key.isReadable()){
                            SocketChannel socketChannel = (SocketChannel) key.channel();
                            //默认服务器端发送的数据都小于1024byte,因此一次可以读完
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            socketChannel.read(buffer);  //利用通道将数据读入buffer中
                            buffer.flip();   //将buffer切换为读模式
                            String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
                            System.out.println(receiveString);
                            buffer.clear();  //清空缓冲区buffer
                        }
                        //设置通道对什么时间感兴趣,该设置是对“读”和“写”感兴趣
                        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                        //移除当前已经处理过的SelectionKey
                        keys.remove();
                    }
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

服务器端程序如下:

public class TestServerSocketChannel {
    public ServerSocketChannel createServerSocketChannel(int port){
        ServerSocketChannel ssc = null;
        try {
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(port));
            ssc.configureBlocking(false);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return ssc;
    }
}
public class Server {
    private static Selector selector = null;
    public static void main(String[] args){
        
        try {
            //创建一个ServerSocketChannel,监听8080端口,非阻塞模式
            ServerSocketChannel ssc8080 = (new TestServerSocketChannel()).createServerSocketChannel(8080);
            //创建一个ServerSocketChannel,监听8090端口,非阻塞模式
            ServerSocketChannel ssc8090 = (new TestServerSocketChannel()).createServerSocketChannel(8090);
            //创建监听器
            selector = Selector.open();
            //向通道注册监听器
            ssc8080.register(selector, SelectionKey.OP_ACCEPT);
            ssc8090.register(selector, SelectionKey.OP_ACCEPT);
            //开启线程,监听客户端发送过来的数据
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static class MyRunnable implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                try {
                    //阻塞3s后判断selector注册的通道是否有就绪的
                    if(selector.select(3000) == 0){
                        System.out.println("正在等待请求......");
                        continue;
                    }else{
                        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                        while (keys.hasNext()) {
                            SelectionKey key = keys.next();
                            //判断是否有新的连接
                            if (key.isAcceptable()) {
                                HandleRequest.handleAccept(key);
                            } else if (key.isReadable()) { //判断是否有读操作
                                HandleRequest.handleRead(key);
                            } else if (key.isValid() && key.isWritable()) {  //判断是否对写操作感兴趣
                                HandleRequest.handleWrite(key);
                            }
                            keys.remove(); // 移除处理过的键
                        }
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
public class HandleRequest {
    //当有新的连接时
    public static void handleAccept(SelectionKey key){
        try {
            //通过SelectionKey对象key创建SocketChannel对象
            SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
            //设置socketChannel为非阻塞模式
            socketChannel.configureBlocking(false);
            //向通道注册选择器和感兴趣事件
            socketChannel.register(key.selector(), SelectionKey.OP_READ);
            //输出数据从服务器的哪个端口传入
            System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void handleRead(SelectionKey key){
        SocketChannel socketChannel = (SocketChannel) key.channel();
        System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
        ByteBuffer buffer = ByteBuffer.allocate(32);
        try {
            int readBytes = socketChannel.read(buffer);
            //输出数据从哪个客户端地址传入
            System.out.println("receive data from " + socketChannel.socket().getRemoteSocketAddress() + ", the data are ");
            //读取缓冲区中的数据
            while(readBytes != 0){
                buffer.flip();
                String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
                System.out.print(receiveString);
                buffer.clear();
                readBytes = socketChannel.read(buffer);
            }
            //更改通道感兴趣的事件为“读操作”和“写操作”
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void handleWrite(SelectionKey key){
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer writeBuffer = ByteBuffer.wrap("This message is from server".getBytes());
        try {
            socketChannel.write(writeBuffer);
            System.out.println("The message is writen in channel");
            key.interestOps(SelectionKey.OP_READ);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
原文地址:https://www.cnblogs.com/zhanglei93/p/6653923.html