Netty详解之一:IO模型与Java NIO

Netty是一个网络IO框架,想要掌握Netty,必须要对于TCP协议、Socket这些基础知识有一个了解。这方面的经典书籍莫过于《UNIX网络编程卷1:套接字联网API》和《TCP-IP详解卷1》。当然,没有这么深厚的基础知识,也不妨碍你使用Netty。

由于Netty是Java领域的框架,因此本系列文章在涉及相关概念时,会优先使用java领域的说法。因此有可能在某些地方显得不够严谨,这首先是本人水平有限所致,有时也是有意为之。

IO模型

在Java网络编程领域,我们通常有三种网络IO模型:bio(Blocking io),nio(non-blocking io),aio。

bio:同步阻塞模型

所有的IO操作都是阻塞的,所以服务端必须为每个连接创建一个线程,实现简单;不能胜任存在大量连接的业务场景。

nio:同步非阻塞模型

也叫selector模型,所有操作都是同步的,但可用一个selector来查询多个连接,哪个链接有响应处理哪个,能够避免阻塞地等待网络事件,一个线程能同时为多个socket连接服务。

aio:异步响应模型

完全的异步响应式模型,底层操作系统做了很多事情,上层程序只需等待系统的响应回调即可;比较新(java 7开始支持),目前运用还不是特别广泛;

虽然java实现了以上3种模型,旦总体来说,IO模型是偏底层的API,在实现具体业务时,仍然需要开发者做大量网络相关的工作。netty刚好帮我们做了这部分工作,它不仅仅对底层IO模型做了封装,更以reactor模式为蓝本,实现了一个完整、易用的网络应用框。

reactor模型的知识大家自行搜索。

虽然Netty底层使用的socket接口随平台而异(Mac OS上可以使用kqueue,linux上使用epoll),但java nio是最典型的方式;在学习Netty的时候,建议大家先聚焦于Netty nio相关部分。

java NIO简介

先简单介绍一下java nio相关的知识。

核心类型:

java nio有三个核心角色:Channel,Buffer,Selector

  • Channel:对socket连接的抽象;
  • Buffer:channel和socket之间增加一个缓冲区,实现非阻塞;
  • Selector:selector管理一组channel的状态变化,并选择有事件发生channel进行处理。

nio buffer:

buffer是一个内存块,用于业务代码和nio之间传递数据,所有的buffer类型都有4个属性(定义在抽象基类Buffer中):

  • capacity:数据容量,创建时指定,不能修改;
  • limit:当前操作模式下的有效终点位置,也即操作不能超过该位置;
  • position:下一次操作(读、写)的位置,满足约束position<limit;
  • mark:记住当前的position,已备后续恢复(有点备忘录模式的思想);

Buffer的设计追求性能,所以它的接口可读性有点差,使用buffer必须充分理解它的内部结构和工作方式。

nio channel

一个抽象的IO读写通道,它一般基于底层流来工作,常用的实现有:FileChannel,DatagramChannel(UDP),ServerSocketChannel,SocketChannel。

nio selector:

selector管理多个channel,也叫多路复用选择器;当一个channel有事件发生的时候,selector能够发现它并处理它;所以就达到了用一个线程处理多个channel的效果,减少了系统开销。

Selector是一个抽象类,核心方法:

  • open():静态工厂方法,打开一个选择器;
  • selectXXX():监控所有注册的channel,查看是否有事件发生;
  • wakeup():唤醒一个阻塞的selector(比如调用了阻塞的select方法);

SelectionKey

SelectionKey代表channel和selector之间的注册关系,在channel注册时创建并返回给使用者。SelectionKey内部有两个状态集合(bit位表示),一个代表selector应当检测的channel状态集合(注册时指定);一个代表selector当前处于ready的channel状态。不同的channel支持的操作是不同的。

这里所说的“状态“,是指channel当前可以执行的操作,比如“可读、可写“,状态变化又称之为“事件”;因此在不同的语义场景下,“状态”、“操作”、“事件”这几个词可能被交替使用。

NIO示例

用一个简单回响程序来展示java nio的用法,服务端代码如下:

public class NIOServer {
    public static void main(String[] args) throws IOException {
    	  
    	 //打开channel,绑定地址
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(6666));
        serverSocketChannel.configureBlocking(false);

		 //打开Selector,并注册channel,这里关注OP_ACCEPT事件
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            if (selector.select() > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey key : selectionKeys) {
                    if (key.isAcceptable()) {
                    	//说明有连接请求进来
                        ServerSocketChannel channel = (ServerSocketChannel) key.channel();			
                        //接收连接,并将新socketChannel注册到selector
                        SocketChannel clientChannel = channel.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                    	//新客户端连接有数据进来,读取并打印
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        channel.read(buffer);
                        buffer.flip();

                        String text = new String(buffer.array(), 0, buffer.limit()).trim();
                        System.out.println("接受到数据:" + text);
                        channel.close();
                    }
                }
                selectionKeys.clear();
            }
        }
    }
}

客户端代码如下

public class NIOClient {
    public static void main(String[] args) throws IOException {
    
    	 //打开一个SocketChannel
        SocketChannel clientChannel = SocketChannel.open();
        clientChannel.configureBlocking(false);

		 //连接服务端
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        if (!clientChannel.connect(inetSocketAddress)) {
            while (!clientChannel.finishConnect()) {
                System.out.println("连续尚未完成");
            }
        }
		
		 //打开Selector,并注册channel,这里只关注OP_WRITE(可写)事件
        Selector selector = Selector.open();
        clientChannel.register(selector, SelectionKey.OP_WRITE);

        if (selector.select() > 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey key : selectionKeys) {
                if (key.isWritable()) {
                		//向服务端发送一句话
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.wrap("Hello Netty".getBytes());
                    channel.write(buffer);
                }
            }
            selectionKeys.clear();
        }
        clientChannel.close();
    }
}

我们学习Netty并不需要先完整地掌握java nio,但是能看懂上面的代码,了解java nio的工作方式还是必要的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值