高性能网络通信框架Netty-Netty客户端底层与Java NIO对应关系
5.1 Netty客户端底层与Java NIO对应关系
在讲解Netty客户端程序时候我们提到指定NioSocketChannel用于创建客户端NIO套接字通道的实例,下面我们来看NioSocketChannel是如何创建一个Java NIO里面的SocketChannel的。
首先我们来看NioSocketChannel的构造函数:
public NioSocketChannel() { this(DEFAULT_SELECTOR_PROVIDER); }
其中DEFAULT_SELECTOR_PROVIDER定义如下:
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
然后继续看
//这里的provider为DEFAULT_SELECTOR_PROVIDER public NioSocketChannel(SelectorProvider provider) { this(newSocket(provider)); }
其中newSocket代码如下:
private static SocketChannel newSocket(SelectorProvider provider) { try { return provider.openSocketChannel(); } catch (IOException e) { throw new ChannelException("Failed to open a socket.", e); } }
所以NioSocketChannel内部是管理一个客户端的SocketChannel的,这个SocketChannel就是讲Java NIO时候的SocketChannel,也就是创建NioSocketChannel实例对象时候相当于执行了Java NIO中:
SocketChannel socketChannel = SocketChannel.open();
另外在NioSocketChannel的父类AbstractNioChannel的构造函数里面默认会记录队op_read事件感兴趣,这个后面当链接完成后会使用到:
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { super(parent, ch, SelectionKey.OP_READ); }
另外在NioSocketChannel的父类AbstractNioChannel的构造函数里面设置了该套接字为非阻塞的
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent); this.ch = ch; this.readInterestOp = readInterestOp; try { ch.configureBlocking(false); } catch (IOException e) { ... } }
下面我们看Netty里面是哪里创建的NioSocketChannel实例,哪里注册到选择器的。
下面我们看下Bootstrap的connect操作代码:
public ChannelFuture connect(InetAddress inetHost, int inetPort) { return connect(new InetSocketAddress(inetHost, inetPort)); }
类似Java NIO传递了一个InetSocketAddress对象用来记录服务端ip和端口:
public ChannelFuture connect(SocketAddress remoteAddress) { ... return doResolveAndConnect(remoteAddress, config.localAddress()); }
下面我们看下doResolveAndConnect的代码:
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) { //(1) final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.isDone()) { if (!regFuture.isSuccess()) { return regFuture; } //(2) return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise()); } ... } }
首先我们来看代码(1)initAndRegister:
final ChannelFuture initAndRegister() { Channel channel = null; try { //(1.1) channel = channelFactory.newChannel(); //(1.2) init(channel); } catch (Throwable t) { ... } //(1.3) ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } }
其中(1.1)作用就是创建一个NioSocketChannel的实例,代码(1.2)是具体设置内部套接字的选项的。
代码(1.3)则是具体注册客户端套接字到选择器的,其首先会调用NioEventLoop的register方法,最后调用NioSocketChannelUnsafe的register方法:
public final void register(EventLoop eventLoop, final ChannelPromise promise) { ... AbstractChannel.this.eventLoop = eventLoop; if (eventLoop.inEventLoop()) { register0(promise); } else { try { eventLoop.execute(new Runnable() { @Override public void run() { register0(promise); } }); } catch (Throwable t) { ... } } }
其中 register0内部调用doRegister,其代码如下:
protected void doRegister() throws Exception { boolean selected = false; for (;;) { try { //注册客户端socket到当前eventloop的selector上 selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); return; } catch (CancelledKeyException e) { ... } } }
到这里代码(1)initAndRegister的流程讲解完毕了,下面我们来看代码(2)的
public final void connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { ... try { ... boolean wasActive = isActive(); if (doConnect(remoteAddress, localAddress)) { fulfillConnectPromise(promise, wasActive); } else { 。。。 } } catch (Throwable t) { ... } }
其中doConnect代码如下:
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { ... boolean success = false; try { //2.1 boolean connected = SocketUtils.connect(javaChannel(), remoteAddress); //2.2 if (!connected) { selectionKey().interestOps(SelectionKey.OP_CONNECT); } success = true; return connected; } finally { if (!success) { doClose(); } } }
其中2.1具体调用客户端套接字的connect方法,等价于Java NIO里面的。
代码2.2 由于connect 方法是异步的,所以类似JavaNIO调用connect方法进行判断,如果当前没有完成链接则设置对op_connect感兴趣。
最后一个点就是何处进行的从选择器获取就绪的事件的,具体是在该客户端套接关联的NioEventLoop里面的做的,每个NioEventLoop里面有一个线程用来循环从选择器里面获取就绪的事件,然后进行处理:
protected void run() { for (;;) { try { ... select(wakenUp.getAndSet(false)); ... processSelectedKeys(); ... } catch (Throwable t) { handleLoopException(t); } ... } }
其中select代码如下:
private void select(boolean oldWakenUp) throws IOException { Selector selector = this.selector; try { ... for (;;) { ... int selectedKeys = selector.select(timeoutMillis); selectCnt ++; ... } catch (CancelledKeyException e) { ... } }
可知会从选择器选取就绪的事件,其中processSelectedKeys代码如下:
private void processSelectedKeys() { ... processSelectedKeysPlain(selector.selectedKeys()); ... }
可知会获取已经就绪的事件集合,然后交给processSelectedKeysPlain处理,后者循环调用processSelectedKey具体处理每个事件,代码如下:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { ... try { //(3)如果是op_connect事件 int readyOps = k.readyOps(); if ((readyOps & SelectionKey.OP_CONNECT) != 0) { int ops = k.interestOps(); ops &= ~SelectionKey.OP_CONNECT; k.interestOps(ops); //3.1 unsafe.finishConnect(); } //4 if ((readyOps & SelectionKey.OP_WRITE) != 0) { ch.unsafe().forceFlush(); } //5 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }
代码(3)如果当前事件key为op_connect则去掉op_connect,然后调用NioSocketChannel的doFinishConnect:
protected void doFinishConnect() throws Exception { if (!javaChannel().finishConnect()) { throw new Error(); } }
可知是调用了客户端套接字的finishConnect方法,最后会调用NioSocketChannel的doBeginRead方法设置对op_read事件感兴趣:
protected void doBeginRead() throws Exception { ... final int interestOps = selectionKey.interestOps(); if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp); } }
这里interestOps为op_read,上面在讲解NioSocketChannel的构造函数时候提到过。
代码(5)如果当前是op_accept事件说明是服务器监听套接字获取到了一个链接套接字,如果是op_read,则说明可以读取客户端发来的数据了,如果是后者则会激活管线里面的所有handler的channelRead方法,这里会激活我们自定义的NettyClientHandler的channelRead读取客户端发来的数据,然后在向客户端写入数据。
5.2 总结
本节讲解了Netty客户端底层如何使用Java NIO进行实现的,可见与我们前面讲解的Java NIO设计的客户端代码步骤是一致的,只是netty对其进行了封装,方便了我们使用,了解了这些对深入研究netty源码提供了一个骨架指导。
原文发布时间为:2018-06-8
本文来自云栖社区合作伙伴“并发编程网 - ifeve.com”,了解相关信息可以关注“并发编程网 - ifeve.com”。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Python3 与 NetCore 基础语法对比(List、Tuple、Dict、Set专栏)
Jupyter最新版:https://www.cnblogs.com/dotnetcrazy/p/9155310.html 在线演示:http://nbviewer.jupyter.org/github/lotapp/BaseCode/blob/master/python/notebook/1.POP/3.list_tuple_dict 更新:新增Python可变Tuple、List切片、Set的扩展:https://www.cnblogs.com/dotnetcrazy/p/9155310.html#extend 今天说说List和Tuple以及Dict。POP部分还有一些如Func、IO(也可以放OOP部分说)然后就说说面向对象吧。 先吐槽一下:Python面向对象真心需要规范,不然太容易走火入魔了 -_-!!! 汗,下次再说。。。 对比写作真的比单写累很多,希望大家多捧捧场 ^_^ 进入扩展:https://www.cnblogs.com/dotnetcrazy/p/9155310.html#ext 步入正题: 1.列表相关: Python定义一个列表(列表虽然可以存不同类型,一...
- 下一篇
【Java】获取中文首字母
获取中文首字母 有时候,比如微信上根据名字拼音首字母排序并检索。那么用Java获取中文首字母是怎么实现的呢? 代码 不多说,直接上代码:(写个工具类) public class FirstCharUtil { // 简体中文的编码范围从B0A1(45217)一直到F7FE(63486) private static int BEGIN = 45217; private static int END = 63486; // 按照声 母表示,这个表是在GB2312中的出现的第一个汉字, //也就是说“啊”是代表首字母a的第一个汉字。 // i, u, v都不做声母, 自定规则跟随前面的字母 private static char[] charTable = { '啊', '芭', '擦', '搭', '蛾', '发', '噶', '哈', '哈', '击', '喀', '垃', '妈', '拿', '哦', '啪', '期', '然', '撒', '塌', '塌', '塌', '挖', '昔', '压', '匝', }; // 二十六个字母区间对应二十七个端点 // GB2312码汉字区间...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- 设置Eclipse缩进为4个空格,增强代码规范
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2全家桶,快速入门学习开发网站教程
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境