Netty5客户端源码解析
Netty5客户端源码解析
今天来分析下netty5的客户端源码,示例代码如下:
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by yaojiafeng on 16/1/17.
*/
public class SimpleClient {
public void connect(int port, String host) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup(1);
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new SimpleClientHandler());
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 当代客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8081;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new SimpleClient().connect(port, "127.0.0.1");
}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* Created by yaojiafeng on 16/1/17.
*/
public class SimpleClientHandler extends ChannelHandlerAdapter {
/**
* Creates a client-side handler.
*/
public SimpleClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf message = Unpooled.copiedBuffer("hello world".getBytes());
ctx.writeAndFlush(message);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf body = (ByteBuf) msg;
byte[] bytes = new byte[body.readableBytes()];
body.readBytes(bytes);
System.out.println(new String(bytes));
}
}
构造Bootstrap对象
设置事件循环组为NioEventLoopGroup,设置channel为NioSocketChannel,设置一些socket配置项,比如ChannelOption.TCP_NODELAY,设置自定义的ChannelHandler发起异步连接
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
内部具体操作如下:
2.1 参数校验
执行validate方法,EventLoopGroup、channelFactory、ChannelHandler这几个基本字段不能为空2.2 initAndRegister方法
异步执行初始化和注册方法,反射构造初始化构造Bootstrap时,设置的NioSocketChannel对象,NioSocketChannel包含的具体字段上篇Netty5服务端源码解析有讲解。构造NioSocketChannel后,调用init方法初始化NioSocketChannel,包括设置ChannelHandler到管道里,设置ChannelOption到socket。然后异步注册NioSocketChannel到NioEventLoopGroup里的NioEventLoop。2.2.1 异步注册NioSocketChannel到NioEventLoop
最终委派调用到channel.unsafe().register(this, promise);
NioSocketChannel里的内部类Unsafe的register方法,然后启动NioEventLoop单线程事件循环内部获取Task并执行,register0方法,内部会调用doRegister方法,注册SocketChannel到NioSocketChannel关联的唯一NioEventLoop的selector上,并加上att为NioSocketChannel。
selectionKey = javaChannel().register(((NioEventLoop) eventLoop().unwrap()).selector, 0, this);
2.2.2 safeSetSuccess方法激活doConnect0方法
刚开始进入的doResolveAndConnect方法会调用doConnect方法,如果异步注册已经完成则直接调用doConnect0方法,否则给ChannelFuture增加监听方法,由channel注册完成后驱动调用doConnect0方法,一般情况下都是通过监听器驱动的。接下来分析doConnect0方法。private static void doConnect0(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelFuture regFuture,
final ChannelPromise connectPromise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
connectPromise.setFailure(regFuture.cause());
}
}
});
}
这个方法也是增加task到NioEventLoop里,内部执行逻辑为,注册成功的情况下调用channel.connect(remoteAddress, connectPromise)方法,它会通过管道链,一路串行调用到unsafe.connect(remoteAddress, localAddress, promise)方法。调用doConnect方法
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
javaChannel().socket().bind(localAddress);
} boolean success = false;
try {
boolean connected = javaChannel().connect(remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
调用JDK NIO的API,因为是异步连接,返回的connected是false,所以后面会设置SelectionKey的感兴趣事件为SelectionKey.OP_CONNECT,为了后续的NioEventLoop的事件循环可以获取CONNECT激活的SelectionKey做后续的连接操作。
2.2.3 完成连接
从SingleThreadEventExecutor的asRunnable到processSelectedKeys到processSelectedKeysOptimized到processSelectedKey方法,判断激活的操作为SelectionKey.OP_CONNECT。if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); unsafe.finishConnect();
}
重新设置感兴趣事件为0,并且调用unsafe.finishConnect方法,内部调用doFinishConnect方法完成最终连接。
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
然后再调用fulfillConnectPromise方法,内部继续调用pipeline().fireChannelActive()方法
public ChannelPipeline fireChannelActive() {
head.fireChannelActive(); if (channel.config().isAutoRead()) {
channel.read();
} return this;
}
head.fireChannelActive走管道链,channel.read()方法也走管道链,最终调用unsafe.beginRead()方法,然后调用doBeginRead方法,重新设置感兴趣事件为SelectionKey.OP_READ(NioSocketChannel的默认感兴趣事件),至此客户端启动完成。
自行操作channel发送消息
客户端启动完成获取channel我们可以调用writeAndFlush发送消息。当然服务端返回的消息,NioEventLoop会感知到,并通过管道链回调到自定义的channelRead方法进行读取。
Netty5客户端源码解析的更多相关文章
- FileZilla客户端源码解析
FileZilla客户端源码解析 FTP是TCP/IP协议组的协议,有指令通路和数据通路两条通道.一般来说,FTP标准命令TCP端口号是21,Port方式数据传输端口是20. FileZilla作为p ...
- Feign 客户端源码解析
Feign的使用非常简单,增加如下配置之后,便可以使用Feign进行调用.非常简单是不是.主要的工作由Feign框架完成.业务代码只提供了一个Interface, 然后由Feign动态生成代理类来实现 ...
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- 第零章 dubbo源码解析目录
第一章 第一个dubbo项目 第二章 dubbo内核之spi源码解析 2.1 jdk-spi的实现原理 2.2 dubbo-spi源码解析 第三章 dubbo内核之ioc源码解析 第四章 dubb ...
- Netty5服务端源码解析
Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...
- Fabric1.4源码解析:客户端安装链码
看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下. 还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子: #-n 指定mycc是由用户定义 ...
- Netty源码解析—客户端启动
Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...
- HDFS源码解析:教你用HDFS客户端写数据
摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
随机推荐
- 没想到: System.out.println(n1 == f1 ? n1 : f1);
int n1 = 404; float f1 = 404.0f; if(n1 == f1) { System.out.println("两者相等"); } System.out.p ...
- blackbox_exporter介绍
Blackbox Exporter是Prometheus社区提供的官方黑盒监控解决方案,其允许用户通过:HTTP.HTTPS.DNS.TCP以及ICMP的方式对网络进行探测. 1.安装部署 cd /u ...
- MySQL_写锁_lock tables tableName write
pre.环境准备 1.建立两个表S,T,并插入一些数据 --创建表S create table S(d int) engine=innodb; ); --创建表T create table T(c i ...
- C语言 内存管理(转)
转自 https://blog.csdn.net/u011616739/article/details/61621815 C语言 内存管理 1.内存分区 C源代码进过预处理.编译.汇编和链接4步生成 ...
- 深悉正则(pcre)最大回溯/递归限制
对于如下的正则 /<script>.*?<\/script>/is 当要匹配的字符串长度大于100014的时候, 就不会得出正确结果: $reg = "/<sc ...
- JGUI源码:prefixfree 这个库有时候会引起网页一直加载中(10)
如题,大部分情况下正常,但是chrome频繁刷新时,会出现这个问题,控制台没有异常信息.最终放弃使用引用第三方库prefixfree.min.js
- 乙方渗透测试之Fuzz爆破
前言 爆破在渗透测试中,对技术的要求不高,但是对技巧和字典的要求就很高了,本篇整理下平时学到的一些爆破思路和技巧(偏web渗透登陆),当你无措可施时,暴力破解是最好的方式. 世界上最可怕的事情是你的习 ...
- linux 上安装图形界面
linux 上安装 vncserver 后,图形界面里只有灰底和一个terminal 框, 解决方法: 修改 .vnc/xstartup为 unset SESSION_MANAGER # exec / ...
- 【转】Java中的新生代、老年代、永久代和各种GC
JVM中的堆,一般分为三大部分:新生代.老年代.永久代: 1 新生代 主要是用来存放新生的对象.一般占据堆的1/3空间.由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收. 新生代又分为 ...
- 本地Git与Github建立关联
准备 本地与Github建立连接,需要用到SSH公钥.一般安装完Git,会在用户目录中生成一个 .ssh的文件夹 如果没有此文件夹,可以通过命令创建 $ ssh-keygen -t rsa -C &q ...