NIO基础详解

Netty 是基于Java NIO 封装的网络通讯框架,只有充分理解了 Java NIO 才能理解好Netty的底层设计。Java NIO 由三个核心组件组件:

  • Buffer

  • Channel

  • Selector

缓冲区 Buffer

Buffer 是一个数据对象,我们可以把它理解为固定数量的数据的容器,它包含一些要写入或者读出的数据。

在 Java NIO 中,任何时候访问 NIO 中的数据,都需要通过缓冲区(Buffer)进行操作。读取数据时,直接从缓冲区中读取,写入数据时,写入至缓冲区。NIO 最常用的缓冲区则是 ByteBuffer。下图是 Buffer 继承关系图:

每一个 Java 基本类型都对应着一种 Buffer,他们都包含这相同的操作,只不过是所处理的数据类型不同而已。

通道 Channel

Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 这根水管读取和写入。传统的 IO 是基于流进行操作的,Channle 和类似,但又有些不同:

区别通过Channel
支持异步不支持支持
是否可双向传输数据不能,只能单向可以,既可以从通道读取数据,也可以向通道写入数据
是否结合 Buffer 使用必须结合 Buffer 使用
性能较低较高

正如上面说到的,Channel 必须要配合 Buffer 一起使用,我们永远不可能将数据直接写入到 Channel 中,同样也不可能直接从 Channel 中读取数据。都是通过从 Channel 读取数据到 Buffer 中或者从 Buffer 写入数据到 Channel 中,如下:

简单点说,Channel 是数据的源头或者数据的目的地,用于向 buffer 提供数据或者读取 buffer 数据,并且对 I/O 提供异步支持。

下图是 Channel 的类图(http://blog.csdn.net/tsyj810883979/article/details/6876594)

Channel 为最顶层接口,所有子 Channel 都实现了该接口,它主要用于 I/O 操作的连接。定义如下:

 
  1. public interface Channel extends Closeable {


  2.    /**

  3.     * 判断此通道是否处于打开状态。

  4.     */

  5.    public boolean isOpen();


  6.    /**

  7.     *关闭此通道。

  8.     */

  9.    public void close() throws IOException;


  10. }

最为重要的Channel实现类为:

  • FileChannel:一个用来写、读、映射和操作文件的通道

  • DatagramChannel:能通过 UDP 读写网络中的数据

  • SocketChannel: 能通过 TCP 读写网络中的数据

  • ServerSocketChannel:可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel

多路复用器 Selector

多路复用器 Selector,它是 Java NIO 编程的基础,它提供了选择已经就绪的任务的能力。从底层来看,Selector 提供了询问通道是否已经准备好执行每个 I/O 操作的能力。简单来讲,Selector 会不断地轮询注册在其上的 Channel,如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

Selector 允许一个线程处理多个 Channel ,也就是说只要一个线程复杂 Selector 的轮询,就可以处理成千上万个 Channel ,相比于多线程来处理势必会减少线程的上下文切换问题。下图是一个 Selector 连接三个 Channel :

实例

服务端

 
  1. public class NIOServer {


  2.    /*接受数据缓冲区*/

  3.    private ByteBuffer sendbuffer = ByteBuffer.allocate(1024);

  4.    /*发送数据缓冲区*/

  5.    private  ByteBuffer receivebuffer = ByteBuffer.allocate(1024);


  6.    private Selector selector;


  7.    public NIOServer(int port) throws IOException {

  8.        // 打开服务器套接字通道

  9.        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

  10.        // 服务器配置为非阻塞

  11.        serverSocketChannel.configureBlocking(false);

  12.        // 检索与此通道关联的服务器套接字

  13.        ServerSocket serverSocket = serverSocketChannel.socket();

  14.        // 进行服务的绑定

  15.        serverSocket.bind(new InetSocketAddress(port));

  16.        // 通过open()方法找到Selector

  17.        selector = Selector.open();

  18.        // 注册到selector,等待连接

  19.        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

  20.        System.out.println("Server Start----:");

  21.    }


  22.    //

  23.    private void listen() throws IOException {

  24.        while (true) {

  25.            selector.select();

  26.            Set<SelectionKey> selectionKeys = selector.selectedKeys();

  27.            Iterator<SelectionKey> iterator = selectionKeys.iterator();

  28.            while (iterator.hasNext()) {

  29.                SelectionKey selectionKey = iterator.next();

  30.                iterator.remove();

  31.                handleKey(selectionKey);

  32.            }

  33.        }

  34.    }


  35.    private void handleKey(SelectionKey selectionKey) throws IOException {

  36.        // 接受请求

  37.        ServerSocketChannel server = null;

  38.        SocketChannel client = null;

  39.        String receiveText;

  40.        String sendText;

  41.        int count=0;

  42.        // 测试此键的通道是否已准备好接受新的套接字连接。

  43.        if (selectionKey.isAcceptable()) {

  44.            // 返回为之创建此键的通道。

  45.            server = (ServerSocketChannel) selectionKey.channel();

  46.            // 接受到此通道套接字的连接。

  47.            // 此方法返回的套接字通道(如果有)将处于阻塞模式。

  48.            client = server.accept();

  49.            // 配置为非阻塞

  50.            client.configureBlocking(false);

  51.            // 注册到selector,等待连接

  52.            client.register(selector, SelectionKey.OP_READ);

  53.        } else if (selectionKey.isReadable()) {

  54.            // 返回为之创建此键的通道。

  55.            client = (SocketChannel) selectionKey.channel();

  56.            //将缓冲区清空以备下次读取

  57.            receivebuffer.clear();

  58.            //读取服务器发送来的数据到缓冲区中

  59.            count = client.read(receivebuffer);

  60.            if (count > 0) {

  61.                receiveText = new String( receivebuffer.array(),0,count);

  62.                System.out.println("服务器端接受客户端数据--:"+receiveText);

  63.                client.register(selector, SelectionKey.OP_WRITE);

  64.            }

  65.        } else if (selectionKey.isWritable()) {

  66.            //将缓冲区清空以备下次写入

  67.            sendbuffer.clear();

  68.            // 返回为之创建此键的通道。

  69.            client = (SocketChannel) selectionKey.channel();

  70.            sendText="message from server--";

  71.            //向缓冲区中输入数据

  72.            sendbuffer.put(sendText.getBytes());

  73.            //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位

  74.            sendbuffer.flip();

  75.            //输出到通道

  76.            client.write(sendbuffer);

  77.            System.out.println("服务器端向客户端发送数据--:"+sendText);

  78.            client.register(selector, SelectionKey.OP_READ);

  79.        }

  80.    }


  81.    /**

  82.    * @param args

  83.    * @throws IOException

  84.    */

  85.    public static void main(String[] args) throws IOException {

  86.        int port = 8080;

  87.        NIOServer server = new NIOServer(port);

  88.        server.listen();

  89.    }

  90. }

客户端

 
  1. public class NIOClient {

  2.    /*接受数据缓冲区*/

  3.    private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024);

  4.    /*发送数据缓冲区*/

  5.    private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024);


  6.    public static void main(String[] args) throws IOException {

  7.        // 打开socket通道

  8.        SocketChannel socketChannel = SocketChannel.open();

  9.        // 设置为非阻塞方式

  10.        socketChannel.configureBlocking(false);

  11.        // 打开选择器

  12.        Selector selector = Selector.open();

  13.        // 注册连接服务端socket动作

  14.        socketChannel.register(selector, SelectionKey.OP_CONNECT);

  15.        // 连接

  16.        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));


  17.        Set<SelectionKey> selectionKeys;

  18.        Iterator<SelectionKey> iterator;

  19.        SelectionKey selectionKey;

  20.        SocketChannel client;

  21.        String receiveText;

  22.        String sendText;

  23.        int count=0;


  24.        while (true) {

  25.            //选择一组键,其相应的通道已为 I/O 操作准备就绪。

  26.            //此方法执行处于阻塞模式的选择操作。

  27.            selector.select();

  28.            //返回此选择器的已选择键集。

  29.            selectionKeys = selector.selectedKeys();

  30.            //System.out.println(selectionKeys.size());

  31.            iterator = selectionKeys.iterator();

  32.            while (iterator.hasNext()) {

  33.                selectionKey = iterator.next();

  34.                if (selectionKey.isConnectable()) {

  35.                    System.out.println("client connect");

  36.                    client = (SocketChannel) selectionKey.channel();

  37.                    // 判断此通道上是否正在进行连接操作。

  38.                    // 完成套接字通道的连接过程。

  39.                    if (client.isConnectionPending()) {

  40.                        client.finishConnect();

  41.                        System.out.println("完成连接!");

  42.                        sendbuffer.clear();

  43.                        sendbuffer.put("Hello,Server".getBytes());

  44.                        sendbuffer.flip();

  45.                        client.write(sendbuffer);

  46.                    }

  47.                    client.register(selector, SelectionKey.OP_READ);

  48.                } else if (selectionKey.isReadable()) {

  49.                    client = (SocketChannel) selectionKey.channel();

  50.                    //将缓冲区清空以备下次读取

  51.                    receivebuffer.clear();

  52.                    //读取服务器发送来的数据到缓冲区中

  53.                    count=client.read(receivebuffer);

  54.                    if(count>0){

  55.                        receiveText = new String( receivebuffer.array(),0,count);

  56.                        System.out.println("客户端接受服务器端数据--:"+receiveText);

  57.                        client.register(selector, SelectionKey.OP_WRITE);

  58.                    }


  59.                } else if (selectionKey.isWritable()) {

  60.                    sendbuffer.clear();

  61.                    client = (SocketChannel) selectionKey.channel();

  62.                    sendText = "message from client--";

  63.                    sendbuffer.put(sendText.getBytes());

  64.                    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位

  65.                    sendbuffer.flip();

  66.                    client.write(sendbuffer);

  67.                    System.out.println("客户端向服务器端发送数据--:"+sendText);

  68.                    client.register(selector, SelectionKey.OP_READ);

  69.                }

  70.            }

  71.            selectionKeys.clear();

  72.        }

  73.    }

  74. }

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值