Java NIO

NIO通信模型

JDK1.4引入,目的是让Java支持非阻塞I/O。与Socket和ServerSocket相对应,NIO提供了SocketChannel和ServerSocketChannel两种套接字通道实现,并且支持阻塞和非阻塞两种模式。下面介绍NIO的一些概念和功能。

1. 缓冲区Buffer

Buffer是一个对象,任何时候访问NIO中的数据,都要通过缓冲区进行操作,它包含了一些要写入或要读出的数据。缓冲区本质上是一个数组,除此之外,缓冲区还提供了对数据的结构化访问以及维护读写位置(limit)等信息。


如上图,基本上每一个种Java基本类型都有对应的缓冲区,以方便网络读写。

2. 通道Channel

网络数据通过Channel进行读取和写入。它与流的区别在于,流只在一个方向上移动(InputStream或OutputStream),而通道可以同时进行读写操作。

3. 多路复用器Selector

Selector会轮询注册在其上的Channel, 如果某个Channel发生了读或写事件,这个Channel就会处于就绪状态,会被Selector轮询出来,然后通过SelectionKey获取就绪的Channel的集合,进行后续的I/O操作。由于Selector基于epoll实现,只需要一个线程负责Selector的轮询就可以接入成千上万的客户端。


通过代码看下NIO使用的流程。

服务端时序图


package com.netty.server.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by hao.g on 18/5/8.
 */
public class NioServer implements Runnable {
    private Selector selector;

    public NioServer(int port){
        try {
            //打开ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

            //绑定监听端口,设置连接为非阻塞模式
            serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
            serverSocketChannel.configureBlocking(false);

            //创建reactor线程,创建多路复用器
            selector = Selector.open();

            //将ServersocketChannel注册到reactor线程的多路复用器selector上,监听Accept事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true){
            try {
                selector.select(2000);
                System.out.println("服务端轮询中...");

                //selector轮询就绪的key
                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = (SelectionKey) iterator.next();
                    iterator.remove();
                    if (selectionKey.isValid()){

                        //处理新接入的请求信息
                        if (selectionKey.isAcceptable()){

                            //接收新的连接
                            ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                            SocketChannel sc = ssc.accept();
                            sc.configureBlocking(false);

                            //新连接注册读事件到selector
                            sc.register(selector, SelectionKey.OP_READ);
                        }

                        if (selectionKey.isReadable()){

                            //读数据
                            SocketChannel sc = (SocketChannel) selectionKey.channel();
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            int index = sc.read(byteBuffer);
                            if (index > 0){

                                //flip之后从写模式转为读模式
                                byteBuffer.flip();
                                byte[] bytes = new byte[byteBuffer.remaining()];
                                byteBuffer.get(bytes);
                                System.out.println(new String(bytes, "UTF-8"));


                                //回写客户端
                                byte[] recallByte = "send a message to client".getBytes();
                                ByteBuffer writeBuffer = ByteBuffer.allocate(recallByte.length);
                                writeBuffer.put(recallByte);
                                writeBuffer.flip();
                                sc.write(writeBuffer);
                            }else if (index < 0){
                                selectionKey.cancel();
                                sc.close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int port = 9090;
        new Thread(new NioServer(port)).start();
    }
}

  • 启动一个线程,创建多路复用器Selector和ServerSocketChannel
  • 绑定本地端口,设置ServerSocketChannel为非阻塞,将ServerSocketChannel注册到selector上,监听SelectionKey.OP_ACCEPT事件
  • run方法循环遍历seletor,每隔2s被唤醒一次。当有事件就绪的时候,返回SelectionKey集合,进行相关事件的处理
  • 如果是accept事件,创建SocketChannel实例,并注册到selector上
  • 如果是read事件,通过byteBuffer读取数据,返回值index有三种结果index大于0,读取到字节;等于0,正常情况,无数据;小于0,链路已经关闭,需要释放资源。
  • 最后server发送一条信息应答客户端

客户端时序图

package com.netty.im.client.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by hao.g on 18/5/8.
 */
public class NioClient implements Runnable {
    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean running = false;

    public NioClient(String host, int port){
        try {
            this.host = host;
            this.port = port;
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            boolean connected = socketChannel.connect(new InetSocketAddress(host, port));

            //如果连接成功, 发送数据
            if (connected){
                socketChannel.register(selector, SelectionKey.OP_READ);
                writeMsg(socketChannel);
            }else {
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
            }

            while (!running){
                selector.select(1000);
                System.out.println("客户端轮询...");

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isValid()){
                        SocketChannel sc = (SocketChannel) selectionKey.channel();

                        //连接成功
                        if (selectionKey.isConnectable()){
                            if (sc.finishConnect()){
                                sc.register(selector, SelectionKey.OP_READ);
                                writeMsg(socketChannel);
                            }
                        }

                        if (selectionKey.isReadable()){
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            int index = sc.read(byteBuffer);
                            if (index > 0){
                                byteBuffer.flip();
                                byte[] bytes = new byte[byteBuffer.remaining()];
                                byteBuffer.get(bytes);
                                System.out.println(new String(bytes, "UTF-8"));
                                running = true;
                            }else if (index < 0){
                                selectionKey.cancel();
                                sc.close();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void writeMsg(SocketChannel sc) throws IOException {
        byte[] msg = "this is a client message".getBytes();
        ByteBuffer byteBuffer = ByteBuffer.allocate(msg.length);
        byteBuffer.put(msg);
        byteBuffer.flip();
        sc.write(byteBuffer);
    }

    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 9090;
        new Thread(new NioClient(host, port)).start();
    }
}

  • 初始化NIO的多路复用器和SocketChannel对象,设置为非阻塞模式
  • 向服务端发起连接请求。如果连接成功,将SocketChannel注册到Selector上,注册SelectionKey.OP_READ事件;如果没有返回握手应答消息,注册SelectionKey.OP_CONNECT事件,当服务端返回TCP的sync-ack消息后,Selector会轮询到连接就绪事件,进行服务端的连接
  • 如果当前SelectionKey处于连接状态,注册SelectionKey.OP_READ,向服务端发送消息;如果当前SelectionKey处于可读状态,读取数据
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值