netty作为java研发中的网络框架曾在dubbo,rockermq,zookeeper等中间件中应用过,甚至在我的世界java版,底层的网络通信也是用这个,下面通过个人在之前研发过程中对netty的使用,谈一谈对netty的理解
connect 阶段
//connect function
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
this.validate(); //验证
return this.doResolveAndConnect(remoteAddress, localAddress);//解析连接的返回结果再连接
}
//解析返回结果并连接
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
ChannelFuture regFuture = this.initAndRegister();//注册channel
final Channel channel = regFuture.channel();
//如果已连接
if (regFuture.isDone()) {
return !regFuture.isSuccess() ? regFuture : this.doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise()); //添加监听器,处理连接后的返回结果
}
//如果未连接
else {
//将需要建立连接的channel委托给Promise
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
//如果监听成功
promise.registered();
//获取channel连接响应结果
Bootstrap.this.doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
- 注册到channelFactory netty在启动流程中采取工厂模式新创建channel
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = this.channelFactory.newChannel();
this.init(channel);
} catch (Throwable var3) {
if (channel != null) {
channel.unsafe().closeForcibly();
return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
}
return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
}
//注册channel到对应的线程组
ChannelFuture regFuture = this.config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
连接的核心就在register函数,核心代码如下:
private static void doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
这里解释了为什么前一步需要引入线程组,并将channel注册到EventLoop线程组中。因为netty设计的本意是为了解决多用户之间的网络通信。要尽可能的及时连接到空闲的客户端。线程组的每一个线程对应连接一个客户端。由于ChannelPromise从功能逻辑层上看,可以看做是对ChannelFutrue的一层aop拦截的监听器,被用来处理futrue的返回结果,所以通过promise可以控制空闲连接的关闭,释放线程所持有的资源
连接完成后,如果保证请求按照pipline所定义的handler顺序请求?
在connect函数中,核心逻辑如下:
public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
} else if (this.isNotValidPromise(promise, false)) {
return promise;
} else {
final AbstractChannelHandlerContext next = this.findContextOutbound(1024);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, (Object)null);
}
return promise;
}
}
从findContextOutBound函数可以看出其实netty也是建立一个HandlerContext的链表结构,来保证按照顺序传递。
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while((ctx.executionMask & mask) == 0);
return ctx;
}
观察到这里使用尾插法进行链表遍历。这里的做法与hashmap扩容时出现循环链表,所以将头插法换成了尾插法的理由一致.避免调用handler时出现的线程切换,所以需要从链表尾部插入,扩容mask之后不在向链表头部添加handler调用
在connect函数中在轮询不到的时候会调用safeExecute函数进行轮询
private static boolean safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {
try {
executor.execute(runnable);
return true;
} catch (Throwable var9) {
Throwable cause = var9;
try {
promise.setFailure(cause);
} finally {
if (msg != null) {
ReferenceCountUtil.release(msg);
}
}
return false;
}
}
这里首先执行某个执行线程的连接递归调用,如果当前线程所在handler没有消息传入的时候,说明消息传递到了handler链表的尾部.此时需要释放消息的引用计数,避免引起GC回收不及时。这里我们留到下一章展开