change netty to modified version from initial commit
This commit is contained in:
parent
d8be274083
commit
6af3fd71e5
212 changed files with 45653 additions and 73 deletions
|
@ -130,6 +130,17 @@ import common.item.ItemControl;
|
|||
import common.item.ItemStack;
|
||||
import common.log.Log;
|
||||
import common.log.LogLevel;
|
||||
import common.net.bootstrap.Bootstrap;
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.channel.ChannelHandler;
|
||||
import common.net.channel.ChannelInitializer;
|
||||
import common.net.channel.ChannelOption;
|
||||
import common.net.channel.nio.NioEventLoopGroup;
|
||||
import common.net.channel.socket.nio.NioSocketChannel;
|
||||
import common.net.handler.timeout.ReadTimeoutHandler;
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
import common.network.IThreadListener;
|
||||
import common.network.NetConnection;
|
||||
import common.network.PacketDecoder;
|
||||
|
@ -162,17 +173,6 @@ import common.util.HitPosition.ObjectType;
|
|||
import common.world.LightType;
|
||||
import common.world.State;
|
||||
import common.world.World;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelException;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
/*
|
||||
Een net ganz funktionierndes Programm ...
|
||||
|
|
|
@ -18,7 +18,7 @@ public class GuiInfo extends Gui {
|
|||
|
||||
private static final String[] LIBRARIES = {
|
||||
"LWJGL 3.3.6+1 (GLFW + OpenGL)",
|
||||
"Netty 4.0.23-Final"
|
||||
"Netty 4.0.23-Final (modifiziert, verkleinert)"
|
||||
};
|
||||
private static final String[] CODE = {
|
||||
"Albert Pham - WorldEdit (Snippets)",
|
||||
|
|
|
@ -5,6 +5,8 @@ import java.security.PublicKey;
|
|||
import javax.crypto.SecretKey;
|
||||
|
||||
import client.Client;
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
import common.network.IClientLoginHandler;
|
||||
import common.network.NetConnection;
|
||||
import common.network.NetHandler;
|
||||
|
@ -16,8 +18,6 @@ import common.packet.RPacketEnableCompression;
|
|||
import common.packet.RPacketLoginSuccess;
|
||||
import common.packet.RPacketRequestEncrypt;
|
||||
import common.util.EncryptUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
public class ClientLoginHandler extends NetHandler implements IClientLoginHandler {
|
||||
private final Client gm;
|
||||
|
|
474
common/src/common/net/bootstrap/AbstractBootstrap.java
Normal file
474
common/src/common/net/bootstrap/AbstractBootstrap.java
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.bootstrap;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.channel.ChannelFuture;
|
||||
import common.net.channel.ChannelFutureListener;
|
||||
import common.net.channel.ChannelHandler;
|
||||
import common.net.channel.ChannelOption;
|
||||
import common.net.channel.ChannelPromise;
|
||||
import common.net.channel.DefaultChannelPromise;
|
||||
import common.net.channel.EventLoopGroup;
|
||||
import common.net.util.AttributeKey;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
import common.net.util.concurrent.GlobalEventExecutor;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support
|
||||
* method-chaining to provide an easy way to configure the {@link AbstractBootstrap}.
|
||||
*
|
||||
* <p>When not used in a {@link ServerBootstrap} context, the {@link #bind()} methods are useful for connectionless
|
||||
* transports such as datagram (UDP).</p>
|
||||
*/
|
||||
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
|
||||
|
||||
private volatile EventLoopGroup group;
|
||||
private volatile ChannelFactory<? extends C> channelFactory;
|
||||
private volatile SocketAddress localAddress;
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
||||
private volatile ChannelHandler handler;
|
||||
|
||||
AbstractBootstrap() {
|
||||
// Disallow extending from a different package.
|
||||
}
|
||||
|
||||
AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
|
||||
group = bootstrap.group;
|
||||
channelFactory = bootstrap.channelFactory;
|
||||
handler = bootstrap.handler;
|
||||
localAddress = bootstrap.localAddress;
|
||||
synchronized (bootstrap.options) {
|
||||
options.putAll(bootstrap.options);
|
||||
}
|
||||
synchronized (bootstrap.attrs) {
|
||||
attrs.putAll(bootstrap.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link EventLoopGroup} which is used to handle all the events for the to-be-creates
|
||||
* {@link Channel}
|
||||
*/
|
||||
|
||||
public B group(EventLoopGroup group) {
|
||||
if (group == null) {
|
||||
throw new NullPointerException("group");
|
||||
}
|
||||
if (this.group != null) {
|
||||
throw new IllegalStateException("group set already");
|
||||
}
|
||||
this.group = group;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Class} which is used to create {@link Channel} instances from.
|
||||
* You either use this or {@link #channelFactory(ChannelFactory)} if your
|
||||
* {@link Channel} implementation has no no-args constructor.
|
||||
*/
|
||||
public B channel(Class<? extends C> channelClass) {
|
||||
if (channelClass == null) {
|
||||
throw new NullPointerException("channelClass");
|
||||
}
|
||||
return channelFactory(new BootstrapChannelFactory<C>(channelClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ChannelFactory} which is used to create {@link Channel} instances from
|
||||
* when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
|
||||
* is not working for you because of some more complex needs. If your {@link Channel} implementation
|
||||
* has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for
|
||||
* simplify your code.
|
||||
*/
|
||||
|
||||
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
|
||||
if (channelFactory == null) {
|
||||
throw new NullPointerException("channelFactory");
|
||||
}
|
||||
if (this.channelFactory != null) {
|
||||
throw new IllegalStateException("channelFactory set already");
|
||||
}
|
||||
|
||||
this.channelFactory = channelFactory;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SocketAddress} which is used to bind the local "end" to.
|
||||
*
|
||||
*/
|
||||
|
||||
public B localAddress(SocketAddress localAddress) {
|
||||
this.localAddress = localAddress;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #localAddress(SocketAddress)}
|
||||
*/
|
||||
public B localAddress(int inetPort) {
|
||||
return localAddress(new InetSocketAddress(inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #localAddress(SocketAddress)}
|
||||
*/
|
||||
public B localAddress(String inetHost, int inetPort) {
|
||||
return localAddress(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #localAddress(SocketAddress)}
|
||||
*/
|
||||
public B localAddress(InetAddress inetHost, int inetPort) {
|
||||
return localAddress(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*/
|
||||
|
||||
public <T> B option(ChannelOption<T> option, T value) {
|
||||
if (option == null) {
|
||||
throw new NullPointerException("option");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (options) {
|
||||
options.remove(option);
|
||||
}
|
||||
} else {
|
||||
synchronized (options) {
|
||||
options.put(option, value);
|
||||
}
|
||||
}
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link Channel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*/
|
||||
public <T> B attr(AttributeKey<T> key, T value) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (attrs) {
|
||||
attrs.remove(key);
|
||||
}
|
||||
} else {
|
||||
synchronized (attrs) {
|
||||
attrs.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
B b = (B) this;
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all the parameters. Sub-classes may override this, but should
|
||||
* call the super method in that case.
|
||||
*/
|
||||
|
||||
public B validate() {
|
||||
if (group == null) {
|
||||
throw new IllegalStateException("group not set");
|
||||
}
|
||||
if (channelFactory == null) {
|
||||
throw new IllegalStateException("channel or channelFactory not set");
|
||||
}
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep clone of this bootstrap which has the identical configuration. This method is useful when making
|
||||
* multiple {@link Channel}s with similar settings. Please note that this method does not clone the
|
||||
* {@link EventLoopGroup} deeply but shallowly, making the group a shared resource.
|
||||
*/
|
||||
@Override
|
||||
|
||||
public abstract B clone();
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and register it with an {@link EventLoop}.
|
||||
*/
|
||||
public ChannelFuture register() {
|
||||
validate();
|
||||
return initAndRegister();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and bind it.
|
||||
*/
|
||||
public ChannelFuture bind() {
|
||||
validate();
|
||||
SocketAddress localAddress = this.localAddress;
|
||||
if (localAddress == null) {
|
||||
throw new IllegalStateException("localAddress not set");
|
||||
}
|
||||
return doBind(localAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and bind it.
|
||||
*/
|
||||
public ChannelFuture bind(int inetPort) {
|
||||
return bind(new InetSocketAddress(inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and bind it.
|
||||
*/
|
||||
public ChannelFuture bind(String inetHost, int inetPort) {
|
||||
return bind(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and bind it.
|
||||
*/
|
||||
public ChannelFuture bind(InetAddress inetHost, int inetPort) {
|
||||
return bind(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Channel} and bind it.
|
||||
*/
|
||||
public ChannelFuture bind(SocketAddress localAddress) {
|
||||
validate();
|
||||
if (localAddress == null) {
|
||||
throw new NullPointerException("localAddress");
|
||||
}
|
||||
return doBind(localAddress);
|
||||
}
|
||||
|
||||
private ChannelFuture doBind(final SocketAddress localAddress) {
|
||||
final ChannelFuture regFuture = initAndRegister();
|
||||
final Channel channel = regFuture.channel();
|
||||
if (regFuture.cause() != null) {
|
||||
return regFuture;
|
||||
}
|
||||
|
||||
final ChannelPromise promise;
|
||||
if (regFuture.isDone()) {
|
||||
promise = channel.newPromise();
|
||||
doBind0(regFuture, channel, localAddress, promise);
|
||||
} else {
|
||||
// Registration future is almost always fulfilled already, but just in case it's not.
|
||||
promise = new PendingRegistrationPromise(channel);
|
||||
regFuture.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
doBind0(regFuture, channel, localAddress, promise);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
final ChannelFuture initAndRegister() {
|
||||
final Channel channel = channelFactory().newChannel();
|
||||
try {
|
||||
init(channel);
|
||||
} catch (Throwable t) {
|
||||
channel.unsafe().closeForcibly();
|
||||
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
|
||||
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
|
||||
}
|
||||
|
||||
ChannelFuture regFuture = group().register(channel);
|
||||
if (regFuture.cause() != null) {
|
||||
if (channel.isRegistered()) {
|
||||
channel.close();
|
||||
} else {
|
||||
channel.unsafe().closeForcibly();
|
||||
}
|
||||
}
|
||||
|
||||
// If we are here and the promise is not failed, it's one of the following cases:
|
||||
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
|
||||
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
|
||||
// 2) If we attempted registration from the other thread, the registration request has been successfully
|
||||
// added to the event loop's task queue for later execution.
|
||||
// i.e. It's safe to attempt bind() or connect() now:
|
||||
// because bind() or connect() will be executed *after* the scheduled registration task is executed
|
||||
// because register(), bind(), and connect() are all bound to the same thread.
|
||||
|
||||
return regFuture;
|
||||
}
|
||||
|
||||
abstract void init(Channel channel) throws Exception;
|
||||
|
||||
private static void doBind0(
|
||||
final ChannelFuture regFuture, final Channel channel,
|
||||
final SocketAddress localAddress, final ChannelPromise promise) {
|
||||
|
||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||
// the pipeline in its channelRegistered() implementation.
|
||||
channel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (regFuture.isSuccess()) {
|
||||
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||
} else {
|
||||
promise.setFailure(regFuture.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* the {@link ChannelHandler} to use for serving the requests.
|
||||
*/
|
||||
|
||||
public B handler(ChannelHandler handler) {
|
||||
if (handler == null) {
|
||||
throw new NullPointerException("handler");
|
||||
}
|
||||
this.handler = handler;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
final SocketAddress localAddress() {
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
final ChannelFactory<? extends C> channelFactory() {
|
||||
return channelFactory;
|
||||
}
|
||||
|
||||
final ChannelHandler handler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link EventLoopGroup} or {@code null} if non is configured yet.
|
||||
*/
|
||||
public final EventLoopGroup group() {
|
||||
return group;
|
||||
}
|
||||
|
||||
final Map<ChannelOption<?>, Object> options() {
|
||||
return options;
|
||||
}
|
||||
|
||||
final Map<AttributeKey<?>, Object> attrs() {
|
||||
return attrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(StringUtil.simpleClassName(this));
|
||||
buf.append('(');
|
||||
if (group != null) {
|
||||
buf.append("group: ");
|
||||
buf.append(StringUtil.simpleClassName(group));
|
||||
buf.append(", ");
|
||||
}
|
||||
if (channelFactory != null) {
|
||||
buf.append("channelFactory: ");
|
||||
buf.append(channelFactory);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (localAddress != null) {
|
||||
buf.append("localAddress: ");
|
||||
buf.append(localAddress);
|
||||
buf.append(", ");
|
||||
}
|
||||
synchronized (options) {
|
||||
if (!options.isEmpty()) {
|
||||
buf.append("options: ");
|
||||
buf.append(options);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
synchronized (attrs) {
|
||||
if (!attrs.isEmpty()) {
|
||||
buf.append("attrs: ");
|
||||
buf.append(attrs);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
if (handler != null) {
|
||||
buf.append("handler: ");
|
||||
buf.append(handler);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (buf.charAt(buf.length() - 1) == '(') {
|
||||
buf.append(')');
|
||||
} else {
|
||||
buf.setCharAt(buf.length() - 2, ')');
|
||||
buf.setLength(buf.length() - 1);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> {
|
||||
private final Class<? extends T> clazz;
|
||||
|
||||
BootstrapChannelFactory(Class<? extends T> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T newChannel() {
|
||||
try {
|
||||
return clazz.newInstance();
|
||||
} catch (Throwable t) {
|
||||
throw new ChannelException("Unable to create Channel from class " + clazz, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(clazz) + ".class";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PendingRegistrationPromise extends DefaultChannelPromise {
|
||||
private PendingRegistrationPromise(Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventExecutor executor() {
|
||||
if (channel().isRegistered()) {
|
||||
// If the registration was a success we can just call super.executor() which will return
|
||||
// channel.eventLoop().
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2586
|
||||
return super.executor();
|
||||
}
|
||||
// The registration failed so we can only use the GlobalEventExecutor as last resort to notify.
|
||||
return GlobalEventExecutor.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
233
common/src/common/net/bootstrap/Bootstrap.java
Normal file
233
common/src/common/net/bootstrap/Bootstrap.java
Normal file
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.bootstrap;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelFuture;
|
||||
import common.net.channel.ChannelFutureListener;
|
||||
import common.net.channel.ChannelOption;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.ChannelPromise;
|
||||
import common.net.util.AttributeKey;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link Bootstrap} that makes it easy to bootstrap a {@link Channel} to use
|
||||
* for clients.
|
||||
*
|
||||
* <p>The {@link #bind()} methods are useful in combination with connectionless transports such as datagram (UDP).
|
||||
* For regular TCP connections, please use the provided {@link #connect()} methods.</p>
|
||||
*/
|
||||
public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
|
||||
|
||||
private volatile SocketAddress remoteAddress;
|
||||
|
||||
public Bootstrap() { }
|
||||
|
||||
private Bootstrap(Bootstrap bootstrap) {
|
||||
super(bootstrap);
|
||||
remoteAddress = bootstrap.remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link SocketAddress} to connect to once the {@link #connect()} method
|
||||
* is called.
|
||||
*/
|
||||
public Bootstrap remoteAddress(SocketAddress remoteAddress) {
|
||||
this.remoteAddress = remoteAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #remoteAddress(SocketAddress)}
|
||||
*/
|
||||
public Bootstrap remoteAddress(String inetHost, int inetPort) {
|
||||
remoteAddress = new InetSocketAddress(inetHost, inetPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #remoteAddress(SocketAddress)}
|
||||
*/
|
||||
public Bootstrap remoteAddress(InetAddress inetHost, int inetPort) {
|
||||
remoteAddress = new InetSocketAddress(inetHost, inetPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect() {
|
||||
validate();
|
||||
SocketAddress remoteAddress = this.remoteAddress;
|
||||
if (remoteAddress == null) {
|
||||
throw new IllegalStateException("remoteAddress not set");
|
||||
}
|
||||
|
||||
return doConnect(remoteAddress, localAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect(String inetHost, int inetPort) {
|
||||
return connect(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect(InetAddress inetHost, int inetPort) {
|
||||
return connect(new InetSocketAddress(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
if (remoteAddress == null) {
|
||||
throw new NullPointerException("remoteAddress");
|
||||
}
|
||||
|
||||
validate();
|
||||
return doConnect(remoteAddress, localAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a {@link Channel} to the remote peer.
|
||||
*/
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||
if (remoteAddress == null) {
|
||||
throw new NullPointerException("remoteAddress");
|
||||
}
|
||||
validate();
|
||||
return doConnect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #connect()}
|
||||
*/
|
||||
private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
|
||||
final ChannelFuture regFuture = initAndRegister();
|
||||
final Channel channel = regFuture.channel();
|
||||
if (regFuture.cause() != null) {
|
||||
return regFuture;
|
||||
}
|
||||
|
||||
final ChannelPromise promise = channel.newPromise();
|
||||
if (regFuture.isDone()) {
|
||||
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
|
||||
} else {
|
||||
regFuture.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
doConnect0(regFuture, channel, remoteAddress, localAddress, promise);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private static void doConnect0(
|
||||
final ChannelFuture regFuture, final Channel channel,
|
||||
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
|
||||
|
||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||
// the pipeline in its channelRegistered() implementation.
|
||||
channel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (regFuture.isSuccess()) {
|
||||
if (localAddress == null) {
|
||||
channel.connect(remoteAddress, promise);
|
||||
} else {
|
||||
channel.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||
} else {
|
||||
promise.setFailure(regFuture.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
void init(Channel channel) throws Exception {
|
||||
ChannelPipeline p = channel.pipeline();
|
||||
p.addLast(handler());
|
||||
|
||||
final Map<ChannelOption<?>, Object> options = options();
|
||||
synchronized (options) {
|
||||
for (Entry<ChannelOption<?>, Object> e: options.entrySet()) {
|
||||
try {
|
||||
if (!channel.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
||||
logger.warn("Unknown channel option: " + e);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Failed to set a channel option: " + channel, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Map<AttributeKey<?>, Object> attrs = attrs();
|
||||
synchronized (attrs) {
|
||||
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
|
||||
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bootstrap validate() {
|
||||
super.validate();
|
||||
if (handler() == null) {
|
||||
throw new IllegalStateException("handler not set");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Bootstrap clone() {
|
||||
return new Bootstrap(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (remoteAddress == null) {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(super.toString());
|
||||
buf.setLength(buf.length() - 1);
|
||||
buf.append(", remoteAddress: ");
|
||||
buf.append(remoteAddress);
|
||||
buf.append(')');
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
29
common/src/common/net/bootstrap/ChannelFactory.java
Normal file
29
common/src/common/net/bootstrap/ChannelFactory.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.bootstrap;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
|
||||
/**
|
||||
* Factory that creates a new {@link Channel} on {@link Bootstrap#bind()}, {@link Bootstrap#connect()}, and
|
||||
* {@link ServerBootstrap#bind()}.
|
||||
*/
|
||||
public interface ChannelFactory<T extends Channel> {
|
||||
/**
|
||||
* Creates a new channel.
|
||||
*/
|
||||
T newChannel();
|
||||
}
|
332
common/src/common/net/bootstrap/ServerBootstrap.java
Normal file
332
common/src/common/net/bootstrap/ServerBootstrap.java
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.bootstrap;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelConfig;
|
||||
import common.net.channel.ChannelFuture;
|
||||
import common.net.channel.ChannelFutureListener;
|
||||
import common.net.channel.ChannelHandler;
|
||||
import common.net.channel.ChannelHandlerContext;
|
||||
import common.net.channel.ChannelInboundHandlerAdapter;
|
||||
import common.net.channel.ChannelInitializer;
|
||||
import common.net.channel.ChannelOption;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.EventLoopGroup;
|
||||
import common.net.channel.ServerChannel;
|
||||
import common.net.util.AttributeKey;
|
||||
import common.net.util.internal.StringUtil;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
|
||||
*
|
||||
*/
|
||||
public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
|
||||
|
||||
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
|
||||
private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
||||
private volatile EventLoopGroup childGroup;
|
||||
private volatile ChannelHandler childHandler;
|
||||
|
||||
public ServerBootstrap() { }
|
||||
|
||||
private ServerBootstrap(ServerBootstrap bootstrap) {
|
||||
super(bootstrap);
|
||||
childGroup = bootstrap.childGroup;
|
||||
childHandler = bootstrap.childHandler;
|
||||
synchronized (bootstrap.childOptions) {
|
||||
childOptions.putAll(bootstrap.childOptions);
|
||||
}
|
||||
synchronized (bootstrap.childAttrs) {
|
||||
childAttrs.putAll(bootstrap.childAttrs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client).
|
||||
*/
|
||||
@Override
|
||||
public ServerBootstrap group(EventLoopGroup group) {
|
||||
return group(group, group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
|
||||
* {@link EventLoopGroup}'s are used to handle all the events and IO for {@link SocketChannel} and
|
||||
* {@link Channel}'s.
|
||||
*/
|
||||
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
|
||||
super.group(parentGroup);
|
||||
if (childGroup == null) {
|
||||
throw new NullPointerException("childGroup");
|
||||
}
|
||||
if (this.childGroup != null) {
|
||||
throw new IllegalStateException("childGroup set already");
|
||||
}
|
||||
this.childGroup = childGroup;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created
|
||||
* (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set
|
||||
* {@link ChannelOption}.
|
||||
*/
|
||||
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
|
||||
if (childOption == null) {
|
||||
throw new NullPointerException("childOption");
|
||||
}
|
||||
if (value == null) {
|
||||
synchronized (childOptions) {
|
||||
childOptions.remove(childOption);
|
||||
}
|
||||
} else {
|
||||
synchronized (childOptions) {
|
||||
childOptions.put(childOption, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
|
||||
* {@code null} the {@link AttributeKey} is removed
|
||||
*/
|
||||
public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
|
||||
if (childKey == null) {
|
||||
throw new NullPointerException("childKey");
|
||||
}
|
||||
if (value == null) {
|
||||
childAttrs.remove(childKey);
|
||||
} else {
|
||||
childAttrs.put(childKey, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s.
|
||||
*/
|
||||
public ServerBootstrap childHandler(ChannelHandler childHandler) {
|
||||
if (childHandler == null) {
|
||||
throw new NullPointerException("childHandler");
|
||||
}
|
||||
this.childHandler = childHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
|
||||
* if non is configured yet.
|
||||
*/
|
||||
public EventLoopGroup childGroup() {
|
||||
return childGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(Channel channel) throws Exception {
|
||||
final Map<ChannelOption<?>, Object> options = options();
|
||||
synchronized (options) {
|
||||
channel.config().setOptions(options);
|
||||
}
|
||||
|
||||
final Map<AttributeKey<?>, Object> attrs = attrs();
|
||||
synchronized (attrs) {
|
||||
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
|
||||
|
||||
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
|
||||
channel.attr(key).set(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
ChannelPipeline p = channel.pipeline();
|
||||
if (handler() != null) {
|
||||
p.addLast(handler());
|
||||
}
|
||||
|
||||
final EventLoopGroup currentChildGroup = childGroup;
|
||||
final ChannelHandler currentChildHandler = childHandler;
|
||||
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
|
||||
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
|
||||
synchronized (childOptions) {
|
||||
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
|
||||
}
|
||||
synchronized (childAttrs) {
|
||||
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
|
||||
}
|
||||
|
||||
p.addLast(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
public void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline().addLast(new ServerBootstrapAcceptor(
|
||||
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerBootstrap validate() {
|
||||
super.validate();
|
||||
if (childHandler == null) {
|
||||
throw new IllegalStateException("childHandler not set");
|
||||
}
|
||||
if (childGroup == null) {
|
||||
logger.warn("childGroup is not set. Using parentGroup instead.");
|
||||
childGroup = group();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private static Entry<ChannelOption<?>, Object>[] newOptionArray(int size) {
|
||||
return new Entry[size];
|
||||
}
|
||||
|
||||
|
||||
private static Entry<AttributeKey<?>, Object>[] newAttrArray(int size) {
|
||||
return new Entry[size];
|
||||
}
|
||||
|
||||
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final EventLoopGroup childGroup;
|
||||
private final ChannelHandler childHandler;
|
||||
private final Entry<ChannelOption<?>, Object>[] childOptions;
|
||||
private final Entry<AttributeKey<?>, Object>[] childAttrs;
|
||||
|
||||
ServerBootstrapAcceptor(
|
||||
EventLoopGroup childGroup, ChannelHandler childHandler,
|
||||
Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
|
||||
this.childGroup = childGroup;
|
||||
this.childHandler = childHandler;
|
||||
this.childOptions = childOptions;
|
||||
this.childAttrs = childAttrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
final Channel child = (Channel) msg;
|
||||
|
||||
child.pipeline().addLast(childHandler);
|
||||
|
||||
for (Entry<ChannelOption<?>, Object> e: childOptions) {
|
||||
try {
|
||||
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
||||
logger.warn("Unknown channel option: " + e);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Failed to set a channel option: " + child, t);
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
|
||||
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
||||
}
|
||||
|
||||
try {
|
||||
childGroup.register(child).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
forceClose(child, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
forceClose(child, t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void forceClose(Channel child, Throwable t) {
|
||||
child.unsafe().closeForcibly();
|
||||
logger.warn("Failed to register an accepted channel: " + child, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
final ChannelConfig config = ctx.channel().config();
|
||||
if (config.isAutoRead()) {
|
||||
// stop accept new connections for 1 second to allow the channel to recover
|
||||
// See https://github.com/netty/netty/issues/1328
|
||||
config.setAutoRead(false);
|
||||
ctx.channel().eventLoop().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
config.setAutoRead(true);
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
// still let the exceptionCaught event flow through the pipeline to give the user
|
||||
// a chance to do something with it
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public ServerBootstrap clone() {
|
||||
return new ServerBootstrap(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(super.toString());
|
||||
buf.setLength(buf.length() - 1);
|
||||
buf.append(", ");
|
||||
if (childGroup != null) {
|
||||
buf.append("childGroup: ");
|
||||
buf.append(StringUtil.simpleClassName(childGroup));
|
||||
buf.append(", ");
|
||||
}
|
||||
synchronized (childOptions) {
|
||||
if (!childOptions.isEmpty()) {
|
||||
buf.append("childOptions: ");
|
||||
buf.append(childOptions);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
synchronized (childAttrs) {
|
||||
if (!childAttrs.isEmpty()) {
|
||||
buf.append("childAttrs: ");
|
||||
buf.append(childAttrs);
|
||||
buf.append(", ");
|
||||
}
|
||||
}
|
||||
if (childHandler != null) {
|
||||
buf.append("childHandler: ");
|
||||
buf.append(childHandler);
|
||||
buf.append(", ");
|
||||
}
|
||||
if (buf.charAt(buf.length() - 1) == '(') {
|
||||
buf.append(')');
|
||||
} else {
|
||||
buf.setCharAt(buf.length() - 2, ')');
|
||||
buf.setLength(buf.length() - 1);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
1190
common/src/common/net/buffer/AbstractByteBuf.java
Normal file
1190
common/src/common/net/buffer/AbstractByteBuf.java
Normal file
File diff suppressed because it is too large
Load diff
219
common/src/common/net/buffer/AbstractByteBufAllocator.java
Normal file
219
common/src/common/net/buffer/AbstractByteBufAllocator.java
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import common.net.util.ResourceLeak;
|
||||
import common.net.util.ResourceLeakDetector;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* Skeletal {@link ByteBufAllocator} implementation to extend.
|
||||
*/
|
||||
public abstract class AbstractByteBufAllocator implements ByteBufAllocator {
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 256;
|
||||
private static final int DEFAULT_MAX_COMPONENTS = 16;
|
||||
|
||||
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
|
||||
ResourceLeak leak;
|
||||
switch (ResourceLeakDetector.getLevel()) {
|
||||
case SIMPLE:
|
||||
leak = AbstractByteBuf.leakDetector.open(buf);
|
||||
if (leak != null) {
|
||||
buf = new SimpleLeakAwareByteBuf(buf, leak);
|
||||
}
|
||||
break;
|
||||
case ADVANCED:
|
||||
case PARANOID:
|
||||
leak = AbstractByteBuf.leakDetector.open(buf);
|
||||
if (leak != null) {
|
||||
buf = new AdvancedLeakAwareByteBuf(buf, leak);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private final boolean directByDefault;
|
||||
private final ByteBuf emptyBuf;
|
||||
|
||||
/**
|
||||
* Instance use heap buffers by default
|
||||
*/
|
||||
protected AbstractByteBufAllocator() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance
|
||||
*
|
||||
* @param preferDirect {@code true} if {@link #buffer(int)} should try to allocate a direct buffer rather than
|
||||
* a heap buffer
|
||||
*/
|
||||
protected AbstractByteBufAllocator(boolean preferDirect) {
|
||||
directByDefault = preferDirect && PlatformDependent.hasUnsafe();
|
||||
emptyBuf = new EmptyByteBuf(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer() {
|
||||
if (directByDefault) {
|
||||
return directBuffer();
|
||||
}
|
||||
return heapBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer(int initialCapacity) {
|
||||
if (directByDefault) {
|
||||
return directBuffer(initialCapacity);
|
||||
}
|
||||
return heapBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer(int initialCapacity, int maxCapacity) {
|
||||
if (directByDefault) {
|
||||
return directBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
return heapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer() {
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
return directBuffer(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
return heapBuffer(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity) {
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
return directBuffer(initialCapacity);
|
||||
}
|
||||
return heapBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
return directBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
return heapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer() {
|
||||
return heapBuffer(DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer(int initialCapacity) {
|
||||
return heapBuffer(initialCapacity, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
|
||||
if (initialCapacity == 0 && maxCapacity == 0) {
|
||||
return emptyBuf;
|
||||
}
|
||||
validate(initialCapacity, maxCapacity);
|
||||
return newHeapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer() {
|
||||
return directBuffer(DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer(int initialCapacity) {
|
||||
return directBuffer(initialCapacity, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
|
||||
if (initialCapacity == 0 && maxCapacity == 0) {
|
||||
return emptyBuf;
|
||||
}
|
||||
validate(initialCapacity, maxCapacity);
|
||||
return newDirectBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeBuffer() {
|
||||
if (directByDefault) {
|
||||
return compositeDirectBuffer();
|
||||
}
|
||||
return compositeHeapBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeBuffer(int maxNumComponents) {
|
||||
if (directByDefault) {
|
||||
return compositeDirectBuffer(maxNumComponents);
|
||||
}
|
||||
return compositeHeapBuffer(maxNumComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeHeapBuffer() {
|
||||
return compositeHeapBuffer(DEFAULT_MAX_COMPONENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) {
|
||||
return new CompositeByteBuf(this, false, maxNumComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeDirectBuffer() {
|
||||
return compositeDirectBuffer(DEFAULT_MAX_COMPONENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) {
|
||||
return new CompositeByteBuf(this, true, maxNumComponents);
|
||||
}
|
||||
|
||||
private static void validate(int initialCapacity, int maxCapacity) {
|
||||
if (initialCapacity < 0) {
|
||||
throw new IllegalArgumentException("initialCapacity: " + initialCapacity + " (expectd: 0+)");
|
||||
}
|
||||
if (initialCapacity > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity: %d (expected: not greater than maxCapacity(%d)",
|
||||
initialCapacity, maxCapacity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a heap {@link ByteBuf} with the given initialCapacity and maxCapacity.
|
||||
*/
|
||||
protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
/**
|
||||
* Create a direct {@link ByteBuf} with the given initialCapacity and maxCapacity.
|
||||
*/
|
||||
protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(this) + "(directByDefault: " + directByDefault + ')';
|
||||
}
|
||||
}
|
67
common/src/common/net/buffer/AbstractDerivedByteBuf.java
Normal file
67
common/src/common/net/buffer/AbstractDerivedByteBuf.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ByteBuf} implementations that wrap another
|
||||
* {@link ByteBuf}.
|
||||
*/
|
||||
public abstract class AbstractDerivedByteBuf extends AbstractByteBuf {
|
||||
|
||||
protected AbstractDerivedByteBuf(int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int refCnt() {
|
||||
return unwrap().refCnt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf retain() {
|
||||
unwrap().retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf retain(int increment) {
|
||||
unwrap().retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean release() {
|
||||
return unwrap().release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean release(int decrement) {
|
||||
return unwrap().release(decrement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
return unwrap().nioBuffer(index, length);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
import common.net.util.IllegalReferenceCountException;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ByteBuf} implementations that count references.
|
||||
*/
|
||||
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
|
||||
|
||||
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater;
|
||||
|
||||
static {
|
||||
AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater =
|
||||
PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
|
||||
if (updater == null) {
|
||||
updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
|
||||
}
|
||||
refCntUpdater = updater;
|
||||
}
|
||||
|
||||
private volatile int refCnt = 1;
|
||||
|
||||
protected AbstractReferenceCountedByteBuf(int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int refCnt() {
|
||||
return refCnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unsafe operation intended for use by a subclass that sets the reference count of the buffer directly
|
||||
*/
|
||||
protected final void setRefCnt(int refCnt) {
|
||||
this.refCnt = refCnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
for (;;) {
|
||||
int refCnt = this.refCnt;
|
||||
if (refCnt == 0) {
|
||||
throw new IllegalReferenceCountException(0, 1);
|
||||
}
|
||||
if (refCnt == Integer.MAX_VALUE) {
|
||||
throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);
|
||||
}
|
||||
if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
if (increment <= 0) {
|
||||
throw new IllegalArgumentException("increment: " + increment + " (expected: > 0)");
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int refCnt = this.refCnt;
|
||||
if (refCnt == 0) {
|
||||
throw new IllegalReferenceCountException(0, increment);
|
||||
}
|
||||
if (refCnt > Integer.MAX_VALUE - increment) {
|
||||
throw new IllegalReferenceCountException(refCnt, increment);
|
||||
}
|
||||
if (refCntUpdater.compareAndSet(this, refCnt, refCnt + increment)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean release() {
|
||||
for (;;) {
|
||||
int refCnt = this.refCnt;
|
||||
if (refCnt == 0) {
|
||||
throw new IllegalReferenceCountException(0, -1);
|
||||
}
|
||||
|
||||
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
|
||||
if (refCnt == 1) {
|
||||
deallocate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean release(int decrement) {
|
||||
if (decrement <= 0) {
|
||||
throw new IllegalArgumentException("decrement: " + decrement + " (expected: > 0)");
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int refCnt = this.refCnt;
|
||||
if (refCnt < decrement) {
|
||||
throw new IllegalReferenceCountException(refCnt, -decrement);
|
||||
}
|
||||
|
||||
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
|
||||
if (refCnt == decrement) {
|
||||
deallocate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once {@link #refCnt()} is equals 0.
|
||||
*/
|
||||
protected abstract void deallocate();
|
||||
}
|
724
common/src/common/net/buffer/AdvancedLeakAwareByteBuf.java
Normal file
724
common/src/common/net/buffer/AdvancedLeakAwareByteBuf.java
Normal file
|
@ -0,0 +1,724 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import common.net.util.ResourceLeak;
|
||||
|
||||
final class AdvancedLeakAwareByteBuf extends WrappedByteBuf {
|
||||
|
||||
private final ResourceLeak leak;
|
||||
|
||||
AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) {
|
||||
super(buf);
|
||||
this.leak = leak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
boolean deallocated = super.release();
|
||||
if (deallocated) {
|
||||
leak.close();
|
||||
} else {
|
||||
leak.record();
|
||||
}
|
||||
return deallocated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
boolean deallocated = super.release(decrement);
|
||||
if (deallocated) {
|
||||
leak.close();
|
||||
} else {
|
||||
leak.record();
|
||||
}
|
||||
return deallocated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
leak.record();
|
||||
if (order() == endianness) {
|
||||
return this;
|
||||
} else {
|
||||
return new AdvancedLeakAwareByteBuf(super.order(endianness), leak);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
leak.record();
|
||||
return new AdvancedLeakAwareByteBuf(super.slice(), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
leak.record();
|
||||
return new AdvancedLeakAwareByteBuf(super.slice(index, length), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
leak.record();
|
||||
return new AdvancedLeakAwareByteBuf(super.duplicate(), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
leak.record();
|
||||
return new AdvancedLeakAwareByteBuf(super.readSlice(length), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardReadBytes() {
|
||||
leak.record();
|
||||
return super.discardReadBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardSomeReadBytes() {
|
||||
leak.record();
|
||||
return super.discardSomeReadBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ensureWritable(int minWritableBytes) {
|
||||
leak.record();
|
||||
return super.ensureWritable(minWritableBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ensureWritable(int minWritableBytes, boolean force) {
|
||||
leak.record();
|
||||
return super.ensureWritable(minWritableBytes, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int index) {
|
||||
leak.record();
|
||||
return super.getBoolean(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
leak.record();
|
||||
return super.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getUnsignedByte(int index) {
|
||||
leak.record();
|
||||
return super.getUnsignedByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
leak.record();
|
||||
return super.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int index) {
|
||||
leak.record();
|
||||
return super.getUnsignedShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMedium(int index) {
|
||||
leak.record();
|
||||
return super.getMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
leak.record();
|
||||
return super.getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
leak.record();
|
||||
return super.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int index) {
|
||||
leak.record();
|
||||
return super.getUnsignedInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
leak.record();
|
||||
return super.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
leak.record();
|
||||
return super.getChar(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
leak.record();
|
||||
return super.getFloat(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
leak.record();
|
||||
return super.getDouble(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int length) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
leak.record();
|
||||
return super.getBytes(index, dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBoolean(int index, boolean value) {
|
||||
leak.record();
|
||||
return super.setBoolean(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
leak.record();
|
||||
return super.setByte(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
leak.record();
|
||||
return super.setShort(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
leak.record();
|
||||
return super.setMedium(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
leak.record();
|
||||
return super.setInt(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
leak.record();
|
||||
return super.setLong(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int index, int value) {
|
||||
leak.record();
|
||||
return super.setChar(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setFloat(int index, float value) {
|
||||
leak.record();
|
||||
return super.setFloat(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setDouble(int index, double value) {
|
||||
leak.record();
|
||||
return super.setDouble(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int length) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src, srcIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src, srcIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
leak.record();
|
||||
return super.setBytes(index, src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setZero(int index, int length) {
|
||||
leak.record();
|
||||
return super.setZero(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
leak.record();
|
||||
return super.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
leak.record();
|
||||
return super.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readUnsignedByte() {
|
||||
leak.record();
|
||||
return super.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
leak.record();
|
||||
return super.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
leak.record();
|
||||
return super.readUnsignedShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMedium() {
|
||||
leak.record();
|
||||
return super.readMedium();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMedium() {
|
||||
leak.record();
|
||||
return super.readUnsignedMedium();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
leak.record();
|
||||
return super.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedInt() {
|
||||
leak.record();
|
||||
return super.readUnsignedInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
leak.record();
|
||||
return super.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
leak.record();
|
||||
return super.readChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
leak.record();
|
||||
return super.readFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
leak.record();
|
||||
return super.readDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(int length) {
|
||||
leak.record();
|
||||
return super.readBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst) {
|
||||
leak.record();
|
||||
return super.readBytes(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int length) {
|
||||
leak.record();
|
||||
return super.readBytes(dst, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
|
||||
leak.record();
|
||||
return super.readBytes(dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst) {
|
||||
leak.record();
|
||||
return super.readBytes(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
leak.record();
|
||||
return super.readBytes(dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
leak.record();
|
||||
return super.readBytes(dst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.readBytes(out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.readBytes(out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf skipBytes(int length) {
|
||||
leak.record();
|
||||
return super.skipBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBoolean(boolean value) {
|
||||
leak.record();
|
||||
return super.writeBoolean(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeByte(int value) {
|
||||
leak.record();
|
||||
return super.writeByte(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShort(int value) {
|
||||
leak.record();
|
||||
return super.writeShort(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMedium(int value) {
|
||||
leak.record();
|
||||
return super.writeMedium(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int value) {
|
||||
leak.record();
|
||||
return super.writeInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long value) {
|
||||
leak.record();
|
||||
return super.writeLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int value) {
|
||||
leak.record();
|
||||
return super.writeChar(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeFloat(float value) {
|
||||
leak.record();
|
||||
return super.writeFloat(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeDouble(double value) {
|
||||
leak.record();
|
||||
return super.writeDouble(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src) {
|
||||
leak.record();
|
||||
return super.writeBytes(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int length) {
|
||||
leak.record();
|
||||
return super.writeBytes(src, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
|
||||
leak.record();
|
||||
return super.writeBytes(src, srcIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src) {
|
||||
leak.record();
|
||||
return super.writeBytes(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
|
||||
leak.record();
|
||||
return super.writeBytes(src, srcIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuffer src) {
|
||||
leak.record();
|
||||
return super.writeBytes(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(InputStream in, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
|
||||
leak.record();
|
||||
return super.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeZero(int length) {
|
||||
leak.record();
|
||||
return super.writeZero(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int fromIndex, int toIndex, byte value) {
|
||||
leak.record();
|
||||
return super.indexOf(fromIndex, toIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(byte value) {
|
||||
leak.record();
|
||||
return super.bytesBefore(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int length, byte value) {
|
||||
leak.record();
|
||||
return super.bytesBefore(length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int index, int length, byte value) {
|
||||
leak.record();
|
||||
return super.bytesBefore(index, length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(ByteBufProcessor processor) {
|
||||
leak.record();
|
||||
return super.forEachByte(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
leak.record();
|
||||
return super.forEachByte(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(ByteBufProcessor processor) {
|
||||
leak.record();
|
||||
return super.forEachByteDesc(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
leak.record();
|
||||
return super.forEachByteDesc(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy() {
|
||||
leak.record();
|
||||
return super.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
leak.record();
|
||||
return super.copy(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
leak.record();
|
||||
return super.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer() {
|
||||
leak.record();
|
||||
return super.nioBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
leak.record();
|
||||
return super.nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers() {
|
||||
leak.record();
|
||||
return super.nioBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
leak.record();
|
||||
return super.nioBuffers(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
leak.record();
|
||||
return super.internalNioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
leak.record();
|
||||
return super.toString(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
leak.record();
|
||||
return super.toString(index, length, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
leak.record();
|
||||
return super.retain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
leak.record();
|
||||
return super.retain(increment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
leak.record();
|
||||
return super.capacity(newCapacity);
|
||||
}
|
||||
}
|
1879
common/src/common/net/buffer/ByteBuf.java
Normal file
1879
common/src/common/net/buffer/ByteBuf.java
Normal file
File diff suppressed because it is too large
Load diff
128
common/src/common/net/buffer/ByteBufAllocator.java
Normal file
128
common/src/common/net/buffer/ByteBufAllocator.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
/**
|
||||
* Implementations are responsible to allocate buffers. Implementations of this interface are expected to be
|
||||
* thread-safe.
|
||||
*/
|
||||
public interface ByteBufAllocator {
|
||||
|
||||
ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf}. If it is a direct or heap buffer
|
||||
* depends on the actual implementation.
|
||||
*/
|
||||
ByteBuf buffer();
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf} with the given initial capacity.
|
||||
* If it is a direct or heap buffer depends on the actual implementation.
|
||||
*/
|
||||
ByteBuf buffer(int initialCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf} with the given initial capacity and the given
|
||||
* maximal capacity. If it is a direct or heap buffer depends on the actual
|
||||
* implementation.
|
||||
*/
|
||||
ByteBuf buffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
|
||||
*/
|
||||
ByteBuf ioBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
|
||||
*/
|
||||
ByteBuf ioBuffer(int initialCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
|
||||
*/
|
||||
ByteBuf ioBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a heap {@link ByteBuf}.
|
||||
*/
|
||||
ByteBuf heapBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a heap {@link ByteBuf} with the given initial capacity.
|
||||
*/
|
||||
ByteBuf heapBuffer(int initialCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a heap {@link ByteBuf} with the given initial capacity and the given
|
||||
* maximal capacity.
|
||||
*/
|
||||
ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a direct {@link ByteBuf}.
|
||||
*/
|
||||
ByteBuf directBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a direct {@link ByteBuf} with the given initial capacity.
|
||||
*/
|
||||
ByteBuf directBuffer(int initialCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a direct {@link ByteBuf} with the given initial capacity and the given
|
||||
* maximal capacity.
|
||||
*/
|
||||
ByteBuf directBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
/**
|
||||
* Allocate a {@link CompositeByteBuf}.
|
||||
* If it is a direct or heap buffer depends on the actual implementation.
|
||||
*/
|
||||
CompositeByteBuf compositeBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
|
||||
* If it is a direct or heap buffer depends on the actual implementation.
|
||||
*/
|
||||
CompositeByteBuf compositeBuffer(int maxNumComponents);
|
||||
|
||||
/**
|
||||
* Allocate a heap {@link CompositeByteBuf}.
|
||||
*/
|
||||
CompositeByteBuf compositeHeapBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a heap {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
|
||||
*/
|
||||
CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
|
||||
|
||||
/**
|
||||
* Allocate a direct {@link CompositeByteBuf}.
|
||||
*/
|
||||
CompositeByteBuf compositeDirectBuffer();
|
||||
|
||||
/**
|
||||
* Allocate a direct {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
|
||||
*/
|
||||
CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if direct {@link ByteBuf}'s are pooled
|
||||
*/
|
||||
boolean isDirectBufferPooled();
|
||||
}
|
257
common/src/common/net/buffer/ByteBufInputStream.java
Normal file
257
common/src/common/net/buffer/ByteBufInputStream.java
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An {@link InputStream} which reads data from a {@link ByteBuf}.
|
||||
* <p>
|
||||
* A read operation against this stream will occur at the {@code readerIndex}
|
||||
* of its underlying buffer and the {@code readerIndex} will increase during
|
||||
* the read operation. Please note that it only reads up to the number of
|
||||
* readable bytes determined at the moment of construction. Therefore,
|
||||
* updating {@link ByteBuf#writerIndex()} will not affect the return
|
||||
* value of {@link #available()}.
|
||||
* <p>
|
||||
* This stream implements {@link DataInput} for your convenience.
|
||||
* The endianness of the stream is not always big endian but depends on
|
||||
* the endianness of the underlying buffer.
|
||||
*
|
||||
* @see ByteBufOutputStream
|
||||
*/
|
||||
public class ByteBufInputStream extends InputStream implements DataInput {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final int startIndex;
|
||||
private final int endIndex;
|
||||
|
||||
/**
|
||||
* Creates a new stream which reads data from the specified {@code buffer}
|
||||
* starting at the current {@code readerIndex} and ending at the current
|
||||
* {@code writerIndex}.
|
||||
*/
|
||||
public ByteBufInputStream(ByteBuf buffer) {
|
||||
this(buffer, buffer.readableBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stream which reads data from the specified {@code buffer}
|
||||
* starting at the current {@code readerIndex} and ending at
|
||||
* {@code readerIndex + length}.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@code readerIndex + length} is greater than
|
||||
* {@code writerIndex}
|
||||
*/
|
||||
public ByteBufInputStream(ByteBuf buffer, int length) {
|
||||
if (buffer == null) {
|
||||
throw new NullPointerException("buffer");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length: " + length);
|
||||
}
|
||||
if (length > buffer.readableBytes()) {
|
||||
throw new IndexOutOfBoundsException("Too many bytes to be read - Needs "
|
||||
+ length + ", maximum is " + buffer.readableBytes());
|
||||
}
|
||||
|
||||
this.buffer = buffer;
|
||||
startIndex = buffer.readerIndex();
|
||||
endIndex = startIndex + length;
|
||||
buffer.markReaderIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of read bytes by this stream so far.
|
||||
*/
|
||||
public int readBytes() {
|
||||
return buffer.readerIndex() - startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return endIndex - buffer.readerIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readlimit) {
|
||||
buffer.markReaderIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!buffer.isReadable()) {
|
||||
return -1;
|
||||
}
|
||||
return buffer.readByte() & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int available = available();
|
||||
if (available == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = Math.min(available, len);
|
||||
buffer.readBytes(b, off, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
buffer.resetReaderIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
if (n > Integer.MAX_VALUE) {
|
||||
return skipBytes(Integer.MAX_VALUE);
|
||||
} else {
|
||||
return skipBytes((int) n);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() throws IOException {
|
||||
checkAvailable(1);
|
||||
return read() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() throws IOException {
|
||||
if (!buffer.isReadable()) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return buffer.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() throws IOException {
|
||||
return (char) readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() throws IOException {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() throws IOException {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b) throws IOException {
|
||||
readFully(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b, int off, int len) throws IOException {
|
||||
checkAvailable(len);
|
||||
buffer.readBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
checkAvailable(4);
|
||||
return buffer.readInt();
|
||||
}
|
||||
|
||||
private final StringBuilder lineBuf = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
lineBuf.setLength(0);
|
||||
|
||||
loop: while (true) {
|
||||
if (!buffer.isReadable()) {
|
||||
return lineBuf.length() > 0 ? lineBuf.toString() : null;
|
||||
}
|
||||
|
||||
int c = buffer.readUnsignedByte();
|
||||
switch (c) {
|
||||
case '\n':
|
||||
break loop;
|
||||
|
||||
case '\r':
|
||||
if (buffer.isReadable() && (char) buffer.getUnsignedByte(buffer.readerIndex()) == '\n') {
|
||||
buffer.skipBytes(1);
|
||||
}
|
||||
break loop;
|
||||
|
||||
default:
|
||||
lineBuf.append((char) c);
|
||||
}
|
||||
}
|
||||
|
||||
return lineBuf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
checkAvailable(8);
|
||||
return buffer.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
checkAvailable(2);
|
||||
return buffer.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF() throws IOException {
|
||||
return DataInputStream.readUTF(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() throws IOException {
|
||||
return readByte() & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() throws IOException {
|
||||
return readShort() & 0xffff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n) throws IOException {
|
||||
int nBytes = Math.min(available(), n);
|
||||
buffer.skipBytes(nBytes);
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
private void checkAvailable(int fieldSize) throws IOException {
|
||||
if (fieldSize < 0) {
|
||||
throw new IndexOutOfBoundsException("fieldSize cannot be a negative number");
|
||||
}
|
||||
if (fieldSize > available()) {
|
||||
throw new EOFException("fieldSize is too long! Length is " + fieldSize
|
||||
+ ", but maximum is " + available());
|
||||
}
|
||||
}
|
||||
}
|
146
common/src/common/net/buffer/ByteBufOutputStream.java
Normal file
146
common/src/common/net/buffer/ByteBufOutputStream.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An {@link OutputStream} which writes data to a {@link ByteBuf}.
|
||||
* <p>
|
||||
* A write operation against this stream will occur at the {@code writerIndex}
|
||||
* of its underlying buffer and the {@code writerIndex} will increase during
|
||||
* the write operation.
|
||||
* <p>
|
||||
* This stream implements {@link DataOutput} for your convenience.
|
||||
* The endianness of the stream is not always big endian but depends on
|
||||
* the endianness of the underlying buffer.
|
||||
*
|
||||
* @see ByteBufInputStream
|
||||
*/
|
||||
public class ByteBufOutputStream extends OutputStream implements DataOutput {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final int startIndex;
|
||||
private final DataOutputStream utf8out = new DataOutputStream(this);
|
||||
|
||||
/**
|
||||
* Creates a new stream which writes data to the specified {@code buffer}.
|
||||
*/
|
||||
public ByteBufOutputStream(ByteBuf buffer) {
|
||||
if (buffer == null) {
|
||||
throw new NullPointerException("buffer");
|
||||
}
|
||||
this.buffer = buffer;
|
||||
startIndex = buffer.writerIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of written bytes by this stream so far.
|
||||
*/
|
||||
public int writtenBytes() {
|
||||
return buffer.writerIndex() - startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.writeBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
buffer.writeBytes(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
buffer.writeByte((byte) b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean v) throws IOException {
|
||||
write(v? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int v) throws IOException {
|
||||
write(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(String s) throws IOException {
|
||||
int len = s.length();
|
||||
for (int i = 0; i < len; i ++) {
|
||||
write((byte) s.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChar(int v) throws IOException {
|
||||
writeShort((short) v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChars(String s) throws IOException {
|
||||
int len = s.length();
|
||||
for (int i = 0 ; i < len ; i ++) {
|
||||
writeChar(s.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double v) throws IOException {
|
||||
writeLong(Double.doubleToLongBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float v) throws IOException {
|
||||
writeInt(Float.floatToIntBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int v) throws IOException {
|
||||
buffer.writeInt(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long v) throws IOException {
|
||||
buffer.writeLong(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int v) throws IOException {
|
||||
buffer.writeShort((short) v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(String s) throws IOException {
|
||||
utf8out.writeUTF(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the buffer where this stream is writing data.
|
||||
*/
|
||||
public ByteBuf buffer() {
|
||||
return buffer;
|
||||
}
|
||||
}
|
126
common/src/common/net/buffer/ByteBufProcessor.java
Normal file
126
common/src/common/net/buffer/ByteBufProcessor.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
public interface ByteBufProcessor {
|
||||
|
||||
/**
|
||||
* Aborts on a {@code NUL (0x00)}.
|
||||
*/
|
||||
ByteBufProcessor FIND_NUL = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value != 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a non-{@code NUL (0x00)}.
|
||||
*/
|
||||
ByteBufProcessor FIND_NON_NUL = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a {@code CR ('\r')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_CR = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value != '\r';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a non-{@code CR ('\r')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_NON_CR = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value == '\r';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a {@code LF ('\n')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_LF = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value != '\n';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a non-{@code LF ('\n')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_NON_LF = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value == '\n';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a {@code CR ('\r')} or a {@code LF ('\n')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_CRLF = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value != '\r' && value != '\n';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a byte which is neither a {@code CR ('\r')} nor a {@code LF ('\n')}.
|
||||
*/
|
||||
ByteBufProcessor FIND_NON_CRLF = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value == '\r' || value == '\n';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a linear whitespace (a ({@code ' '} or a {@code '\t'}).
|
||||
*/
|
||||
ByteBufProcessor FIND_LINEAR_WHITESPACE = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value != ' ' && value != '\t';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aborts on a byte which is not a linear whitespace (neither {@code ' '} nor {@code '\t'}).
|
||||
*/
|
||||
ByteBufProcessor FIND_NON_LINEAR_WHITESPACE = new ByteBufProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value == ' ' || value == '\t';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {@code true} if the processor wants to continue the loop and handle the next byte in the buffer.
|
||||
* {@code false} if the processor wants to stop handling bytes and abort the loop.
|
||||
*/
|
||||
boolean process(byte value) throws Exception;
|
||||
}
|
483
common/src/common/net/buffer/ByteBufUtil.java
Normal file
483
common/src/common/net/buffer/ByteBufUtil.java
Normal file
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.util.Locale;
|
||||
|
||||
import common.net.util.CharsetUtil;
|
||||
import common.net.util.Recycler;
|
||||
import common.net.util.Recycler.Handle;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.SystemPropertyUtil;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* A collection of utility methods that is related with handling {@link ByteBuf}.
|
||||
*/
|
||||
public final class ByteBufUtil {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class);
|
||||
|
||||
private static final char[] HEXDUMP_TABLE = new char[256 * 4];
|
||||
|
||||
static final ByteBufAllocator DEFAULT_ALLOCATOR;
|
||||
|
||||
private static final int THREAD_LOCAL_BUFFER_SIZE;
|
||||
|
||||
static {
|
||||
final char[] DIGITS = "0123456789abcdef".toCharArray();
|
||||
for (int i = 0; i < 256; i ++) {
|
||||
HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F];
|
||||
HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
|
||||
}
|
||||
|
||||
String allocType = SystemPropertyUtil.get("game.net.allocator.type", "unpooled").toLowerCase(Locale.US).trim();
|
||||
ByteBufAllocator alloc;
|
||||
if ("unpooled".equals(allocType)) {
|
||||
alloc = UnpooledByteBufAllocator.DEFAULT;
|
||||
logger.debug("-Dgame.net.allocator.type: {}", allocType);
|
||||
} else if ("pooled".equals(allocType)) {
|
||||
alloc = PooledByteBufAllocator.DEFAULT;
|
||||
logger.debug("-Dgame.net.allocator.type: {}", allocType);
|
||||
} else {
|
||||
alloc = UnpooledByteBufAllocator.DEFAULT;
|
||||
logger.debug("-Dgame.net.allocator.type: unpooled (unknown: {})", allocType);
|
||||
}
|
||||
|
||||
DEFAULT_ALLOCATOR = alloc;
|
||||
|
||||
THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("game.net.threadLocalDirectBufferSize", 64 * 1024);
|
||||
logger.debug("-Dgame.net.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
|
||||
* of the specified buffer's readable bytes.
|
||||
*/
|
||||
public static String hexDump(ByteBuf buffer) {
|
||||
return hexDump(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
|
||||
* of the specified buffer's sub-region.
|
||||
*/
|
||||
public static String hexDump(ByteBuf buffer, int fromIndex, int length) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length: " + length);
|
||||
}
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int endIndex = fromIndex + length;
|
||||
char[] buf = new char[length << 1];
|
||||
|
||||
int srcIdx = fromIndex;
|
||||
int dstIdx = 0;
|
||||
for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) {
|
||||
System.arraycopy(
|
||||
HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1,
|
||||
buf, dstIdx, 2);
|
||||
}
|
||||
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash code of the specified buffer. This method is
|
||||
* useful when implementing a new buffer type.
|
||||
*/
|
||||
public static int hashCode(ByteBuf buffer) {
|
||||
final int aLen = buffer.readableBytes();
|
||||
final int intCount = aLen >>> 2;
|
||||
final int byteCount = aLen & 3;
|
||||
|
||||
int hashCode = 1;
|
||||
int arrayIndex = buffer.readerIndex();
|
||||
if (buffer.order() == ByteOrder.BIG_ENDIAN) {
|
||||
for (int i = intCount; i > 0; i --) {
|
||||
hashCode = 31 * hashCode + buffer.getInt(arrayIndex);
|
||||
arrayIndex += 4;
|
||||
}
|
||||
} else {
|
||||
for (int i = intCount; i > 0; i --) {
|
||||
hashCode = 31 * hashCode + swapInt(buffer.getInt(arrayIndex));
|
||||
arrayIndex += 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = byteCount; i > 0; i --) {
|
||||
hashCode = 31 * hashCode + buffer.getByte(arrayIndex ++);
|
||||
}
|
||||
|
||||
if (hashCode == 0) {
|
||||
hashCode = 1;
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the two specified buffers are
|
||||
* identical to each other as described in {@code ChannelBuffer#equals(Object)}.
|
||||
* This method is useful when implementing a new buffer type.
|
||||
*/
|
||||
public static boolean equals(ByteBuf bufferA, ByteBuf bufferB) {
|
||||
final int aLen = bufferA.readableBytes();
|
||||
if (aLen != bufferB.readableBytes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int longCount = aLen >>> 3;
|
||||
final int byteCount = aLen & 7;
|
||||
|
||||
int aIndex = bufferA.readerIndex();
|
||||
int bIndex = bufferB.readerIndex();
|
||||
|
||||
if (bufferA.order() == bufferB.order()) {
|
||||
for (int i = longCount; i > 0; i --) {
|
||||
if (bufferA.getLong(aIndex) != bufferB.getLong(bIndex)) {
|
||||
return false;
|
||||
}
|
||||
aIndex += 8;
|
||||
bIndex += 8;
|
||||
}
|
||||
} else {
|
||||
for (int i = longCount; i > 0; i --) {
|
||||
if (bufferA.getLong(aIndex) != swapLong(bufferB.getLong(bIndex))) {
|
||||
return false;
|
||||
}
|
||||
aIndex += 8;
|
||||
bIndex += 8;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = byteCount; i > 0; i --) {
|
||||
if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) {
|
||||
return false;
|
||||
}
|
||||
aIndex ++;
|
||||
bIndex ++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two specified buffers as described in {@link ByteBuf#compareTo(ByteBuf)}.
|
||||
* This method is useful when implementing a new buffer type.
|
||||
*/
|
||||
public static int compare(ByteBuf bufferA, ByteBuf bufferB) {
|
||||
final int aLen = bufferA.readableBytes();
|
||||
final int bLen = bufferB.readableBytes();
|
||||
final int minLength = Math.min(aLen, bLen);
|
||||
final int uintCount = minLength >>> 2;
|
||||
final int byteCount = minLength & 3;
|
||||
|
||||
int aIndex = bufferA.readerIndex();
|
||||
int bIndex = bufferB.readerIndex();
|
||||
|
||||
if (bufferA.order() == bufferB.order()) {
|
||||
for (int i = uintCount; i > 0; i --) {
|
||||
long va = bufferA.getUnsignedInt(aIndex);
|
||||
long vb = bufferB.getUnsignedInt(bIndex);
|
||||
if (va > vb) {
|
||||
return 1;
|
||||
}
|
||||
if (va < vb) {
|
||||
return -1;
|
||||
}
|
||||
aIndex += 4;
|
||||
bIndex += 4;
|
||||
}
|
||||
} else {
|
||||
for (int i = uintCount; i > 0; i --) {
|
||||
long va = bufferA.getUnsignedInt(aIndex);
|
||||
long vb = swapInt(bufferB.getInt(bIndex)) & 0xFFFFFFFFL;
|
||||
if (va > vb) {
|
||||
return 1;
|
||||
}
|
||||
if (va < vb) {
|
||||
return -1;
|
||||
}
|
||||
aIndex += 4;
|
||||
bIndex += 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = byteCount; i > 0; i --) {
|
||||
short va = bufferA.getUnsignedByte(aIndex);
|
||||
short vb = bufferB.getUnsignedByte(bIndex);
|
||||
if (va > vb) {
|
||||
return 1;
|
||||
}
|
||||
if (va < vb) {
|
||||
return -1;
|
||||
}
|
||||
aIndex ++;
|
||||
bIndex ++;
|
||||
}
|
||||
|
||||
return aLen - bLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of {@link ByteBuf#indexOf(int, int, byte)}.
|
||||
* This method is useful when implementing a new buffer type.
|
||||
*/
|
||||
public static int indexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
|
||||
if (fromIndex <= toIndex) {
|
||||
return firstIndexOf(buffer, fromIndex, toIndex, value);
|
||||
} else {
|
||||
return lastIndexOf(buffer, fromIndex, toIndex, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the endianness of the specified 16-bit short integer.
|
||||
*/
|
||||
public static short swapShort(short value) {
|
||||
return Short.reverseBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the endianness of the specified 24-bit medium integer.
|
||||
*/
|
||||
public static int swapMedium(int value) {
|
||||
int swapped = value << 16 & 0xff0000 | value & 0xff00 | value >>> 16 & 0xff;
|
||||
if ((swapped & 0x800000) != 0) {
|
||||
swapped |= 0xff000000;
|
||||
}
|
||||
return swapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the endianness of the specified 32-bit integer.
|
||||
*/
|
||||
public static int swapInt(int value) {
|
||||
return Integer.reverseBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the endianness of the specified 64-bit long integer.
|
||||
*/
|
||||
public static long swapLong(long value) {
|
||||
return Long.reverseBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given amount of bytes into a new {@link ByteBuf} that is allocated from the {@link ByteBufAllocator}.
|
||||
*/
|
||||
public static ByteBuf readBytes(ByteBufAllocator alloc, ByteBuf buffer, int length) {
|
||||
boolean release = true;
|
||||
ByteBuf dst = alloc.buffer(length);
|
||||
try {
|
||||
buffer.readBytes(dst);
|
||||
release = false;
|
||||
return dst;
|
||||
} finally {
|
||||
if (release) {
|
||||
dst.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int firstIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
|
||||
fromIndex = Math.max(fromIndex, 0);
|
||||
if (fromIndex >= toIndex || buffer.capacity() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = fromIndex; i < toIndex; i ++) {
|
||||
if (buffer.getByte(i) == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int lastIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
|
||||
fromIndex = Math.min(fromIndex, buffer.capacity());
|
||||
if (fromIndex < 0 || buffer.capacity() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = fromIndex - 1; i >= toIndex; i --) {
|
||||
if (buffer.getByte(i) == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which
|
||||
* is allocated via the {@link ByteBufAllocator}.
|
||||
*/
|
||||
public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) {
|
||||
return encodeString0(alloc, false, src, charset);
|
||||
}
|
||||
|
||||
static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset) {
|
||||
final CharsetEncoder encoder = CharsetUtil.getEncoder(charset);
|
||||
int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar());
|
||||
boolean release = true;
|
||||
final ByteBuf dst;
|
||||
if (enforceHeap) {
|
||||
dst = alloc.heapBuffer(length);
|
||||
} else {
|
||||
dst = alloc.buffer(length);
|
||||
}
|
||||
try {
|
||||
final ByteBuffer dstBuf = dst.internalNioBuffer(0, length);
|
||||
final int pos = dstBuf.position();
|
||||
CoderResult cr = encoder.encode(src, dstBuf, true);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
cr = encoder.flush(dstBuf);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
dst.writerIndex(dst.writerIndex() + dstBuf.position() - pos);
|
||||
release = false;
|
||||
return dst;
|
||||
} catch (CharacterCodingException x) {
|
||||
throw new IllegalStateException(x);
|
||||
} finally {
|
||||
if (release) {
|
||||
dst.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String decodeString(ByteBuffer src, Charset charset) {
|
||||
final CharsetDecoder decoder = CharsetUtil.getDecoder(charset);
|
||||
final CharBuffer dst = CharBuffer.allocate(
|
||||
(int) ((double) src.remaining() * decoder.maxCharsPerByte()));
|
||||
try {
|
||||
CoderResult cr = decoder.decode(src, dst, true);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
cr = decoder.flush(dst);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
} catch (CharacterCodingException x) {
|
||||
throw new IllegalStateException(x);
|
||||
}
|
||||
return dst.flip().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached thread-local direct buffer, if available.
|
||||
*
|
||||
* @return a cached thread-local direct buffer, if available. {@code null} otherwise.
|
||||
*/
|
||||
public static ByteBuf threadLocalDirectBuffer() {
|
||||
if (THREAD_LOCAL_BUFFER_SIZE <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
return ThreadLocalUnsafeDirectByteBuf.newInstance();
|
||||
} else {
|
||||
return ThreadLocalDirectByteBuf.newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf {
|
||||
|
||||
private static final Recycler<ThreadLocalUnsafeDirectByteBuf> RECYCLER =
|
||||
new Recycler<ThreadLocalUnsafeDirectByteBuf>() {
|
||||
@Override
|
||||
protected ThreadLocalUnsafeDirectByteBuf newObject(Handle handle) {
|
||||
return new ThreadLocalUnsafeDirectByteBuf(handle);
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadLocalUnsafeDirectByteBuf newInstance() {
|
||||
ThreadLocalUnsafeDirectByteBuf buf = RECYCLER.get();
|
||||
buf.setRefCnt(1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private final Handle handle;
|
||||
|
||||
private ThreadLocalUnsafeDirectByteBuf(Handle handle) {
|
||||
super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
if (capacity() > THREAD_LOCAL_BUFFER_SIZE) {
|
||||
super.deallocate();
|
||||
} else {
|
||||
clear();
|
||||
RECYCLER.recycle(this, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class ThreadLocalDirectByteBuf extends UnpooledDirectByteBuf {
|
||||
|
||||
private static final Recycler<ThreadLocalDirectByteBuf> RECYCLER = new Recycler<ThreadLocalDirectByteBuf>() {
|
||||
@Override
|
||||
protected ThreadLocalDirectByteBuf newObject(Handle handle) {
|
||||
return new ThreadLocalDirectByteBuf(handle);
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadLocalDirectByteBuf newInstance() {
|
||||
ThreadLocalDirectByteBuf buf = RECYCLER.get();
|
||||
buf.setRefCnt(1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private final Handle handle;
|
||||
|
||||
private ThreadLocalDirectByteBuf(Handle handle) {
|
||||
super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
if (capacity() > THREAD_LOCAL_BUFFER_SIZE) {
|
||||
super.deallocate();
|
||||
} else {
|
||||
clear();
|
||||
RECYCLER.recycle(this, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBufUtil() { }
|
||||
}
|
1602
common/src/common/net/buffer/CompositeByteBuf.java
Normal file
1602
common/src/common/net/buffer/CompositeByteBuf.java
Normal file
File diff suppressed because it is too large
Load diff
305
common/src/common/net/buffer/DuplicatedByteBuf.java
Normal file
305
common/src/common/net/buffer/DuplicatedByteBuf.java
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
|
||||
/**
|
||||
* A derived buffer which simply forwards all data access requests to its
|
||||
* parent. It is recommended to use {@link ByteBuf#duplicate()} instead
|
||||
* of calling the constructor explicitly.
|
||||
*/
|
||||
public class DuplicatedByteBuf extends AbstractDerivedByteBuf {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
|
||||
public DuplicatedByteBuf(ByteBuf buffer) {
|
||||
super(buffer.maxCapacity());
|
||||
|
||||
if (buffer instanceof DuplicatedByteBuf) {
|
||||
this.buffer = ((DuplicatedByteBuf) buffer).buffer;
|
||||
} else {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
setIndex(buffer.readerIndex(), buffer.writerIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return buffer.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return buffer.order();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buffer.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return buffer.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
buffer.capacity(newCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return buffer.hasArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return buffer.arrayOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return buffer.hasMemoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return buffer.memoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return _getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return buffer.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
return _getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return buffer.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
return _getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return buffer.getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return _getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return buffer.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
return _getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return buffer.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
return buffer.copy(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return buffer.slice(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
buffer.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
buffer.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
buffer.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
_setByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
buffer.setByte(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
_setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
buffer.setShort(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
_setMedium(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
buffer.setMedium(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
_setInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
buffer.setInt(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
_setLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
buffer.setLong(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
buffer.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
buffer.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
buffer.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length)
|
||||
throws IOException {
|
||||
buffer.getBytes(index, out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length)
|
||||
throws IOException {
|
||||
return buffer.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length)
|
||||
throws IOException {
|
||||
return buffer.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length)
|
||||
throws IOException {
|
||||
return buffer.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return buffer.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return buffer.nioBuffers(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
return buffer.forEachByte(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
return buffer.forEachByteDesc(index, length, processor);
|
||||
}
|
||||
}
|
||||
|
870
common/src/common/net/buffer/EmptyByteBuf.java
Normal file
870
common/src/common/net/buffer/EmptyByteBuf.java
Normal file
|
@ -0,0 +1,870 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import common.net.util.internal.EmptyArrays;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* An empty {@link ByteBuf} whose capacity and maximum capacity are all {@code 0}.
|
||||
*/
|
||||
public final class EmptyByteBuf extends ByteBuf {
|
||||
|
||||
private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocateDirect(0);
|
||||
private static final long EMPTY_BYTE_BUFFER_ADDRESS;
|
||||
|
||||
static {
|
||||
long emptyByteBufferAddress = 0;
|
||||
try {
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
emptyByteBufferAddress = PlatformDependent.directBufferAddress(EMPTY_BYTE_BUFFER);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
EMPTY_BYTE_BUFFER_ADDRESS = emptyByteBufferAddress;
|
||||
}
|
||||
|
||||
private final ByteBufAllocator alloc;
|
||||
private final ByteOrder order;
|
||||
private final String str;
|
||||
private EmptyByteBuf swapped;
|
||||
|
||||
public EmptyByteBuf(ByteBufAllocator alloc) {
|
||||
this(alloc, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
private EmptyByteBuf(ByteBufAllocator alloc, ByteOrder order) {
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
this.order = order;
|
||||
str = StringUtil.simpleClassName(this) + (order == ByteOrder.BIG_ENDIAN? "BE" : "LE");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return alloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxCapacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
if (endianness == null) {
|
||||
throw new NullPointerException("endianness");
|
||||
}
|
||||
if (endianness == order()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
EmptyByteBuf swapped = this.swapped;
|
||||
if (swapped != null) {
|
||||
return swapped;
|
||||
}
|
||||
|
||||
this.swapped = swapped = new EmptyByteBuf(alloc(), endianness);
|
||||
return swapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readerIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readerIndex(int readerIndex) {
|
||||
return checkIndex(readerIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writerIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writerIndex(int writerIndex) {
|
||||
return checkIndex(writerIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setIndex(int readerIndex, int writerIndex) {
|
||||
checkIndex(readerIndex);
|
||||
checkIndex(writerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableBytes() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writableBytes() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxWritableBytes() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf clear() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markReaderIndex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetReaderIndex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markWriterIndex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetWriterIndex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardReadBytes() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardSomeReadBytes() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ensureWritable(int minWritableBytes) {
|
||||
if (minWritableBytes < 0) {
|
||||
throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)");
|
||||
}
|
||||
if (minWritableBytes != 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ensureWritable(int minWritableBytes, boolean force) {
|
||||
if (minWritableBytes < 0) {
|
||||
throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)");
|
||||
}
|
||||
|
||||
if (minWritableBytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getUnsignedByte(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMedium(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst) {
|
||||
return checkIndex(index, dst.writableBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst) {
|
||||
return checkIndex(index, dst.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
return checkIndex(index, dst.remaining());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) {
|
||||
checkIndex(index, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBoolean(int index, boolean value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int index, int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setFloat(int index, float value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setDouble(int index, double value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src) {
|
||||
return checkIndex(index, src.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
return checkIndex(index, src.remaining());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) {
|
||||
checkIndex(index, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) {
|
||||
checkIndex(index, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setZero(int index, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readUnsignedByte() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMedium() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMedium() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedInt() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst) {
|
||||
return checkLength(dst.writableBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst) {
|
||||
return checkLength(dst.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
return checkLength(dst.remaining());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) {
|
||||
checkLength(length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf skipBytes(int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBoolean(boolean value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeByte(int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShort(int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMedium(int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeFloat(float value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeDouble(double value) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src) {
|
||||
return checkLength(src.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuffer src) {
|
||||
return checkLength(src.remaining());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(InputStream in, int length) {
|
||||
checkLength(length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(ScatteringByteChannel in, int length) {
|
||||
checkLength(length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeZero(int length) {
|
||||
return checkLength(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int fromIndex, int toIndex, byte value) {
|
||||
checkIndex(fromIndex);
|
||||
checkIndex(toIndex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(byte value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int length, byte value) {
|
||||
checkLength(length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int index, int length, byte value) {
|
||||
checkIndex(index, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(ByteBufProcessor processor) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
checkIndex(index, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(ByteBufProcessor processor) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
checkIndex(index, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return checkIndex(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer() {
|
||||
return EMPTY_BYTE_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return nioBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers() {
|
||||
return new ByteBuffer[] { EMPTY_BYTE_BUFFER };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return nioBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return EMPTY_BYTE_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return EmptyArrays.EMPTY_BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return EMPTY_BYTE_BUFFER_ADDRESS != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
if (hasMemoryAddress()) {
|
||||
return EMPTY_BYTE_BUFFER_ADDRESS;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
checkIndex(index, length);
|
||||
return toString(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof ByteBuf && !((ByteBuf) obj).isReadable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ByteBuf buffer) {
|
||||
return buffer.isReadable()? -1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(int size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(int size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private ByteBuf checkIndex(int index) {
|
||||
if (index != 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private ByteBuf checkIndex(int index, int length) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length: " + length);
|
||||
}
|
||||
if (index != 0 || length != 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private ByteBuf checkLength(int length) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length: " + length + " (expected: >= 0)");
|
||||
}
|
||||
if (length != 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
477
common/src/common/net/buffer/PoolArena.java
Normal file
477
common/src/common/net/buffer/PoolArena.java
Normal file
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
abstract class PoolArena<T> {
|
||||
|
||||
static final int numTinySubpagePools = 512 >>> 4;
|
||||
|
||||
final PooledByteBufAllocator parent;
|
||||
|
||||
private final int maxOrder;
|
||||
final int pageSize;
|
||||
final int pageShifts;
|
||||
final int chunkSize;
|
||||
final int subpageOverflowMask;
|
||||
final int numSmallSubpagePools;
|
||||
private final PoolSubpage<T>[] tinySubpagePools;
|
||||
private final PoolSubpage<T>[] smallSubpagePools;
|
||||
|
||||
private final PoolChunkList<T> q050;
|
||||
private final PoolChunkList<T> q025;
|
||||
private final PoolChunkList<T> q000;
|
||||
private final PoolChunkList<T> qInit;
|
||||
private final PoolChunkList<T> q075;
|
||||
private final PoolChunkList<T> q100;
|
||||
|
||||
// TODO: Test if adding padding helps under contention
|
||||
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
|
||||
|
||||
protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
this.parent = parent;
|
||||
this.pageSize = pageSize;
|
||||
this.maxOrder = maxOrder;
|
||||
this.pageShifts = pageShifts;
|
||||
this.chunkSize = chunkSize;
|
||||
subpageOverflowMask = ~(pageSize - 1);
|
||||
tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
|
||||
for (int i = 0; i < tinySubpagePools.length; i ++) {
|
||||
tinySubpagePools[i] = newSubpagePoolHead(pageSize);
|
||||
}
|
||||
|
||||
numSmallSubpagePools = pageShifts - 9;
|
||||
smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
|
||||
for (int i = 0; i < smallSubpagePools.length; i ++) {
|
||||
smallSubpagePools[i] = newSubpagePoolHead(pageSize);
|
||||
}
|
||||
|
||||
q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE);
|
||||
q075 = new PoolChunkList<T>(this, q100, 75, 100);
|
||||
q050 = new PoolChunkList<T>(this, q075, 50, 100);
|
||||
q025 = new PoolChunkList<T>(this, q050, 25, 75);
|
||||
q000 = new PoolChunkList<T>(this, q025, 1, 50);
|
||||
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);
|
||||
|
||||
q100.prevList = q075;
|
||||
q075.prevList = q050;
|
||||
q050.prevList = q025;
|
||||
q025.prevList = q000;
|
||||
q000.prevList = null;
|
||||
qInit.prevList = qInit;
|
||||
}
|
||||
|
||||
private PoolSubpage<T> newSubpagePoolHead(int pageSize) {
|
||||
PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
|
||||
head.prev = head;
|
||||
head.next = head;
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
private PoolSubpage<T>[] newSubpagePoolArray(int size) {
|
||||
return new PoolSubpage[size];
|
||||
}
|
||||
|
||||
abstract boolean isDirect();
|
||||
|
||||
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
|
||||
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
|
||||
allocate(cache, buf, reqCapacity);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int tinyIdx(int normCapacity) {
|
||||
return normCapacity >>> 4;
|
||||
}
|
||||
|
||||
static int smallIdx(int normCapacity) {
|
||||
int tableIdx = 0;
|
||||
int i = normCapacity >>> 10;
|
||||
while (i != 0) {
|
||||
i >>>= 1;
|
||||
tableIdx ++;
|
||||
}
|
||||
return tableIdx;
|
||||
}
|
||||
|
||||
// capacity < pageSize
|
||||
boolean isTinyOrSmall(int normCapacity) {
|
||||
return (normCapacity & subpageOverflowMask) == 0;
|
||||
}
|
||||
|
||||
// normCapacity < 512
|
||||
static boolean isTiny(int normCapacity) {
|
||||
return (normCapacity & 0xFFFFFE00) == 0;
|
||||
}
|
||||
|
||||
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
|
||||
final int normCapacity = normalizeCapacity(reqCapacity);
|
||||
if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
|
||||
int tableIdx;
|
||||
PoolSubpage<T>[] table;
|
||||
if (isTiny(normCapacity)) { // < 512
|
||||
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
|
||||
// was able to allocate out of the cache so move on
|
||||
return;
|
||||
}
|
||||
tableIdx = tinyIdx(normCapacity);
|
||||
table = tinySubpagePools;
|
||||
} else {
|
||||
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
|
||||
// was able to allocate out of the cache so move on
|
||||
return;
|
||||
}
|
||||
tableIdx = smallIdx(normCapacity);
|
||||
table = smallSubpagePools;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
final PoolSubpage<T> head = table[tableIdx];
|
||||
final PoolSubpage<T> s = head.next;
|
||||
if (s != head) {
|
||||
assert s.doNotDestroy && s.elemSize == normCapacity;
|
||||
long handle = s.allocate();
|
||||
assert handle >= 0;
|
||||
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (normCapacity <= chunkSize) {
|
||||
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
|
||||
// was able to allocate out of the cache so move on
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Huge allocations are never served via the cache so just call allocateHuge
|
||||
allocateHuge(buf, reqCapacity);
|
||||
return;
|
||||
}
|
||||
allocateNormal(buf, reqCapacity, normCapacity);
|
||||
}
|
||||
|
||||
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
|
||||
if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
|
||||
q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
|
||||
q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a new chunk.
|
||||
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
|
||||
long handle = c.allocate(normCapacity);
|
||||
assert handle > 0;
|
||||
c.initBuf(buf, handle, reqCapacity);
|
||||
qInit.add(c);
|
||||
}
|
||||
|
||||
private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
|
||||
buf.initUnpooled(newUnpooledChunk(reqCapacity), reqCapacity);
|
||||
}
|
||||
|
||||
void free(PoolChunk<T> chunk, long handle, int normCapacity) {
|
||||
if (chunk.unpooled) {
|
||||
destroyChunk(chunk);
|
||||
} else {
|
||||
PoolThreadCache cache = parent.threadCache.get();
|
||||
if (cache.add(this, chunk, handle, normCapacity)) {
|
||||
// cached so not free it.
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
chunk.parent.free(chunk, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PoolSubpage<T> findSubpagePoolHead(int elemSize) {
|
||||
int tableIdx;
|
||||
PoolSubpage<T>[] table;
|
||||
if (isTiny(elemSize)) { // < 512
|
||||
tableIdx = elemSize >>> 4;
|
||||
table = tinySubpagePools;
|
||||
} else {
|
||||
tableIdx = 0;
|
||||
elemSize >>>= 10;
|
||||
while (elemSize != 0) {
|
||||
elemSize >>>= 1;
|
||||
tableIdx ++;
|
||||
}
|
||||
table = smallSubpagePools;
|
||||
}
|
||||
|
||||
return table[tableIdx];
|
||||
}
|
||||
|
||||
int normalizeCapacity(int reqCapacity) {
|
||||
if (reqCapacity < 0) {
|
||||
throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)");
|
||||
}
|
||||
if (reqCapacity >= chunkSize) {
|
||||
return reqCapacity;
|
||||
}
|
||||
|
||||
if (!isTiny(reqCapacity)) { // >= 512
|
||||
// Doubled
|
||||
|
||||
int normalizedCapacity = reqCapacity;
|
||||
normalizedCapacity --;
|
||||
normalizedCapacity |= normalizedCapacity >>> 1;
|
||||
normalizedCapacity |= normalizedCapacity >>> 2;
|
||||
normalizedCapacity |= normalizedCapacity >>> 4;
|
||||
normalizedCapacity |= normalizedCapacity >>> 8;
|
||||
normalizedCapacity |= normalizedCapacity >>> 16;
|
||||
normalizedCapacity ++;
|
||||
|
||||
if (normalizedCapacity < 0) {
|
||||
normalizedCapacity >>>= 1;
|
||||
}
|
||||
|
||||
return normalizedCapacity;
|
||||
}
|
||||
|
||||
// Quantum-spaced
|
||||
if ((reqCapacity & 15) == 0) {
|
||||
return reqCapacity;
|
||||
}
|
||||
|
||||
return (reqCapacity & ~15) + 16;
|
||||
}
|
||||
|
||||
void reallocate(PooledByteBuf<T> buf, int newCapacity, boolean freeOldMemory) {
|
||||
if (newCapacity < 0 || newCapacity > buf.maxCapacity()) {
|
||||
throw new IllegalArgumentException("newCapacity: " + newCapacity);
|
||||
}
|
||||
|
||||
int oldCapacity = buf.length;
|
||||
if (oldCapacity == newCapacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
PoolChunk<T> oldChunk = buf.chunk;
|
||||
long oldHandle = buf.handle;
|
||||
T oldMemory = buf.memory;
|
||||
int oldOffset = buf.offset;
|
||||
int oldMaxLength = buf.maxLength;
|
||||
int readerIndex = buf.readerIndex();
|
||||
int writerIndex = buf.writerIndex();
|
||||
|
||||
allocate(parent.threadCache.get(), buf, newCapacity);
|
||||
if (newCapacity > oldCapacity) {
|
||||
memoryCopy(
|
||||
oldMemory, oldOffset,
|
||||
buf.memory, buf.offset, oldCapacity);
|
||||
} else if (newCapacity < oldCapacity) {
|
||||
if (readerIndex < newCapacity) {
|
||||
if (writerIndex > newCapacity) {
|
||||
writerIndex = newCapacity;
|
||||
}
|
||||
memoryCopy(
|
||||
oldMemory, oldOffset + readerIndex,
|
||||
buf.memory, buf.offset + readerIndex, writerIndex - readerIndex);
|
||||
} else {
|
||||
readerIndex = writerIndex = newCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
buf.setIndex(readerIndex, writerIndex);
|
||||
|
||||
if (freeOldMemory) {
|
||||
free(oldChunk, oldHandle, oldMaxLength);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract PoolChunk<T> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize);
|
||||
protected abstract PoolChunk<T> newUnpooledChunk(int capacity);
|
||||
protected abstract PooledByteBuf<T> newByteBuf(int maxCapacity);
|
||||
protected abstract void memoryCopy(T src, int srcOffset, T dst, int dstOffset, int length);
|
||||
protected abstract void destroyChunk(PoolChunk<T> chunk);
|
||||
|
||||
public synchronized String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Chunk(s) at 0~25%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(qInit);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("Chunk(s) at 0~50%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(q000);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("Chunk(s) at 25~75%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(q025);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("Chunk(s) at 50~100%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(q050);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("Chunk(s) at 75~100%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(q075);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("Chunk(s) at 100%:");
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(q100);
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("tiny subpages:");
|
||||
for (int i = 1; i < tinySubpagePools.length; i ++) {
|
||||
PoolSubpage<T> head = tinySubpagePools[i];
|
||||
if (head.next == head) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(i);
|
||||
buf.append(": ");
|
||||
PoolSubpage<T> s = head.next;
|
||||
for (;;) {
|
||||
buf.append(s);
|
||||
s = s.next;
|
||||
if (s == head) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("small subpages:");
|
||||
for (int i = 1; i < smallSubpagePools.length; i ++) {
|
||||
PoolSubpage<T> head = smallSubpagePools[i];
|
||||
if (head.next == head) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append(i);
|
||||
buf.append(": ");
|
||||
PoolSubpage<T> s = head.next;
|
||||
for (;;) {
|
||||
buf.append(s);
|
||||
s = s.next;
|
||||
if (s == head) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
static final class HeapArena extends PoolArena<byte[]> {
|
||||
|
||||
HeapArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
super(parent, pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PoolChunk<byte[]> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
return new PoolChunk<byte[]>(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
|
||||
return new PoolChunk<byte[]>(this, new byte[capacity], capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyChunk(PoolChunk<byte[]> chunk) {
|
||||
// Rely on GC.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PooledByteBuf<byte[]> newByteBuf(int maxCapacity) {
|
||||
return PooledHeapByteBuf.newInstance(maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.arraycopy(src, srcOffset, dst, dstOffset, length);
|
||||
}
|
||||
}
|
||||
|
||||
static final class DirectArena extends PoolArena<ByteBuffer> {
|
||||
|
||||
private static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe();
|
||||
|
||||
DirectArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
super(parent, pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
return new PoolChunk<ByteBuffer>(
|
||||
this, ByteBuffer.allocateDirect(chunkSize), pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PoolChunk<ByteBuffer> newUnpooledChunk(int capacity) {
|
||||
return new PoolChunk<ByteBuffer>(this, ByteBuffer.allocateDirect(capacity), capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyChunk(PoolChunk<ByteBuffer> chunk) {
|
||||
PlatformDependent.freeDirectBuffer(chunk.memory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
|
||||
if (HAS_UNSAFE) {
|
||||
return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
|
||||
} else {
|
||||
return PooledDirectByteBuf.newInstance(maxCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryCopy(ByteBuffer src, int srcOffset, ByteBuffer dst, int dstOffset, int length) {
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (HAS_UNSAFE) {
|
||||
PlatformDependent.copyMemory(
|
||||
PlatformDependent.directBufferAddress(src) + srcOffset,
|
||||
PlatformDependent.directBufferAddress(dst) + dstOffset, length);
|
||||
} else {
|
||||
// We must duplicate the NIO buffers because they may be accessed by other Netty buffers.
|
||||
src = src.duplicate();
|
||||
dst = dst.duplicate();
|
||||
src.position(srcOffset).limit(srcOffset + length);
|
||||
dst.position(dstOffset);
|
||||
dst.put(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
431
common/src/common/net/buffer/PoolChunk.java
Normal file
431
common/src/common/net/buffer/PoolChunk.java
Normal file
|
@ -0,0 +1,431 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
/**
|
||||
* Description of algorithm for PageRun/PoolSubpage allocation from PoolChunk
|
||||
*
|
||||
* Notation: The following terms are important to understand the code
|
||||
* > page - a page is the smallest unit of memory chunk that can be allocated
|
||||
* > chunk - a chunk is a collection of pages
|
||||
* > in this code chunkSize = 2^{maxOrder} * pageSize
|
||||
*
|
||||
* To begin we allocate a byte array of size = chunkSize
|
||||
* Whenever a ByteBuf of given size needs to be created we search for the first position
|
||||
* in the byte array that has enough empty space to accommodate the requested size and
|
||||
* return a (long) handle that encodes this offset information, (this memory segment is then
|
||||
* marked as reserved so it is always used by exactly one ByteBuf and no more)
|
||||
*
|
||||
* For simplicity all sizes are normalized according to PoolArena#normalizeCapacity method
|
||||
* This ensures that when we request for memory segments of size >= pageSize the normalizedCapacity
|
||||
* equals the next nearest power of 2
|
||||
*
|
||||
* To search for the first offset in chunk that has at least requested size available we construct a
|
||||
* complete balanced binary tree and store it in an array (just like heaps) - memoryMap
|
||||
*
|
||||
* The tree looks like this (the size of each node being mentioned in the parenthesis)
|
||||
*
|
||||
* depth=0 1 node (chunkSize)
|
||||
* depth=1 2 nodes (chunkSize/2)
|
||||
* ..
|
||||
* ..
|
||||
* depth=d 2^d nodes (chunkSize/2^d)
|
||||
* ..
|
||||
* depth=maxOrder 2^maxOrder nodes (chunkSize/2^{maxOrder} = pageSize)
|
||||
*
|
||||
* depth=maxOrder is the last level and the leafs consist of pages
|
||||
*
|
||||
* With this tree available searching in chunkArray translates like this:
|
||||
* To allocate a memory segment of size chunkSize/2^k we search for the first node (from left) at height k
|
||||
* which is unused
|
||||
*
|
||||
* Algorithm:
|
||||
* ----------
|
||||
* Encode the tree in memoryMap with the notation
|
||||
* memoryMap[id] = x => in the subtree rooted at id, the first node that is free to be allocated
|
||||
* is at depth x (counted from depth=0) i.e., at depths [depth_of_id, x), there is no node that is free
|
||||
*
|
||||
* As we allocate & free nodes, we update values stored in memoryMap so that the property is maintained
|
||||
*
|
||||
* Initialization -
|
||||
* In the beginning we construct the memoryMap array by storing the depth of a node at each node
|
||||
* i.e., memoryMap[id] = depth_of_id
|
||||
*
|
||||
* Observations:
|
||||
* -------------
|
||||
* 1) memoryMap[id] = depth_of_id => it is free / unallocated
|
||||
* 2) memoryMap[id] > depth_of_id => at least one of its child nodes is allocated, so we cannot allocate it, but
|
||||
* some of its children can still be allocated based on their availability
|
||||
* 3) memoryMap[id] = maxOrder + 1 => the node is fully allocated & thus none of its children can be allocated, it
|
||||
* is thus marked as unusable
|
||||
*
|
||||
* Algorithm: [allocateNode(d) => we want to find the first node (from left) at height h that can be allocated]
|
||||
* ----------
|
||||
* 1) start at root (i.e., depth = 0 or id = 1)
|
||||
* 2) if memoryMap[1] > d => cannot be allocated from this chunk
|
||||
* 3) if left node value <= h; we can allocate from left subtree so move to left and repeat until found
|
||||
* 4) else try in right subtree
|
||||
*
|
||||
* Algorithm: [allocateRun(size)]
|
||||
* ----------
|
||||
* 1) Compute d = log_2(chunkSize/size)
|
||||
* 2) Return allocateNode(d)
|
||||
*
|
||||
* Algorithm: [allocateSubpage(size)]
|
||||
* ----------
|
||||
* 1) use allocateNode(maxOrder) to find an empty (i.e., unused) leaf (i.e., page)
|
||||
* 2) use this handle to construct the PoolSubpage object or if it already exists just call init(normCapacity)
|
||||
* note that this PoolSubpage object is added to subpagesPool in the PoolArena when we init() it
|
||||
*
|
||||
* Note:
|
||||
* -----
|
||||
* In the implementation for improving cache coherence,
|
||||
* we store 2 pieces of information (i.e, 2 byte vals) as a short value in memoryMap
|
||||
*
|
||||
* memoryMap[id]= (depth_of_id, x)
|
||||
* where as per convention defined above
|
||||
* the second value (i.e, x) indicates that the first node which is free to be allocated is at depth x (from root)
|
||||
*/
|
||||
|
||||
final class PoolChunk<T> {
|
||||
|
||||
final PoolArena<T> arena;
|
||||
final T memory;
|
||||
final boolean unpooled;
|
||||
|
||||
private final byte[] memoryMap;
|
||||
private final byte[] depthMap;
|
||||
private final PoolSubpage<T>[] subpages;
|
||||
/** Used to determine if the requested capacity is equal to or greater than pageSize. */
|
||||
private final int subpageOverflowMask;
|
||||
private final int pageSize;
|
||||
private final int pageShifts;
|
||||
private final int maxOrder;
|
||||
private final int chunkSize;
|
||||
private final int log2ChunkSize;
|
||||
private final int maxSubpageAllocs;
|
||||
/** Used to mark memory as unusable */
|
||||
private final byte unusable;
|
||||
|
||||
private int freeBytes;
|
||||
|
||||
PoolChunkList<T> parent;
|
||||
PoolChunk<T> prev;
|
||||
PoolChunk<T> next;
|
||||
|
||||
// TODO: Test if adding padding helps under contention
|
||||
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
|
||||
|
||||
PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||
unpooled = false;
|
||||
this.arena = arena;
|
||||
this.memory = memory;
|
||||
this.pageSize = pageSize;
|
||||
this.pageShifts = pageShifts;
|
||||
this.maxOrder = maxOrder;
|
||||
this.chunkSize = chunkSize;
|
||||
unusable = (byte) (maxOrder + 1);
|
||||
log2ChunkSize = log2(chunkSize);
|
||||
subpageOverflowMask = ~(pageSize - 1);
|
||||
freeBytes = chunkSize;
|
||||
|
||||
assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
|
||||
maxSubpageAllocs = 1 << maxOrder;
|
||||
|
||||
// Generate the memory map.
|
||||
memoryMap = new byte[maxSubpageAllocs << 1];
|
||||
depthMap = new byte[memoryMap.length];
|
||||
int memoryMapIndex = 1;
|
||||
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
|
||||
int depth = 1 << d;
|
||||
for (int p = 0; p < depth; ++ p) {
|
||||
// in each level traverse left to right and set value to the depth of subtree
|
||||
memoryMap[memoryMapIndex] = (byte) d;
|
||||
depthMap[memoryMapIndex] = (byte) d;
|
||||
memoryMapIndex ++;
|
||||
}
|
||||
}
|
||||
|
||||
subpages = newSubpageArray(maxSubpageAllocs);
|
||||
}
|
||||
|
||||
/** Creates a special chunk that is not pooled. */
|
||||
PoolChunk(PoolArena<T> arena, T memory, int size) {
|
||||
unpooled = true;
|
||||
this.arena = arena;
|
||||
this.memory = memory;
|
||||
memoryMap = null;
|
||||
depthMap = null;
|
||||
subpages = null;
|
||||
subpageOverflowMask = 0;
|
||||
pageSize = 0;
|
||||
pageShifts = 0;
|
||||
maxOrder = 0;
|
||||
unusable = (byte) (maxOrder + 1);
|
||||
chunkSize = size;
|
||||
log2ChunkSize = log2(chunkSize);
|
||||
maxSubpageAllocs = 0;
|
||||
}
|
||||
|
||||
|
||||
private PoolSubpage<T>[] newSubpageArray(int size) {
|
||||
return new PoolSubpage[size];
|
||||
}
|
||||
|
||||
int usage() {
|
||||
final int freeBytes = this.freeBytes;
|
||||
if (freeBytes == 0) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
int freePercentage = (int) (freeBytes * 100L / chunkSize);
|
||||
if (freePercentage == 0) {
|
||||
return 99;
|
||||
}
|
||||
return 100 - freePercentage;
|
||||
}
|
||||
|
||||
long allocate(int normCapacity) {
|
||||
if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
|
||||
return allocateRun(normCapacity);
|
||||
} else {
|
||||
return allocateSubpage(normCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update method used by allocate
|
||||
* This is triggered only when a successor is allocated and all its predecessors
|
||||
* need to update their state
|
||||
* The minimal depth at which subtree rooted at id has some free space
|
||||
*
|
||||
* @param id id
|
||||
*/
|
||||
private void updateParentsAlloc(int id) {
|
||||
while (id > 1) {
|
||||
int parentId = id >>> 1;
|
||||
byte val1 = value(id);
|
||||
byte val2 = value(id ^ 1);
|
||||
byte val = val1 < val2 ? val1 : val2;
|
||||
setValue(parentId, val);
|
||||
id = parentId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update method used by free
|
||||
* This needs to handle the special case when both children are completely free
|
||||
* in which case parent be directly allocated on request of size = child-size * 2
|
||||
*
|
||||
* @param id id
|
||||
*/
|
||||
private void updateParentsFree(int id) {
|
||||
int logChild = depth(id) + 1;
|
||||
while (id > 1) {
|
||||
int parentId = id >>> 1;
|
||||
byte val1 = value(id);
|
||||
byte val2 = value(id ^ 1);
|
||||
logChild -= 1; // in first iteration equals log, subsequently reduce 1 from logChild as we traverse up
|
||||
|
||||
if (val1 == logChild && val2 == logChild) {
|
||||
setValue(parentId, (byte) (logChild - 1));
|
||||
} else {
|
||||
byte val = val1 < val2 ? val1 : val2;
|
||||
setValue(parentId, val);
|
||||
}
|
||||
|
||||
id = parentId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm to allocate an index in memoryMap when we query for a free node
|
||||
* at depth d
|
||||
*
|
||||
* @param d depth
|
||||
* @return index in memoryMap
|
||||
*/
|
||||
private int allocateNode(int d) {
|
||||
int id = 1;
|
||||
int initial = - (1 << d); // has last d bits = 0 and rest all = 1
|
||||
byte val = value(id);
|
||||
if (val > d) { // unusable
|
||||
return -1;
|
||||
}
|
||||
while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
|
||||
id <<= 1;
|
||||
val = value(id);
|
||||
if (val > d) {
|
||||
id ^= 1;
|
||||
val = value(id);
|
||||
}
|
||||
}
|
||||
byte value = value(id);
|
||||
assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
|
||||
value, id & initial, d);
|
||||
setValue(id, unusable); // mark as unusable
|
||||
updateParentsAlloc(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a run of pages (>=1)
|
||||
*
|
||||
* @param normCapacity normalized capacity
|
||||
* @return index in memoryMap
|
||||
*/
|
||||
private long allocateRun(int normCapacity) {
|
||||
int d = maxOrder - (log2(normCapacity) - pageShifts);
|
||||
int id = allocateNode(d);
|
||||
if (id < 0) {
|
||||
return id;
|
||||
}
|
||||
freeBytes -= runLength(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/ initialize a new PoolSubpage of normCapacity
|
||||
* Any PoolSubpage created/ initialized here is added to subpage pool in the PoolArena that owns this PoolChunk
|
||||
*
|
||||
* @param normCapacity normalized capacity
|
||||
* @return index in memoryMap
|
||||
*/
|
||||
private long allocateSubpage(int normCapacity) {
|
||||
int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
|
||||
int id = allocateNode(d);
|
||||
if (id < 0) {
|
||||
return id;
|
||||
}
|
||||
|
||||
final PoolSubpage<T>[] subpages = this.subpages;
|
||||
final int pageSize = this.pageSize;
|
||||
|
||||
freeBytes -= pageSize;
|
||||
|
||||
int subpageIdx = subpageIdx(id);
|
||||
PoolSubpage<T> subpage = subpages[subpageIdx];
|
||||
if (subpage == null) {
|
||||
subpage = new PoolSubpage<T>(this, id, runOffset(id), pageSize, normCapacity);
|
||||
subpages[subpageIdx] = subpage;
|
||||
} else {
|
||||
subpage.init(normCapacity);
|
||||
}
|
||||
return subpage.allocate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a subpage or a run of pages
|
||||
* When a subpage is freed from PoolSubpage, it might be added back to subpage pool of the owning PoolArena
|
||||
* If the subpage pool in PoolArena has at least one other PoolSubpage of given elemSize, we can
|
||||
* completely free the owning Page so it is available for subsequent allocations
|
||||
*
|
||||
* @param handle handle to free
|
||||
*/
|
||||
void free(long handle) {
|
||||
int memoryMapIdx = (int) handle;
|
||||
int bitmapIdx = (int) (handle >>> Integer.SIZE);
|
||||
|
||||
if (bitmapIdx != 0) { // free a subpage
|
||||
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
|
||||
assert subpage != null && subpage.doNotDestroy;
|
||||
if (subpage.free(bitmapIdx & 0x3FFFFFFF)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
freeBytes += runLength(memoryMapIdx);
|
||||
setValue(memoryMapIdx, depth(memoryMapIdx));
|
||||
updateParentsFree(memoryMapIdx);
|
||||
}
|
||||
|
||||
void initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity) {
|
||||
int memoryMapIdx = (int) handle;
|
||||
int bitmapIdx = (int) (handle >>> Integer.SIZE);
|
||||
if (bitmapIdx == 0) {
|
||||
byte val = value(memoryMapIdx);
|
||||
assert val == unusable : String.valueOf(val);
|
||||
buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx));
|
||||
} else {
|
||||
initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int reqCapacity) {
|
||||
initBufWithSubpage(buf, handle, (int) (handle >>> Integer.SIZE), reqCapacity);
|
||||
}
|
||||
|
||||
private void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int bitmapIdx, int reqCapacity) {
|
||||
assert bitmapIdx != 0;
|
||||
|
||||
int memoryMapIdx = (int) handle;
|
||||
|
||||
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
|
||||
assert subpage.doNotDestroy;
|
||||
assert reqCapacity <= subpage.elemSize;
|
||||
|
||||
buf.init(
|
||||
this, handle,
|
||||
runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize);
|
||||
}
|
||||
|
||||
private byte value(int id) {
|
||||
return memoryMap[id];
|
||||
}
|
||||
|
||||
private void setValue(int id, byte val) {
|
||||
memoryMap[id] = val;
|
||||
}
|
||||
|
||||
private byte depth(int id) {
|
||||
return depthMap[id];
|
||||
}
|
||||
|
||||
private static int log2(int val) {
|
||||
// compute the (0-based, with lsb = 0) position of highest set bit i.e, log2
|
||||
return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(val);
|
||||
}
|
||||
|
||||
private int runLength(int id) {
|
||||
// represents the size in #bytes supported by node 'id' in the tree
|
||||
return 1 << log2ChunkSize - depth(id);
|
||||
}
|
||||
|
||||
private int runOffset(int id) {
|
||||
// represents the 0-based offset in #bytes from start of the byte-array chunk
|
||||
int shift = id ^ 1 << depth(id);
|
||||
return shift * runLength(id);
|
||||
}
|
||||
|
||||
private int subpageIdx(int memoryMapIdx) {
|
||||
return memoryMapIdx ^ maxSubpageAllocs; // remove highest set bit, to get offset
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Chunk(");
|
||||
buf.append(Integer.toHexString(System.identityHashCode(this)));
|
||||
buf.append(": ");
|
||||
buf.append(usage());
|
||||
buf.append("%, ");
|
||||
buf.append(chunkSize - freeBytes);
|
||||
buf.append('/');
|
||||
buf.append(chunkSize);
|
||||
buf.append(')');
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
129
common/src/common/net/buffer/PoolChunkList.java
Normal file
129
common/src/common/net/buffer/PoolChunkList.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
final class PoolChunkList<T> {
|
||||
private final PoolArena<T> arena;
|
||||
private final PoolChunkList<T> nextList;
|
||||
PoolChunkList<T> prevList;
|
||||
|
||||
private final int minUsage;
|
||||
private final int maxUsage;
|
||||
|
||||
private PoolChunk<T> head;
|
||||
|
||||
// TODO: Test if adding padding helps under contention
|
||||
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
|
||||
|
||||
PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage) {
|
||||
this.arena = arena;
|
||||
this.nextList = nextList;
|
||||
this.minUsage = minUsage;
|
||||
this.maxUsage = maxUsage;
|
||||
}
|
||||
|
||||
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
|
||||
if (head == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PoolChunk<T> cur = head;;) {
|
||||
long handle = cur.allocate(normCapacity);
|
||||
if (handle < 0) {
|
||||
cur = cur.next;
|
||||
if (cur == null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
cur.initBuf(buf, handle, reqCapacity);
|
||||
if (cur.usage() >= maxUsage) {
|
||||
remove(cur);
|
||||
nextList.add(cur);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void free(PoolChunk<T> chunk, long handle) {
|
||||
chunk.free(handle);
|
||||
if (chunk.usage() < minUsage) {
|
||||
remove(chunk);
|
||||
if (prevList == null) {
|
||||
assert chunk.usage() == 0;
|
||||
arena.destroyChunk(chunk);
|
||||
} else {
|
||||
prevList.add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add(PoolChunk<T> chunk) {
|
||||
if (chunk.usage() >= maxUsage) {
|
||||
nextList.add(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
chunk.parent = this;
|
||||
if (head == null) {
|
||||
head = chunk;
|
||||
chunk.prev = null;
|
||||
chunk.next = null;
|
||||
} else {
|
||||
chunk.prev = null;
|
||||
chunk.next = head;
|
||||
head.prev = chunk;
|
||||
head = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(PoolChunk<T> cur) {
|
||||
if (cur == head) {
|
||||
head = cur.next;
|
||||
if (head != null) {
|
||||
head.prev = null;
|
||||
}
|
||||
} else {
|
||||
PoolChunk<T> next = cur.next;
|
||||
cur.prev.next = next;
|
||||
if (next != null) {
|
||||
next.prev = cur.prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (head == null) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (PoolChunk<T> cur = head;;) {
|
||||
buf.append(cur);
|
||||
cur = cur.next;
|
||||
if (cur == null) {
|
||||
break;
|
||||
}
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
213
common/src/common/net/buffer/PoolSubpage.java
Normal file
213
common/src/common/net/buffer/PoolSubpage.java
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
final class PoolSubpage<T> {
|
||||
|
||||
final PoolChunk<T> chunk;
|
||||
private final int memoryMapIdx;
|
||||
private final int runOffset;
|
||||
private final int pageSize;
|
||||
private final long[] bitmap;
|
||||
|
||||
PoolSubpage<T> prev;
|
||||
PoolSubpage<T> next;
|
||||
|
||||
boolean doNotDestroy;
|
||||
int elemSize;
|
||||
private int maxNumElems;
|
||||
private int bitmapLength;
|
||||
private int nextAvail;
|
||||
private int numAvail;
|
||||
|
||||
// TODO: Test if adding padding helps under contention
|
||||
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
|
||||
|
||||
/** Special constructor that creates a linked list head */
|
||||
PoolSubpage(int pageSize) {
|
||||
chunk = null;
|
||||
memoryMapIdx = -1;
|
||||
runOffset = -1;
|
||||
elemSize = -1;
|
||||
this.pageSize = pageSize;
|
||||
bitmap = null;
|
||||
}
|
||||
|
||||
PoolSubpage(PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
|
||||
this.chunk = chunk;
|
||||
this.memoryMapIdx = memoryMapIdx;
|
||||
this.runOffset = runOffset;
|
||||
this.pageSize = pageSize;
|
||||
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
|
||||
init(elemSize);
|
||||
}
|
||||
|
||||
void init(int elemSize) {
|
||||
doNotDestroy = true;
|
||||
this.elemSize = elemSize;
|
||||
if (elemSize != 0) {
|
||||
maxNumElems = numAvail = pageSize / elemSize;
|
||||
nextAvail = 0;
|
||||
bitmapLength = maxNumElems >>> 6;
|
||||
if ((maxNumElems & 63) != 0) {
|
||||
bitmapLength ++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bitmapLength; i ++) {
|
||||
bitmap[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
addToPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitmap index of the subpage allocation.
|
||||
*/
|
||||
long allocate() {
|
||||
if (elemSize == 0) {
|
||||
return toHandle(0);
|
||||
}
|
||||
|
||||
if (numAvail == 0 || !doNotDestroy) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final int bitmapIdx = getNextAvail();
|
||||
int q = bitmapIdx >>> 6;
|
||||
int r = bitmapIdx & 63;
|
||||
assert (bitmap[q] >>> r & 1) == 0;
|
||||
bitmap[q] |= 1L << r;
|
||||
|
||||
if (-- numAvail == 0) {
|
||||
removeFromPool();
|
||||
}
|
||||
|
||||
return toHandle(bitmapIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if this subpage is in use.
|
||||
* {@code false} if this subpage is not used by its chunk and thus it's OK to be released.
|
||||
*/
|
||||
boolean free(int bitmapIdx) {
|
||||
|
||||
if (elemSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int q = bitmapIdx >>> 6;
|
||||
int r = bitmapIdx & 63;
|
||||
assert (bitmap[q] >>> r & 1) != 0;
|
||||
bitmap[q] ^= 1L << r;
|
||||
|
||||
setNextAvail(bitmapIdx);
|
||||
|
||||
if (numAvail ++ == 0) {
|
||||
addToPool();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numAvail != maxNumElems) {
|
||||
return true;
|
||||
} else {
|
||||
// Subpage not in use (numAvail == maxNumElems)
|
||||
if (prev == next) {
|
||||
// Do not remove if this subpage is the only one left in the pool.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove this subpage from the pool if there are other subpages left in the pool.
|
||||
doNotDestroy = false;
|
||||
removeFromPool();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addToPool() {
|
||||
PoolSubpage<T> head = chunk.arena.findSubpagePoolHead(elemSize);
|
||||
assert prev == null && next == null;
|
||||
prev = head;
|
||||
next = head.next;
|
||||
next.prev = this;
|
||||
head.next = this;
|
||||
}
|
||||
|
||||
private void removeFromPool() {
|
||||
assert prev != null && next != null;
|
||||
prev.next = next;
|
||||
next.prev = prev;
|
||||
next = null;
|
||||
prev = null;
|
||||
}
|
||||
|
||||
private void setNextAvail(int bitmapIdx) {
|
||||
nextAvail = bitmapIdx;
|
||||
}
|
||||
|
||||
private int getNextAvail() {
|
||||
int nextAvail = this.nextAvail;
|
||||
if (nextAvail >= 0) {
|
||||
this.nextAvail = -1;
|
||||
return nextAvail;
|
||||
}
|
||||
return findNextAvail();
|
||||
}
|
||||
|
||||
private int findNextAvail() {
|
||||
final long[] bitmap = this.bitmap;
|
||||
final int bitmapLength = this.bitmapLength;
|
||||
for (int i = 0; i < bitmapLength; i ++) {
|
||||
long bits = bitmap[i];
|
||||
if (~bits != 0) {
|
||||
return findNextAvail0(i, bits);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int findNextAvail0(int i, long bits) {
|
||||
final int maxNumElems = this.maxNumElems;
|
||||
final int baseVal = i << 6;
|
||||
|
||||
for (int j = 0; j < 64; j ++) {
|
||||
if ((bits & 1) == 0) {
|
||||
int val = baseVal | j;
|
||||
if (val < maxNumElems) {
|
||||
return val;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bits >>>= 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private long toHandle(int bitmapIdx) {
|
||||
return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (!doNotDestroy) {
|
||||
return "(" + memoryMapIdx + ": not in use)";
|
||||
}
|
||||
|
||||
return String.valueOf('(') + memoryMapIdx + ": " + (maxNumElems - numAvail) + '/' + maxNumElems +
|
||||
", offset: " + runOffset + ", length: " + pageSize + ", elemSize: " + elemSize + ')';
|
||||
}
|
||||
}
|
485
common/src/common/net/buffer/PoolThreadCache.java
Normal file
485
common/src/common/net/buffer/PoolThreadCache.java
Normal file
|
@ -0,0 +1,485 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import common.net.util.ThreadDeathWatcher;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* Acts a Thread cache for allocations. This implementation is moduled after
|
||||
* <a href="http://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf">jemalloc</a> and the descripted
|
||||
* technics of <a href="https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/
|
||||
* 480222803919">Scalable memory allocation using jemalloc</a>.
|
||||
*/
|
||||
final class PoolThreadCache {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PoolThreadCache.class);
|
||||
|
||||
final PoolArena<byte[]> heapArena;
|
||||
final PoolArena<ByteBuffer> directArena;
|
||||
|
||||
// Hold the caches for the different size classes, which are tiny, small and normal.
|
||||
private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
|
||||
private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
|
||||
private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
|
||||
private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
|
||||
private final MemoryRegionCache<byte[]>[] normalHeapCaches;
|
||||
private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
|
||||
|
||||
// Used for bitshifting when calculate the index of normal caches later
|
||||
private final int numShiftsNormalDirect;
|
||||
private final int numShiftsNormalHeap;
|
||||
private final int freeSweepAllocationThreshold;
|
||||
|
||||
private int allocations;
|
||||
|
||||
private final Thread thread = Thread.currentThread();
|
||||
private final Runnable freeTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
free0();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Test if adding padding helps under contention
|
||||
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
|
||||
|
||||
PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
|
||||
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
|
||||
int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
|
||||
if (maxCachedBufferCapacity < 0) {
|
||||
throw new IllegalArgumentException("maxCachedBufferCapacity: "
|
||||
+ maxCachedBufferCapacity + " (expected: >= 0)");
|
||||
}
|
||||
if (freeSweepAllocationThreshold < 1) {
|
||||
throw new IllegalArgumentException("freeSweepAllocationThreshold: "
|
||||
+ maxCachedBufferCapacity + " (expected: > 0)");
|
||||
}
|
||||
this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;
|
||||
this.heapArena = heapArena;
|
||||
this.directArena = directArena;
|
||||
if (directArena != null) {
|
||||
tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools);
|
||||
smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.numSmallSubpagePools);
|
||||
|
||||
numShiftsNormalDirect = log2(directArena.pageSize);
|
||||
normalDirectCaches = createNormalCaches(
|
||||
normalCacheSize, maxCachedBufferCapacity, directArena);
|
||||
} else {
|
||||
// No directArea is configured so just null out all caches
|
||||
tinySubPageDirectCaches = null;
|
||||
smallSubPageDirectCaches = null;
|
||||
normalDirectCaches = null;
|
||||
numShiftsNormalDirect = -1;
|
||||
}
|
||||
if (heapArena != null) {
|
||||
// Create the caches for the heap allocations
|
||||
tinySubPageHeapCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools);
|
||||
smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.numSmallSubpagePools);
|
||||
|
||||
numShiftsNormalHeap = log2(heapArena.pageSize);
|
||||
normalHeapCaches = createNormalCaches(
|
||||
normalCacheSize, maxCachedBufferCapacity, heapArena);
|
||||
} else {
|
||||
// No heapArea is configured so just null out all caches
|
||||
tinySubPageHeapCaches = null;
|
||||
smallSubPageHeapCaches = null;
|
||||
normalHeapCaches = null;
|
||||
numShiftsNormalHeap = -1;
|
||||
}
|
||||
|
||||
// The thread-local cache will keep a list of pooled buffers which must be returned to
|
||||
// the pool when the thread is not alive anymore.
|
||||
ThreadDeathWatcher.watch(thread, freeTask);
|
||||
}
|
||||
|
||||
private static <T> SubPageMemoryRegionCache<T>[] createSubPageCaches(int cacheSize, int numCaches) {
|
||||
if (cacheSize > 0) {
|
||||
|
||||
SubPageMemoryRegionCache<T>[] cache = new SubPageMemoryRegionCache[numCaches];
|
||||
for (int i = 0; i < cache.length; i++) {
|
||||
// TODO: maybe use cacheSize / cache.length
|
||||
cache[i] = new SubPageMemoryRegionCache<T>(cacheSize);
|
||||
}
|
||||
return cache;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> NormalMemoryRegionCache<T>[] createNormalCaches(
|
||||
int cacheSize, int maxCachedBufferCapacity, PoolArena<T> area) {
|
||||
if (cacheSize > 0) {
|
||||
int max = Math.min(area.chunkSize, maxCachedBufferCapacity);
|
||||
int arraySize = Math.max(1, max / area.pageSize);
|
||||
|
||||
|
||||
NormalMemoryRegionCache<T>[] cache = new NormalMemoryRegionCache[arraySize];
|
||||
for (int i = 0; i < cache.length; i++) {
|
||||
cache[i] = new NormalMemoryRegionCache<T>(cacheSize);
|
||||
}
|
||||
return cache;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int log2(int val) {
|
||||
int res = 0;
|
||||
while (val > 1) {
|
||||
val >>= 1;
|
||||
res++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to allocate a tiny buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
|
||||
*/
|
||||
boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
|
||||
return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
|
||||
*/
|
||||
boolean allocateSmall(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
|
||||
return allocate(cacheForSmall(area, normCapacity), buf, reqCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
|
||||
*/
|
||||
boolean allocateNormal(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
|
||||
return allocate(cacheForNormal(area, normCapacity), buf, reqCapacity);
|
||||
}
|
||||
|
||||
|
||||
private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {
|
||||
if (cache == null) {
|
||||
// no cache found so just return false here
|
||||
return false;
|
||||
}
|
||||
boolean allocated = cache.allocate(buf, reqCapacity);
|
||||
if (++ allocations >= freeSweepAllocationThreshold) {
|
||||
allocations = 0;
|
||||
trim();
|
||||
}
|
||||
return allocated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link PoolChunk} and {@code handle} to the cache if there is enough room.
|
||||
* Returns {@code true} if it fit into the cache {@code false} otherwise.
|
||||
*/
|
||||
|
||||
boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity) {
|
||||
MemoryRegionCache<?> cache;
|
||||
if (area.isTinyOrSmall(normCapacity)) {
|
||||
if (PoolArena.isTiny(normCapacity)) {
|
||||
cache = cacheForTiny(area, normCapacity);
|
||||
} else {
|
||||
cache = cacheForSmall(area, normCapacity);
|
||||
}
|
||||
} else {
|
||||
cache = cacheForNormal(area, normCapacity);
|
||||
}
|
||||
if (cache == null) {
|
||||
return false;
|
||||
}
|
||||
return cache.add(chunk, handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the Thread that uses this cache is about to exist to release resources out of the cache
|
||||
*/
|
||||
void free() {
|
||||
ThreadDeathWatcher.unwatch(thread, freeTask);
|
||||
free0();
|
||||
}
|
||||
|
||||
private void free0() {
|
||||
int numFreed = free(tinySubPageDirectCaches) +
|
||||
free(smallSubPageDirectCaches) +
|
||||
free(normalDirectCaches) +
|
||||
free(tinySubPageHeapCaches) +
|
||||
free(smallSubPageHeapCaches) +
|
||||
free(normalHeapCaches);
|
||||
|
||||
if (numFreed > 0 && logger.isDebugEnabled()) {
|
||||
logger.debug("Freed {} thread-local buffer(s) from thread: {}", numFreed, thread.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static int free(MemoryRegionCache<?>[] caches) {
|
||||
if (caches == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int numFreed = 0;
|
||||
for (MemoryRegionCache<?> c: caches) {
|
||||
numFreed += free(c);
|
||||
}
|
||||
return numFreed;
|
||||
}
|
||||
|
||||
private static int free(MemoryRegionCache<?> cache) {
|
||||
if (cache == null) {
|
||||
return 0;
|
||||
}
|
||||
return cache.free();
|
||||
}
|
||||
|
||||
void trim() {
|
||||
trim(tinySubPageDirectCaches);
|
||||
trim(smallSubPageDirectCaches);
|
||||
trim(normalDirectCaches);
|
||||
trim(tinySubPageHeapCaches);
|
||||
trim(smallSubPageHeapCaches);
|
||||
trim(normalHeapCaches);
|
||||
}
|
||||
|
||||
private static void trim(MemoryRegionCache<?>[] caches) {
|
||||
if (caches == null) {
|
||||
return;
|
||||
}
|
||||
for (MemoryRegionCache<?> c: caches) {
|
||||
trim(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static void trim(MemoryRegionCache<?> cache) {
|
||||
if (cache == null) {
|
||||
return;
|
||||
}
|
||||
cache.trim();
|
||||
}
|
||||
|
||||
private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
|
||||
int idx = PoolArena.tinyIdx(normCapacity);
|
||||
if (area.isDirect()) {
|
||||
return cache(tinySubPageDirectCaches, idx);
|
||||
}
|
||||
return cache(tinySubPageHeapCaches, idx);
|
||||
}
|
||||
|
||||
private MemoryRegionCache<?> cacheForSmall(PoolArena<?> area, int normCapacity) {
|
||||
int idx = PoolArena.smallIdx(normCapacity);
|
||||
if (area.isDirect()) {
|
||||
return cache(smallSubPageDirectCaches, idx);
|
||||
}
|
||||
return cache(smallSubPageHeapCaches, idx);
|
||||
}
|
||||
|
||||
private MemoryRegionCache<?> cacheForNormal(PoolArena<?> area, int normCapacity) {
|
||||
if (area.isDirect()) {
|
||||
int idx = log2(normCapacity >> numShiftsNormalDirect);
|
||||
return cache(normalDirectCaches, idx);
|
||||
}
|
||||
int idx = log2(normCapacity >> numShiftsNormalHeap);
|
||||
return cache(normalHeapCaches, idx);
|
||||
}
|
||||
|
||||
private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
|
||||
if (cache == null || idx > cache.length - 1) {
|
||||
return null;
|
||||
}
|
||||
return cache[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache used for buffers which are backed by TINY or SMALL size.
|
||||
*/
|
||||
private static final class SubPageMemoryRegionCache<T> extends MemoryRegionCache<T> {
|
||||
SubPageMemoryRegionCache(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initBuf(
|
||||
PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) {
|
||||
chunk.initBufWithSubpage(buf, handle, reqCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache used for buffers which are backed by NORMAL size.
|
||||
*/
|
||||
private static final class NormalMemoryRegionCache<T> extends MemoryRegionCache<T> {
|
||||
NormalMemoryRegionCache(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initBuf(
|
||||
PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) {
|
||||
chunk.initBuf(buf, handle, reqCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache of {@link PoolChunk} and handles which can be used to allocate a buffer without locking at all.
|
||||
*/
|
||||
private abstract static class MemoryRegionCache<T> {
|
||||
private final Entry<T>[] entries;
|
||||
private final int maxUnusedCached;
|
||||
private int head;
|
||||
private int tail;
|
||||
private int maxEntriesInUse;
|
||||
private int entriesInUse;
|
||||
|
||||
|
||||
MemoryRegionCache(int size) {
|
||||
entries = new Entry[powerOfTwo(size)];
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
entries[i] = new Entry<T>();
|
||||
}
|
||||
maxUnusedCached = size / 2;
|
||||
}
|
||||
|
||||
private static int powerOfTwo(int res) {
|
||||
if (res <= 2) {
|
||||
return 2;
|
||||
}
|
||||
res--;
|
||||
res |= res >> 1;
|
||||
res |= res >> 2;
|
||||
res |= res >> 4;
|
||||
res |= res >> 8;
|
||||
res |= res >> 16;
|
||||
res++;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the {@link PooledByteBuf} using the provided chunk and handle with the capacity restrictions.
|
||||
*/
|
||||
protected abstract void initBuf(PoolChunk<T> chunk, long handle,
|
||||
PooledByteBuf<T> buf, int reqCapacity);
|
||||
|
||||
/**
|
||||
* Add to cache if not already full.
|
||||
*/
|
||||
public boolean add(PoolChunk<T> chunk, long handle) {
|
||||
Entry<T> entry = entries[tail];
|
||||
if (entry.chunk != null) {
|
||||
// cache is full
|
||||
return false;
|
||||
}
|
||||
entriesInUse --;
|
||||
|
||||
entry.chunk = chunk;
|
||||
entry.handle = handle;
|
||||
tail = nextIdx(tail);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate something out of the cache if possible and remove the entry from the cache.
|
||||
*/
|
||||
public boolean allocate(PooledByteBuf<T> buf, int reqCapacity) {
|
||||
Entry<T> entry = entries[head];
|
||||
if (entry.chunk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entriesInUse ++;
|
||||
if (maxEntriesInUse < entriesInUse) {
|
||||
maxEntriesInUse = entriesInUse;
|
||||
}
|
||||
initBuf(entry.chunk, entry.handle, buf, reqCapacity);
|
||||
// only null out the chunk as we only use the chunk to check if the buffer is full or not.
|
||||
entry.chunk = null;
|
||||
head = nextIdx(head);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out this cache and free up all previous cached {@link PoolChunk}s and {@code handle}s.
|
||||
*/
|
||||
public int free() {
|
||||
int numFreed = 0;
|
||||
entriesInUse = 0;
|
||||
maxEntriesInUse = 0;
|
||||
for (int i = head;; i = nextIdx(i)) {
|
||||
if (freeEntry(entries[i])) {
|
||||
numFreed++;
|
||||
} else {
|
||||
// all cleared
|
||||
return numFreed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free up cached {@link PoolChunk}s if not allocated frequently enough.
|
||||
*/
|
||||
private void trim() {
|
||||
int free = size() - maxEntriesInUse;
|
||||
entriesInUse = 0;
|
||||
maxEntriesInUse = 0;
|
||||
|
||||
if (free <= maxUnusedCached) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i = head;
|
||||
for (; free > 0; free--) {
|
||||
if (!freeEntry(entries[i])) {
|
||||
// all freed
|
||||
return;
|
||||
}
|
||||
i = nextIdx(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean freeEntry(Entry entry) {
|
||||
PoolChunk chunk = entry.chunk;
|
||||
if (chunk == null) {
|
||||
return false;
|
||||
}
|
||||
// need to synchronize on the area from which it was allocated before.
|
||||
synchronized (chunk.arena) {
|
||||
chunk.parent.free(chunk, entry.handle);
|
||||
}
|
||||
entry.chunk = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of cached entries.
|
||||
*/
|
||||
private int size() {
|
||||
return tail - head & entries.length - 1;
|
||||
}
|
||||
|
||||
private int nextIdx(int index) {
|
||||
// use bitwise operation as this is faster as using modulo.
|
||||
return index + 1 & entries.length - 1;
|
||||
}
|
||||
|
||||
private static final class Entry<T> {
|
||||
PoolChunk<T> chunk;
|
||||
long handle;
|
||||
}
|
||||
}
|
||||
}
|
160
common/src/common/net/buffer/PooledByteBuf.java
Normal file
160
common/src/common/net/buffer/PooledByteBuf.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import common.net.util.Recycler;
|
||||
|
||||
abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
|
||||
|
||||
private final Recycler.Handle recyclerHandle;
|
||||
|
||||
protected PoolChunk<T> chunk;
|
||||
protected long handle;
|
||||
protected T memory;
|
||||
protected int offset;
|
||||
protected int length;
|
||||
int maxLength;
|
||||
|
||||
private ByteBuffer tmpNioBuf;
|
||||
|
||||
protected PooledByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
this.recyclerHandle = recyclerHandle;
|
||||
}
|
||||
|
||||
void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength) {
|
||||
assert handle >= 0;
|
||||
assert chunk != null;
|
||||
|
||||
this.chunk = chunk;
|
||||
this.handle = handle;
|
||||
memory = chunk.memory;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.maxLength = maxLength;
|
||||
setIndex(0, 0);
|
||||
tmpNioBuf = null;
|
||||
}
|
||||
|
||||
void initUnpooled(PoolChunk<T> chunk, int length) {
|
||||
assert chunk != null;
|
||||
|
||||
this.chunk = chunk;
|
||||
handle = 0;
|
||||
memory = chunk.memory;
|
||||
offset = 0;
|
||||
this.length = maxLength = length;
|
||||
setIndex(0, 0);
|
||||
tmpNioBuf = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int capacity() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf capacity(int newCapacity) {
|
||||
ensureAccessible();
|
||||
|
||||
// If the request capacity does not require reallocation, just update the length of the memory.
|
||||
if (chunk.unpooled) {
|
||||
if (newCapacity == length) {
|
||||
return this;
|
||||
}
|
||||
} else {
|
||||
if (newCapacity > length) {
|
||||
if (newCapacity <= maxLength) {
|
||||
length = newCapacity;
|
||||
return this;
|
||||
}
|
||||
} else if (newCapacity < length) {
|
||||
if (newCapacity > maxLength >>> 1) {
|
||||
if (maxLength <= 512) {
|
||||
if (newCapacity > maxLength - 16) {
|
||||
length = newCapacity;
|
||||
setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
|
||||
return this;
|
||||
}
|
||||
} else { // > 512 (i.e. >= 1024)
|
||||
length = newCapacity;
|
||||
setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Reallocation required.
|
||||
chunk.arena.reallocate(this, newCapacity, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBufAllocator alloc() {
|
||||
return chunk.arena.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteOrder order() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final ByteBuffer internalNioBuffer() {
|
||||
ByteBuffer tmpNioBuf = this.tmpNioBuf;
|
||||
if (tmpNioBuf == null) {
|
||||
this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory);
|
||||
}
|
||||
return tmpNioBuf;
|
||||
}
|
||||
|
||||
protected abstract ByteBuffer newInternalNioBuffer(T memory);
|
||||
|
||||
@Override
|
||||
protected final void deallocate() {
|
||||
if (handle >= 0) {
|
||||
final long handle = this.handle;
|
||||
this.handle = -1;
|
||||
memory = null;
|
||||
chunk.arena.free(chunk, handle, maxLength);
|
||||
recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void recycle() {
|
||||
Recycler.Handle recyclerHandle = this.recyclerHandle;
|
||||
if (recyclerHandle != null) {
|
||||
((Recycler<Object>) recycler()).recycle(this, recyclerHandle);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Recycler<?> recycler();
|
||||
|
||||
protected final int idx(int index) {
|
||||
return offset + index;
|
||||
}
|
||||
}
|
334
common/src/common/net/buffer/PooledByteBufAllocator.java
Normal file
334
common/src/common/net/buffer/PooledByteBufAllocator.java
Normal file
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import common.net.util.concurrent.FastThreadLocal;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.SystemPropertyUtil;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
public class PooledByteBufAllocator extends AbstractByteBufAllocator {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PooledByteBufAllocator.class);
|
||||
private static final int DEFAULT_NUM_HEAP_ARENA;
|
||||
private static final int DEFAULT_NUM_DIRECT_ARENA;
|
||||
|
||||
private static final int DEFAULT_PAGE_SIZE;
|
||||
private static final int DEFAULT_MAX_ORDER; // 8192 << 11 = 16 MiB per chunk
|
||||
private static final int DEFAULT_TINY_CACHE_SIZE;
|
||||
private static final int DEFAULT_SMALL_CACHE_SIZE;
|
||||
private static final int DEFAULT_NORMAL_CACHE_SIZE;
|
||||
private static final int DEFAULT_MAX_CACHED_BUFFER_CAPACITY;
|
||||
private static final int DEFAULT_CACHE_TRIM_INTERVAL;
|
||||
|
||||
private static final int MIN_PAGE_SIZE = 4096;
|
||||
private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);
|
||||
|
||||
static {
|
||||
int defaultPageSize = SystemPropertyUtil.getInt("game.net.allocator.pageSize", 8192);
|
||||
Throwable pageSizeFallbackCause = null;
|
||||
try {
|
||||
validateAndCalculatePageShifts(defaultPageSize);
|
||||
} catch (Throwable t) {
|
||||
pageSizeFallbackCause = t;
|
||||
defaultPageSize = 8192;
|
||||
}
|
||||
DEFAULT_PAGE_SIZE = defaultPageSize;
|
||||
|
||||
int defaultMaxOrder = SystemPropertyUtil.getInt("game.net.allocator.maxOrder", 11);
|
||||
Throwable maxOrderFallbackCause = null;
|
||||
try {
|
||||
validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder);
|
||||
} catch (Throwable t) {
|
||||
maxOrderFallbackCause = t;
|
||||
defaultMaxOrder = 11;
|
||||
}
|
||||
DEFAULT_MAX_ORDER = defaultMaxOrder;
|
||||
|
||||
// Determine reasonable default for nHeapArena and nDirectArena.
|
||||
// Assuming each arena has 3 chunks, the pool should not consume more than 50% of max memory.
|
||||
final Runtime runtime = Runtime.getRuntime();
|
||||
final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
|
||||
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
|
||||
SystemPropertyUtil.getInt(
|
||||
"game.net.allocator.numHeapArenas",
|
||||
(int) Math.min(
|
||||
runtime.availableProcessors(),
|
||||
Runtime.getRuntime().maxMemory() / defaultChunkSize / 2 / 3)));
|
||||
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
|
||||
SystemPropertyUtil.getInt(
|
||||
"game.net.allocator.numDirectArenas",
|
||||
(int) Math.min(
|
||||
runtime.availableProcessors(),
|
||||
PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
|
||||
|
||||
// cache sizes
|
||||
DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.tinyCacheSize", 512);
|
||||
DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.smallCacheSize", 256);
|
||||
DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.normalCacheSize", 64);
|
||||
|
||||
// 32 kb is the default maximum capacity of the cached buffer. Similar to what is explained in
|
||||
// 'Scalable memory allocation using jemalloc'
|
||||
DEFAULT_MAX_CACHED_BUFFER_CAPACITY = SystemPropertyUtil.getInt(
|
||||
"game.net.allocator.maxCachedBufferCapacity", 32 * 1024);
|
||||
|
||||
// the number of threshold of allocations when cached entries will be freed up if not frequently used
|
||||
DEFAULT_CACHE_TRIM_INTERVAL = SystemPropertyUtil.getInt(
|
||||
"game.net.allocator.cacheTrimInterval", 8192);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("-Dgame.net.allocator.numHeapArenas: {}", DEFAULT_NUM_HEAP_ARENA);
|
||||
logger.debug("-Dgame.net.allocator.numDirectArenas: {}", DEFAULT_NUM_DIRECT_ARENA);
|
||||
if (pageSizeFallbackCause == null) {
|
||||
logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE);
|
||||
} else {
|
||||
logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE, pageSizeFallbackCause);
|
||||
}
|
||||
if (maxOrderFallbackCause == null) {
|
||||
logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER);
|
||||
} else {
|
||||
logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER, maxOrderFallbackCause);
|
||||
}
|
||||
logger.debug("-Dgame.net.allocator.chunkSize: {}", DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER);
|
||||
logger.debug("-Dgame.net.allocator.tinyCacheSize: {}", DEFAULT_TINY_CACHE_SIZE);
|
||||
logger.debug("-Dgame.net.allocator.smallCacheSize: {}", DEFAULT_SMALL_CACHE_SIZE);
|
||||
logger.debug("-Dgame.net.allocator.normalCacheSize: {}", DEFAULT_NORMAL_CACHE_SIZE);
|
||||
logger.debug("-Dgame.net.allocator.maxCachedBufferCapacity: {}", DEFAULT_MAX_CACHED_BUFFER_CAPACITY);
|
||||
logger.debug("-Dgame.net.allocator.cacheTrimInterval: {}", DEFAULT_CACHE_TRIM_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
public static final PooledByteBufAllocator DEFAULT =
|
||||
new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());
|
||||
|
||||
private final PoolArena<byte[]>[] heapArenas;
|
||||
private final PoolArena<ByteBuffer>[] directArenas;
|
||||
private final int tinyCacheSize;
|
||||
private final int smallCacheSize;
|
||||
private final int normalCacheSize;
|
||||
|
||||
final PoolThreadLocalCache threadCache;
|
||||
|
||||
public PooledByteBufAllocator() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public PooledByteBufAllocator(boolean preferDirect) {
|
||||
this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER);
|
||||
}
|
||||
|
||||
public PooledByteBufAllocator(int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
|
||||
this(false, nHeapArena, nDirectArena, pageSize, maxOrder);
|
||||
}
|
||||
|
||||
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
|
||||
this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
|
||||
DEFAULT_TINY_CACHE_SIZE, DEFAULT_SMALL_CACHE_SIZE, DEFAULT_NORMAL_CACHE_SIZE);
|
||||
}
|
||||
|
||||
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
|
||||
int tinyCacheSize, int smallCacheSize, int normalCacheSize) {
|
||||
super(preferDirect);
|
||||
threadCache = new PoolThreadLocalCache();
|
||||
this.tinyCacheSize = tinyCacheSize;
|
||||
this.smallCacheSize = smallCacheSize;
|
||||
this.normalCacheSize = normalCacheSize;
|
||||
final int chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
|
||||
|
||||
if (nHeapArena < 0) {
|
||||
throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)");
|
||||
}
|
||||
if (nDirectArena < 0) {
|
||||
throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)");
|
||||
}
|
||||
|
||||
int pageShifts = validateAndCalculatePageShifts(pageSize);
|
||||
|
||||
if (nHeapArena > 0) {
|
||||
heapArenas = newArenaArray(nHeapArena);
|
||||
for (int i = 0; i < heapArenas.length; i ++) {
|
||||
heapArenas[i] = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
} else {
|
||||
heapArenas = null;
|
||||
}
|
||||
|
||||
if (nDirectArena > 0) {
|
||||
directArenas = newArenaArray(nDirectArena);
|
||||
for (int i = 0; i < directArenas.length; i ++) {
|
||||
directArenas[i] = new PoolArena.DirectArena(this, pageSize, maxOrder, pageShifts, chunkSize);
|
||||
}
|
||||
} else {
|
||||
directArenas = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
||||
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
|
||||
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
|
||||
long cacheThreadAliveCheckInterval) {
|
||||
this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
|
||||
tinyCacheSize, smallCacheSize, normalCacheSize);
|
||||
}
|
||||
|
||||
|
||||
private static <T> PoolArena<T>[] newArenaArray(int size) {
|
||||
return new PoolArena[size];
|
||||
}
|
||||
|
||||
private static int validateAndCalculatePageShifts(int pageSize) {
|
||||
if (pageSize < MIN_PAGE_SIZE) {
|
||||
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + "+)");
|
||||
}
|
||||
|
||||
if ((pageSize & pageSize - 1) != 0) {
|
||||
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
|
||||
}
|
||||
|
||||
// Logarithm base 2. At this point we know that pageSize is a power of two.
|
||||
return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
|
||||
}
|
||||
|
||||
private static int validateAndCalculateChunkSize(int pageSize, int maxOrder) {
|
||||
if (maxOrder > 14) {
|
||||
throw new IllegalArgumentException("maxOrder: " + maxOrder + " (expected: 0-14)");
|
||||
}
|
||||
|
||||
// Ensure the resulting chunkSize does not overflow.
|
||||
int chunkSize = pageSize;
|
||||
for (int i = maxOrder; i > 0; i --) {
|
||||
if (chunkSize > MAX_CHUNK_SIZE / 2) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"pageSize (%d) << maxOrder (%d) must not exceed %d", pageSize, maxOrder, MAX_CHUNK_SIZE));
|
||||
}
|
||||
chunkSize <<= 1;
|
||||
}
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
|
||||
PoolThreadCache cache = threadCache.get();
|
||||
PoolArena<byte[]> heapArena = cache.heapArena;
|
||||
|
||||
ByteBuf buf;
|
||||
if (heapArena != null) {
|
||||
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
|
||||
} else {
|
||||
buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
return toLeakAwareBuffer(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
|
||||
PoolThreadCache cache = threadCache.get();
|
||||
PoolArena<ByteBuffer> directArena = cache.directArena;
|
||||
|
||||
ByteBuf buf;
|
||||
if (directArena != null) {
|
||||
buf = directArena.allocate(cache, initialCapacity, maxCapacity);
|
||||
} else {
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
|
||||
} else {
|
||||
buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
return toLeakAwareBuffer(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectBufferPooled() {
|
||||
return directArenas != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the calling {@link Thread} has a {@link ThreadLocal} cache for the allocated
|
||||
* buffers.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean hasThreadLocalCache() {
|
||||
return threadCache.isSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Free all cached buffers for the calling {@link Thread}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void freeThreadLocalCache() {
|
||||
threadCache.remove();
|
||||
}
|
||||
|
||||
final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache> {
|
||||
private final AtomicInteger index = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
protected PoolThreadCache initialValue() {
|
||||
final int idx = index.getAndIncrement();
|
||||
final PoolArena<byte[]> heapArena;
|
||||
final PoolArena<ByteBuffer> directArena;
|
||||
|
||||
if (heapArenas != null) {
|
||||
heapArena = heapArenas[Math.abs(idx % heapArenas.length)];
|
||||
} else {
|
||||
heapArena = null;
|
||||
}
|
||||
|
||||
if (directArenas != null) {
|
||||
directArena = directArenas[Math.abs(idx % directArenas.length)];
|
||||
} else {
|
||||
directArena = null;
|
||||
}
|
||||
|
||||
return new PoolThreadCache(
|
||||
heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
|
||||
DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoval(PoolThreadCache value) {
|
||||
value.free();
|
||||
}
|
||||
}
|
||||
|
||||
// Too noisy at the moment.
|
||||
//
|
||||
// public String toString() {
|
||||
// StringBuilder buf = new StringBuilder();
|
||||
// buf.append(heapArenas.length);
|
||||
// buf.append(" heap arena(s):");
|
||||
// buf.append(StringUtil.NEWLINE);
|
||||
// for (PoolArena<byte[]> a: heapArenas) {
|
||||
// buf.append(a);
|
||||
// }
|
||||
// buf.append(directArenas.length);
|
||||
// buf.append(" direct arena(s):");
|
||||
// buf.append(StringUtil.NEWLINE);
|
||||
// for (PoolArena<ByteBuffer> a: directArenas) {
|
||||
// buf.append(a);
|
||||
// }
|
||||
// return buf.toString();
|
||||
// }
|
||||
}
|
377
common/src/common/net/buffer/PooledDirectByteBuf.java
Normal file
377
common/src/common/net/buffer/PooledDirectByteBuf.java
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.Recycler;
|
||||
|
||||
final class PooledDirectByteBuf extends PooledByteBuf<ByteBuffer> {
|
||||
|
||||
private static final Recycler<PooledDirectByteBuf> RECYCLER = new Recycler<PooledDirectByteBuf>() {
|
||||
@Override
|
||||
protected PooledDirectByteBuf newObject(Handle handle) {
|
||||
return new PooledDirectByteBuf(handle, 0);
|
||||
}
|
||||
};
|
||||
|
||||
static PooledDirectByteBuf newInstance(int maxCapacity) {
|
||||
PooledDirectByteBuf buf = RECYCLER.get();
|
||||
buf.setRefCnt(1);
|
||||
buf.maxCapacity(maxCapacity);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private PooledDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
|
||||
super(recyclerHandle, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) {
|
||||
return memory.duplicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return memory.get(idx(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return memory.getShort(idx(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
index = idx(index);
|
||||
return (memory.get(index) & 0xff) << 16 | (memory.get(index + 1) & 0xff) << 8 | memory.get(index + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return memory.getInt(idx(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return memory.getLong(idx(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.capacity());
|
||||
if (dst.hasArray()) {
|
||||
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else if (dst.nioBufferCount() > 0) {
|
||||
for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
|
||||
int bbLen = bb.remaining();
|
||||
getBytes(index, bb);
|
||||
index += bbLen;
|
||||
}
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
getBytes(index, dst, dstIndex, length, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
|
||||
checkDstIndex(index, length, dstIndex, dst.length);
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
tmpBuf.get(dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, dstIndex, length, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
getBytes(index, dst, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, ByteBuffer dst, boolean internal) {
|
||||
checkIndex(index);
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
int length = dst.remaining();
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
getBytes(index, out, length, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException {
|
||||
checkIndex(index, length);
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] tmp = new byte[length];
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(idx(index));
|
||||
tmpBuf.get(tmp);
|
||||
out.write(tmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, out, length, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
checkIndex(index, length);
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
return out.write(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
memory.put(idx(index), (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
memory.putShort(idx(index), (short) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
index = idx(index);
|
||||
memory.put(index, (byte) (value >>> 16));
|
||||
memory.put(index + 1, (byte) (value >>> 8));
|
||||
memory.put(index + 2, (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
memory.putInt(idx(index), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
memory.putLong(idx(index), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.capacity());
|
||||
if (src.hasArray()) {
|
||||
setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
|
||||
} else if (src.nioBufferCount() > 0) {
|
||||
for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) {
|
||||
int bbLen = bb.remaining();
|
||||
setBytes(index, bb);
|
||||
index += bbLen;
|
||||
}
|
||||
} else {
|
||||
src.getBytes(srcIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.length);
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
tmpBuf.put(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
checkIndex(index, src.remaining());
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
if (src == tmpBuf) {
|
||||
src = src.duplicate();
|
||||
}
|
||||
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + src.remaining());
|
||||
tmpBuf.put(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
byte[] tmp = new byte[length];
|
||||
int readBytes = in.read(tmp);
|
||||
if (readBytes <= 0) {
|
||||
return readBytes;
|
||||
}
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(idx(index));
|
||||
tmpBuf.put(tmp, 0, readBytes);
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
try {
|
||||
return in.read(tmpBuf);
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
ByteBuf copy = alloc().directBuffer(length, maxCapacity());
|
||||
copy.writeBytes(this, index, length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Recycler<?> recycler() {
|
||||
return RECYCLER;
|
||||
}
|
||||
}
|
307
common/src/common/net/buffer/PooledHeapByteBuf.java
Normal file
307
common/src/common/net/buffer/PooledHeapByteBuf.java
Normal file
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file tothe License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.Recycler;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
final class PooledHeapByteBuf extends PooledByteBuf<byte[]> {
|
||||
|
||||
private static final Recycler<PooledHeapByteBuf> RECYCLER = new Recycler<PooledHeapByteBuf>() {
|
||||
@Override
|
||||
protected PooledHeapByteBuf newObject(Handle handle) {
|
||||
return new PooledHeapByteBuf(handle, 0);
|
||||
}
|
||||
};
|
||||
|
||||
static PooledHeapByteBuf newInstance(int maxCapacity) {
|
||||
PooledHeapByteBuf buf = RECYCLER.get();
|
||||
buf.setRefCnt(1);
|
||||
buf.maxCapacity(maxCapacity);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private PooledHeapByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
|
||||
super(recyclerHandle, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return memory[idx(index)];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
index = idx(index);
|
||||
return (short) (memory[index] << 8 | memory[index + 1] & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
index = idx(index);
|
||||
return (memory[index] & 0xff) << 16 |
|
||||
(memory[index + 1] & 0xff) << 8 |
|
||||
memory[index + 2] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
index = idx(index);
|
||||
return (memory[index] & 0xff) << 24 |
|
||||
(memory[index + 1] & 0xff) << 16 |
|
||||
(memory[index + 2] & 0xff) << 8 |
|
||||
memory[index + 3] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
index = idx(index);
|
||||
return ((long) memory[index] & 0xff) << 56 |
|
||||
((long) memory[index + 1] & 0xff) << 48 |
|
||||
((long) memory[index + 2] & 0xff) << 40 |
|
||||
((long) memory[index + 3] & 0xff) << 32 |
|
||||
((long) memory[index + 4] & 0xff) << 24 |
|
||||
((long) memory[index + 5] & 0xff) << 16 |
|
||||
((long) memory[index + 6] & 0xff) << 8 |
|
||||
(long) memory[index + 7] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.capacity());
|
||||
if (dst.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(memory, idx(index), dst.memoryAddress() + dstIndex, length);
|
||||
} else if (dst.hasArray()) {
|
||||
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else {
|
||||
dst.setBytes(dstIndex, memory, idx(index), length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.length);
|
||||
System.arraycopy(memory, idx(index), dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
checkIndex(index);
|
||||
dst.put(memory, idx(index), Math.min(capacity() - index, dst.remaining()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
out.write(memory, idx(index), length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = ByteBuffer.wrap(memory);
|
||||
}
|
||||
return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
memory[idx(index)] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
index = idx(index);
|
||||
memory[index] = (byte) (value >>> 8);
|
||||
memory[index + 1] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
index = idx(index);
|
||||
memory[index] = (byte) (value >>> 16);
|
||||
memory[index + 1] = (byte) (value >>> 8);
|
||||
memory[index + 2] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
index = idx(index);
|
||||
memory[index] = (byte) (value >>> 24);
|
||||
memory[index + 1] = (byte) (value >>> 16);
|
||||
memory[index + 2] = (byte) (value >>> 8);
|
||||
memory[index + 3] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
index = idx(index);
|
||||
memory[index] = (byte) (value >>> 56);
|
||||
memory[index + 1] = (byte) (value >>> 48);
|
||||
memory[index + 2] = (byte) (value >>> 40);
|
||||
memory[index + 3] = (byte) (value >>> 32);
|
||||
memory[index + 4] = (byte) (value >>> 24);
|
||||
memory[index + 5] = (byte) (value >>> 16);
|
||||
memory[index + 6] = (byte) (value >>> 8);
|
||||
memory[index + 7] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.capacity());
|
||||
if (src.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, memory, idx(index), length);
|
||||
} else if (src.hasArray()) {
|
||||
setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
|
||||
} else {
|
||||
src.getBytes(srcIndex, memory, idx(index), length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.length);
|
||||
System.arraycopy(src, srcIndex, memory, idx(index), length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
int length = src.remaining();
|
||||
checkIndex(index, length);
|
||||
src.get(memory, idx(index), length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
return in.read(memory, idx(index), length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
try {
|
||||
return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length));
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
ByteBuf copy = alloc().heapBuffer(length, maxCapacity());
|
||||
copy.writeBytes(memory, idx(index), length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
ByteBuffer buf = ByteBuffer.wrap(memory, index, length);
|
||||
return buf.slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer newInternalNioBuffer(byte[] memory) {
|
||||
return ByteBuffer.wrap(memory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Recycler<?> recycler() {
|
||||
return RECYCLER;
|
||||
}
|
||||
}
|
394
common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java
Normal file
394
common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java
Normal file
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.Recycler;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
final class PooledUnsafeDirectByteBuf extends PooledByteBuf<ByteBuffer> {
|
||||
|
||||
private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
|
||||
private static final Recycler<PooledUnsafeDirectByteBuf> RECYCLER = new Recycler<PooledUnsafeDirectByteBuf>() {
|
||||
@Override
|
||||
protected PooledUnsafeDirectByteBuf newObject(Handle handle) {
|
||||
return new PooledUnsafeDirectByteBuf(handle, 0);
|
||||
}
|
||||
};
|
||||
|
||||
static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
|
||||
PooledUnsafeDirectByteBuf buf = RECYCLER.get();
|
||||
buf.setRefCnt(1);
|
||||
buf.maxCapacity(maxCapacity);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private long memoryAddress;
|
||||
|
||||
private PooledUnsafeDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
|
||||
super(recyclerHandle, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(PoolChunk<ByteBuffer> chunk, long handle, int offset, int length, int maxLength) {
|
||||
super.init(chunk, handle, offset, length, maxLength);
|
||||
initMemoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
void initUnpooled(PoolChunk<ByteBuffer> chunk, int length) {
|
||||
super.initUnpooled(chunk, length);
|
||||
initMemoryAddress();
|
||||
}
|
||||
|
||||
private void initMemoryAddress() {
|
||||
memoryAddress = PlatformDependent.directBufferAddress(memory) + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) {
|
||||
return memory.duplicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return PlatformDependent.getByte(addr(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
short v = PlatformDependent.getShort(addr(index));
|
||||
return NATIVE_ORDER? v : Short.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
long addr = addr(index);
|
||||
return (PlatformDependent.getByte(addr) & 0xff) << 16 |
|
||||
(PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
|
||||
PlatformDependent.getByte(addr + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
int v = PlatformDependent.getInt(addr(index));
|
||||
return NATIVE_ORDER? v : Integer.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
long v = PlatformDependent.getLong(addr(index));
|
||||
return NATIVE_ORDER? v : Long.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
if (dst.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
|
||||
} else if (dst.hasArray()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.length - length) {
|
||||
throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
|
||||
}
|
||||
if (length != 0) {
|
||||
PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
getBytes(index, dst, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, ByteBuffer dst, boolean internal) {
|
||||
checkIndex(index);
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
int length = dst.remaining();
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
if (length != 0) {
|
||||
byte[] tmp = new byte[length];
|
||||
PlatformDependent.copyMemory(addr(index), tmp, 0, length);
|
||||
out.write(tmp);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
checkIndex(index, length);
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = memory.duplicate();
|
||||
}
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
return out.write(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length)
|
||||
throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
PlatformDependent.putByte(addr(index), (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
long addr = addr(index);
|
||||
PlatformDependent.putByte(addr, (byte) (value >>> 16));
|
||||
PlatformDependent.putByte(addr + 1, (byte) (value >>> 8));
|
||||
PlatformDependent.putByte(addr + 2, (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (src == null) {
|
||||
throw new NullPointerException("src");
|
||||
}
|
||||
if (srcIndex < 0 || srcIndex > src.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException("srcIndex: " + srcIndex);
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
if (src.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length);
|
||||
} else if (src.hasArray()) {
|
||||
PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length);
|
||||
} else {
|
||||
src.getBytes(srcIndex, this, index, length);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (length != 0) {
|
||||
PlatformDependent.copyMemory(src, srcIndex, addr(index), length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
checkIndex(index, src.remaining());
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
if (src == tmpBuf) {
|
||||
src = src.duplicate();
|
||||
}
|
||||
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + src.remaining());
|
||||
tmpBuf.put(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
byte[] tmp = new byte[length];
|
||||
int readBytes = in.read(tmp);
|
||||
if (readBytes > 0) {
|
||||
PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes);
|
||||
}
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
index = idx(index);
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
try {
|
||||
return in.read(tmpBuf);
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
ByteBuf copy = alloc().directBuffer(length, maxCapacity());
|
||||
if (length != 0) {
|
||||
if (copy.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
|
||||
copy.setIndex(0, length);
|
||||
} else {
|
||||
copy.writeBytes(this, index, length);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
index = idx(index);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return memoryAddress;
|
||||
}
|
||||
|
||||
private long addr(int index) {
|
||||
return memoryAddress + index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Recycler<?> recycler() {
|
||||
return RECYCLER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SwappedByteBuf newSwappedByteBuf() {
|
||||
return new UnsafeDirectSwappedByteBuf(this);
|
||||
}
|
||||
}
|
317
common/src/common/net/buffer/ReadOnlyByteBuf.java
Normal file
317
common/src/common/net/buffer/ReadOnlyByteBuf.java
Normal file
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
/**
|
||||
* A derived buffer which forbids any write requests to its parent. It is
|
||||
* recommended to use {@link Unpooled#unmodifiableBuffer(ByteBuf)}
|
||||
* instead of calling the constructor explicitly.
|
||||
*/
|
||||
public class ReadOnlyByteBuf extends AbstractDerivedByteBuf {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
|
||||
public ReadOnlyByteBuf(ByteBuf buffer) {
|
||||
super(buffer.maxCapacity());
|
||||
|
||||
if (buffer instanceof ReadOnlyByteBuf || buffer instanceof DuplicatedByteBuf) {
|
||||
this.buffer = buffer.unwrap();
|
||||
} else {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
setIndex(buffer.readerIndex(), buffer.writerIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(int numBytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return buffer.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return buffer.order();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buffer.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardReadBytes() {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length)
|
||||
throws IOException {
|
||||
return buffer.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length)
|
||||
throws IOException {
|
||||
buffer.getBytes(index, out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
buffer.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
buffer.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
buffer.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return new ReadOnlyByteBuf(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
return buffer.copy(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return Unpooled.unmodifiableBuffer(buffer.slice(index, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return _getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return buffer.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
return _getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return buffer.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
return _getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return buffer.getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return _getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return buffer.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
return _getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return buffer.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return buffer.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
return buffer.nioBuffer(index, length).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return buffer.nioBuffers(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
return buffer.forEachByte(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
return buffer.forEachByteDesc(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return buffer.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
}
|
335
common/src/common/net/buffer/ReadOnlyByteBufferBuf.java
Normal file
335
common/src/common/net/buffer/ReadOnlyByteBufferBuf.java
Normal file
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Read-only ByteBuf which wraps a read-only ByteBuffer.
|
||||
*/
|
||||
class ReadOnlyByteBufferBuf extends AbstractReferenceCountedByteBuf {
|
||||
|
||||
protected final ByteBuffer buffer;
|
||||
private final ByteBufAllocator allocator;
|
||||
private ByteBuffer tmpNioBuf;
|
||||
|
||||
ReadOnlyByteBufferBuf(ByteBufAllocator allocator, ByteBuffer buffer) {
|
||||
super(buffer.remaining());
|
||||
if (!buffer.isReadOnly()) {
|
||||
throw new IllegalArgumentException("must be a readonly buffer: " + StringUtil.simpleClassName(buffer));
|
||||
}
|
||||
|
||||
this.allocator = allocator;
|
||||
this.buffer = buffer.slice().order(ByteOrder.BIG_ENDIAN);
|
||||
writerIndex(this.buffer.limit());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() { }
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
ensureAccessible();
|
||||
return _getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return buffer.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
ensureAccessible();
|
||||
return _getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return buffer.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
ensureAccessible();
|
||||
return _getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
ensureAccessible();
|
||||
return _getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return buffer.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
ensureAccessible();
|
||||
return _getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return buffer.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.capacity());
|
||||
if (dst.hasArray()) {
|
||||
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else if (dst.nioBufferCount() > 0) {
|
||||
for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
|
||||
int bbLen = bb.remaining();
|
||||
getBytes(index, bb);
|
||||
index += bbLen;
|
||||
}
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.length);
|
||||
|
||||
if (dstIndex < 0 || dstIndex > dst.length - length) {
|
||||
throw new IndexOutOfBoundsException(String.format(
|
||||
"dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
tmpBuf.get(dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
checkIndex(index);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return maxCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return allocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buffer.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length == 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (buffer.hasArray()) {
|
||||
out.write(buffer.array(), index + buffer.arrayOffset(), length);
|
||||
} else {
|
||||
byte[] tmp = new byte[length];
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index);
|
||||
tmpBuf.get(tmp);
|
||||
out.write(tmp);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
return out.write(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
throw new ReadOnlyBufferException();
|
||||
}
|
||||
|
||||
protected final ByteBuffer internalNioBuffer() {
|
||||
ByteBuffer tmpNioBuf = this.tmpNioBuf;
|
||||
if (tmpNioBuf == null) {
|
||||
this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
|
||||
}
|
||||
return tmpNioBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
ensureAccessible();
|
||||
ByteBuffer src;
|
||||
try {
|
||||
src = (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length));
|
||||
}
|
||||
|
||||
ByteBuffer dst = ByteBuffer.allocateDirect(length);
|
||||
dst.put(src);
|
||||
dst.order(order());
|
||||
dst.clear();
|
||||
return new UnpooledDirectByteBuf(alloc(), dst, maxCapacity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
return (ByteBuffer) buffer.duplicate().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
ensureAccessible();
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return buffer.hasArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return buffer.arrayOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
138
common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java
Normal file
138
common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Read-only ByteBuf which wraps a read-only direct ByteBuffer and use unsafe for best performance.
|
||||
*/
|
||||
final class ReadOnlyUnsafeDirectByteBuf extends ReadOnlyByteBufferBuf {
|
||||
private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
private final long memoryAddress;
|
||||
|
||||
ReadOnlyUnsafeDirectByteBuf(ByteBufAllocator allocator, ByteBuffer buffer) {
|
||||
super(allocator, buffer);
|
||||
memoryAddress = PlatformDependent.directBufferAddress(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return PlatformDependent.getByte(addr(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
short v = PlatformDependent.getShort(addr(index));
|
||||
return NATIVE_ORDER? v : Short.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
long addr = addr(index);
|
||||
return (PlatformDependent.getByte(addr) & 0xff) << 16 |
|
||||
(PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
|
||||
PlatformDependent.getByte(addr + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
int v = PlatformDependent.getInt(addr(index));
|
||||
return NATIVE_ORDER? v : Integer.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
long v = PlatformDependent.getLong(addr(index));
|
||||
return NATIVE_ORDER? v : Long.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
|
||||
}
|
||||
|
||||
if (dst.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
|
||||
} else if (dst.hasArray()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.length - length) {
|
||||
throw new IndexOutOfBoundsException(String.format(
|
||||
"dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
checkIndex(index);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
ByteBuf copy = alloc().directBuffer(length, maxCapacity());
|
||||
if (length != 0) {
|
||||
if (copy.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
|
||||
copy.setIndex(0, length);
|
||||
} else {
|
||||
copy.writeBytes(this, index, length);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
private long addr(int index) {
|
||||
return memoryAddress + index;
|
||||
}
|
||||
}
|
79
common/src/common/net/buffer/SimpleLeakAwareByteBuf.java
Normal file
79
common/src/common/net/buffer/SimpleLeakAwareByteBuf.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import common.net.util.ResourceLeak;
|
||||
|
||||
final class SimpleLeakAwareByteBuf extends WrappedByteBuf {
|
||||
|
||||
private final ResourceLeak leak;
|
||||
|
||||
SimpleLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) {
|
||||
super(buf);
|
||||
this.leak = leak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
boolean deallocated = super.release();
|
||||
if (deallocated) {
|
||||
leak.close();
|
||||
}
|
||||
return deallocated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
boolean deallocated = super.release(decrement);
|
||||
if (deallocated) {
|
||||
leak.close();
|
||||
}
|
||||
return deallocated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
leak.record();
|
||||
if (order() == endianness) {
|
||||
return this;
|
||||
} else {
|
||||
return new SimpleLeakAwareByteBuf(super.order(endianness), leak);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
return new SimpleLeakAwareByteBuf(super.slice(), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return new SimpleLeakAwareByteBuf(super.slice(index, length), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return new SimpleLeakAwareByteBuf(super.duplicate(), leak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
return new SimpleLeakAwareByteBuf(super.readSlice(length), leak);
|
||||
}
|
||||
}
|
296
common/src/common/net/buffer/SlicedByteBuf.java
Normal file
296
common/src/common/net/buffer/SlicedByteBuf.java
Normal file
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
|
||||
/**
|
||||
* A derived buffer which exposes its parent's sub-region only. It is
|
||||
* recommended to use {@link ByteBuf#slice()} and
|
||||
* {@link ByteBuf#slice(int, int)} instead of calling the constructor
|
||||
* explicitly.
|
||||
*/
|
||||
public class SlicedByteBuf extends AbstractDerivedByteBuf {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final int adjustment;
|
||||
private final int length;
|
||||
|
||||
public SlicedByteBuf(ByteBuf buffer, int index, int length) {
|
||||
super(length);
|
||||
if (index < 0 || index > buffer.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException(buffer + ".slice(" + index + ", " + length + ')');
|
||||
}
|
||||
|
||||
if (buffer instanceof SlicedByteBuf) {
|
||||
this.buffer = ((SlicedByteBuf) buffer).buffer;
|
||||
adjustment = ((SlicedByteBuf) buffer).adjustment + index;
|
||||
} else if (buffer instanceof DuplicatedByteBuf) {
|
||||
this.buffer = buffer.unwrap();
|
||||
adjustment = index;
|
||||
} else {
|
||||
this.buffer = buffer;
|
||||
adjustment = index;
|
||||
}
|
||||
this.length = length;
|
||||
|
||||
writerIndex(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return buffer.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return buffer.order();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buffer.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
throw new UnsupportedOperationException("sliced buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return buffer.hasArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return buffer.arrayOffset() + adjustment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return buffer.hasMemoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return buffer.memoryAddress() + adjustment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return buffer.getByte(index + adjustment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return buffer.getShort(index + adjustment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return buffer.getUnsignedMedium(index + adjustment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return buffer.getInt(index + adjustment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return buffer.getLong(index + adjustment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
ByteBuf duplicate = buffer.slice(adjustment, length);
|
||||
duplicate.setIndex(readerIndex(), writerIndex());
|
||||
return duplicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return buffer.copy(index + adjustment, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
if (length == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
return buffer.slice(index + adjustment, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
buffer.getBytes(index + adjustment, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
buffer.getBytes(index + adjustment, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
checkIndex(index, dst.remaining());
|
||||
buffer.getBytes(index + adjustment, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
buffer.setByte(index + adjustment, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
buffer.setShort(index + adjustment, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
buffer.setMedium(index + adjustment, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
buffer.setInt(index + adjustment, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
buffer.setLong(index + adjustment, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
buffer.setBytes(index + adjustment, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
buffer.setBytes(index + adjustment, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
checkIndex(index, src.remaining());
|
||||
buffer.setBytes(index + adjustment, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
buffer.getBytes(index + adjustment, out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
return buffer.getBytes(index + adjustment, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
return buffer.setBytes(index + adjustment, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
return buffer.setBytes(index + adjustment, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return buffer.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return buffer.nioBuffer(index + adjustment, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return buffer.nioBuffers(index + adjustment, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
int ret = buffer.forEachByte(index + adjustment, length, processor);
|
||||
if (ret >= adjustment) {
|
||||
return ret - adjustment;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
int ret = buffer.forEachByteDesc(index + adjustment, length, processor);
|
||||
if (ret >= adjustment) {
|
||||
return ret - adjustment;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
852
common/src/common/net/buffer/SwappedByteBuf.java
Normal file
852
common/src/common/net/buffer/SwappedByteBuf.java
Normal file
|
@ -0,0 +1,852 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Wrapper which swap the {@link ByteOrder} of a {@link ByteBuf}.
|
||||
*/
|
||||
public class SwappedByteBuf extends ByteBuf {
|
||||
|
||||
private final ByteBuf buf;
|
||||
private final ByteOrder order;
|
||||
|
||||
public SwappedByteBuf(ByteBuf buf) {
|
||||
if (buf == null) {
|
||||
throw new NullPointerException("buf");
|
||||
}
|
||||
this.buf = buf;
|
||||
if (buf.order() == ByteOrder.BIG_ENDIAN) {
|
||||
order = ByteOrder.LITTLE_ENDIAN;
|
||||
} else {
|
||||
order = ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
if (endianness == null) {
|
||||
throw new NullPointerException("endianness");
|
||||
}
|
||||
if (endianness == order) {
|
||||
return this;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return buf.unwrap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return buf.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return buf.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
buf.capacity(newCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxCapacity() {
|
||||
return buf.maxCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buf.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readerIndex() {
|
||||
return buf.readerIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readerIndex(int readerIndex) {
|
||||
buf.readerIndex(readerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writerIndex() {
|
||||
return buf.writerIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writerIndex(int writerIndex) {
|
||||
buf.writerIndex(writerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setIndex(int readerIndex, int writerIndex) {
|
||||
buf.setIndex(readerIndex, writerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableBytes() {
|
||||
return buf.readableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writableBytes() {
|
||||
return buf.writableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxWritableBytes() {
|
||||
return buf.maxWritableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
return buf.isReadable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(int size) {
|
||||
return buf.isReadable(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return buf.isWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(int size) {
|
||||
return buf.isWritable(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf clear() {
|
||||
buf.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markReaderIndex() {
|
||||
buf.markReaderIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetReaderIndex() {
|
||||
buf.resetReaderIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markWriterIndex() {
|
||||
buf.markWriterIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetWriterIndex() {
|
||||
buf.resetWriterIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardReadBytes() {
|
||||
buf.discardReadBytes();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardSomeReadBytes() {
|
||||
buf.discardSomeReadBytes();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ensureWritable(int writableBytes) {
|
||||
buf.ensureWritable(writableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ensureWritable(int minWritableBytes, boolean force) {
|
||||
return buf.ensureWritable(minWritableBytes, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int index) {
|
||||
return buf.getBoolean(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return buf.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getUnsignedByte(int index) {
|
||||
return buf.getUnsignedByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
return ByteBufUtil.swapShort(buf.getShort(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int index) {
|
||||
return getShort(index) & 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMedium(int index) {
|
||||
return ByteBufUtil.swapMedium(buf.getMedium(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
return getMedium(index) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return ByteBufUtil.swapInt(buf.getInt(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int index) {
|
||||
return getInt(index) & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
return ByteBufUtil.swapLong(buf.getLong(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
return (char) getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
return Float.intBitsToFloat(getInt(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
return Double.longBitsToDouble(getLong(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int length) {
|
||||
buf.getBytes(index, dst, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
buf.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
buf.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
buf.getBytes(index, out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return buf.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBoolean(int index, boolean value) {
|
||||
buf.setBoolean(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
buf.setByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
buf.setShort(index, ByteBufUtil.swapShort((short) value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
buf.setMedium(index, ByteBufUtil.swapMedium(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
buf.setInt(index, ByteBufUtil.swapInt(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
buf.setLong(index, ByteBufUtil.swapLong(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int index, int value) {
|
||||
setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setFloat(int index, float value) {
|
||||
setInt(index, Float.floatToRawIntBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setDouble(int index, double value) {
|
||||
setLong(index, Double.doubleToRawLongBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int length) {
|
||||
buf.setBytes(index, src, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
buf.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
buf.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
return buf.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
return buf.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setZero(int index, int length) {
|
||||
buf.setZero(index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
return buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
return buf.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readUnsignedByte() {
|
||||
return buf.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
return ByteBufUtil.swapShort(buf.readShort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
return readShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMedium() {
|
||||
return ByteBufUtil.swapMedium(buf.readMedium());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMedium() {
|
||||
return readMedium() & 0xFFFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
return ByteBufUtil.swapInt(buf.readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedInt() {
|
||||
return readInt() & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
return ByteBufUtil.swapLong(buf.readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
return (char) readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(int length) {
|
||||
return buf.readBytes(length).order(order());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
return buf.readSlice(length).order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int length) {
|
||||
buf.readBytes(dst, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
|
||||
buf.readBytes(dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
buf.readBytes(dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) throws IOException {
|
||||
buf.readBytes(out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
return buf.readBytes(out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf skipBytes(int length) {
|
||||
buf.skipBytes(length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBoolean(boolean value) {
|
||||
buf.writeBoolean(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeByte(int value) {
|
||||
buf.writeByte(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShort(int value) {
|
||||
buf.writeShort(ByteBufUtil.swapShort((short) value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMedium(int value) {
|
||||
buf.writeMedium(ByteBufUtil.swapMedium(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int value) {
|
||||
buf.writeInt(ByteBufUtil.swapInt(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long value) {
|
||||
buf.writeLong(ByteBufUtil.swapLong(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int value) {
|
||||
writeShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeFloat(float value) {
|
||||
writeInt(Float.floatToRawIntBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeDouble(double value) {
|
||||
writeLong(Double.doubleToRawLongBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int length) {
|
||||
buf.writeBytes(src, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
|
||||
buf.writeBytes(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
|
||||
buf.writeBytes(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuffer src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(InputStream in, int length) throws IOException {
|
||||
return buf.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
|
||||
return buf.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeZero(int length) {
|
||||
buf.writeZero(length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int fromIndex, int toIndex, byte value) {
|
||||
return buf.indexOf(fromIndex, toIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(byte value) {
|
||||
return buf.bytesBefore(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int length, byte value) {
|
||||
return buf.bytesBefore(length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int index, int length, byte value) {
|
||||
return buf.bytesBefore(index, length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(ByteBufProcessor processor) {
|
||||
return buf.forEachByte(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
return buf.forEachByte(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(ByteBufProcessor processor) {
|
||||
return buf.forEachByteDesc(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
return buf.forEachByteDesc(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy() {
|
||||
return buf.copy().order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
return buf.copy(index, length).order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
return buf.slice().order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return buf.slice(index, length).order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return buf.duplicate().order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return buf.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer() {
|
||||
return buf.nioBuffer().order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
return buf.nioBuffer(index, length).order(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers() {
|
||||
ByteBuffer[] nioBuffers = buf.nioBuffers();
|
||||
for (int i = 0; i < nioBuffers.length; i++) {
|
||||
nioBuffers[i] = nioBuffers[i].order(order);
|
||||
}
|
||||
return nioBuffers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
ByteBuffer[] nioBuffers = buf.nioBuffers(index, length);
|
||||
for (int i = 0; i < nioBuffers.length; i++) {
|
||||
nioBuffers[i] = nioBuffers[i].order(order);
|
||||
}
|
||||
return nioBuffers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return buf.hasArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return buf.arrayOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return buf.hasMemoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return buf.memoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
return buf.toString(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
return buf.toString(index, length, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return buf.refCnt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
buf.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
buf.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return buf.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return buf.release(decrement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof ByteBuf) {
|
||||
return ByteBufUtil.equals(this, (ByteBuf) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ByteBuf buffer) {
|
||||
return ByteBufUtil.compare(this, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Swapped(" + buf.toString() + ')';
|
||||
}
|
||||
}
|
861
common/src/common/net/buffer/Unpooled.java
Normal file
861
common/src/common/net/buffer/Unpooled.java
Normal file
|
@ -0,0 +1,861 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteBuf} by allocating new space or by wrapping
|
||||
* or copying existing byte arrays, byte buffers and a string.
|
||||
*
|
||||
* <h3>Use static import</h3>
|
||||
* This classes is intended to be used with Java 5 static import statement:
|
||||
*
|
||||
* <pre>
|
||||
* import static game.net.buffer.{@link Unpooled}.*;
|
||||
*
|
||||
* {@link ByteBuf} heapBuffer = buffer(128);
|
||||
* {@link ByteBuf} directBuffer = directBuffer(256);
|
||||
* {@link ByteBuf} wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
|
||||
* {@link ByteBuf} copiedBuffe r = copiedBuffer({@link ByteBuffer}.allocate(128));
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Allocating a new buffer</h3>
|
||||
*
|
||||
* Three buffer types are provided out of the box.
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #buffer(int)} allocates a new fixed-capacity heap buffer.</li>
|
||||
* <li>{@link #directBuffer(int)} allocates a new fixed-capacity direct buffer.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Creating a wrapped buffer</h3>
|
||||
*
|
||||
* Wrapped buffer is a buffer which is a view of one or more existing
|
||||
* byte arrays and byte buffers. Any changes in the content of the original
|
||||
* array or buffer will be visible in the wrapped buffer. Various wrapper
|
||||
* methods are provided and their name is all {@code wrappedBuffer()}.
|
||||
* You might want to take a look at the methods that accept varargs closely if
|
||||
* you want to create a buffer which is composed of more than one array to
|
||||
* reduce the number of memory copy.
|
||||
*
|
||||
* <h3>Creating a copied buffer</h3>
|
||||
*
|
||||
* Copied buffer is a deep copy of one or more existing byte arrays, byte
|
||||
* buffers or a string. Unlike a wrapped buffer, there's no shared data
|
||||
* between the original data and the copied buffer. Various copy methods are
|
||||
* provided and their name is all {@code copiedBuffer()}. It is also convenient
|
||||
* to use this operation to merge multiple buffers into one buffer.
|
||||
*
|
||||
* <h3>Miscellaneous utility methods</h3>
|
||||
*
|
||||
* This class also provides various utility methods to help implementation
|
||||
* of a new buffer type, generation of hex dump and swapping an integer's
|
||||
* byte order.
|
||||
*/
|
||||
public final class Unpooled {
|
||||
|
||||
private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
|
||||
|
||||
/**
|
||||
* Big endian byte order.
|
||||
*/
|
||||
public static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
|
||||
|
||||
/**
|
||||
* Little endian byte order.
|
||||
*/
|
||||
public static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
|
||||
|
||||
/**
|
||||
* A buffer whose capacity is {@code 0}.
|
||||
*/
|
||||
public static final ByteBuf EMPTY_BUFFER = ALLOC.buffer(0, 0);
|
||||
|
||||
/**
|
||||
* Creates a new big-endian Java heap buffer with reasonably small initial capacity, which
|
||||
* expands its capacity boundlessly on demand.
|
||||
*/
|
||||
public static ByteBuf buffer() {
|
||||
return ALLOC.heapBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian direct buffer with reasonably small initial capacity, which
|
||||
* expands its capacity boundlessly on demand.
|
||||
*/
|
||||
public static ByteBuf directBuffer() {
|
||||
return ALLOC.directBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian Java heap buffer with the specified {@code capacity}, which
|
||||
* expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and
|
||||
* {@code writerIndex} are {@code 0}.
|
||||
*/
|
||||
public static ByteBuf buffer(int initialCapacity) {
|
||||
return ALLOC.heapBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian direct buffer with the specified {@code capacity}, which
|
||||
* expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and
|
||||
* {@code writerIndex} are {@code 0}.
|
||||
*/
|
||||
public static ByteBuf directBuffer(int initialCapacity) {
|
||||
return ALLOC.directBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian Java heap buffer with the specified
|
||||
* {@code initialCapacity}, that may grow up to {@code maxCapacity}
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0}.
|
||||
*/
|
||||
public static ByteBuf buffer(int initialCapacity, int maxCapacity) {
|
||||
return ALLOC.heapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian direct buffer with the specified
|
||||
* {@code initialCapacity}, that may grow up to {@code maxCapacity}.
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0}.
|
||||
*/
|
||||
public static ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
|
||||
return ALLOC.directBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer which wraps the specified {@code array}.
|
||||
* A modification on the specified array's content will be visible to the
|
||||
* returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(byte[] array) {
|
||||
if (array.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
return new UnpooledHeapByteBuf(ALLOC, array, array.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer which wraps the sub-region of the
|
||||
* specified {@code array}. A modification on the specified array's
|
||||
* content will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(byte[] array, int offset, int length) {
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
if (offset == 0 && length == array.length) {
|
||||
return wrappedBuffer(array);
|
||||
}
|
||||
|
||||
return wrappedBuffer(array).slice(offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer which wraps the specified NIO buffer's current
|
||||
* slice. A modification on the specified buffer's content will be
|
||||
* visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(ByteBuffer buffer) {
|
||||
if (!buffer.hasRemaining()) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
if (buffer.hasArray()) {
|
||||
return wrappedBuffer(
|
||||
buffer.array(),
|
||||
buffer.arrayOffset() + buffer.position(),
|
||||
buffer.remaining()).order(buffer.order());
|
||||
} else if (PlatformDependent.hasUnsafe()) {
|
||||
if (buffer.isReadOnly()) {
|
||||
if (buffer.isDirect()) {
|
||||
return new ReadOnlyUnsafeDirectByteBuf(ALLOC, buffer);
|
||||
} else {
|
||||
return new ReadOnlyByteBufferBuf(ALLOC, buffer);
|
||||
}
|
||||
} else {
|
||||
return new UnpooledUnsafeDirectByteBuf(ALLOC, buffer, buffer.remaining());
|
||||
}
|
||||
} else {
|
||||
if (buffer.isReadOnly()) {
|
||||
return new ReadOnlyByteBufferBuf(ALLOC, buffer);
|
||||
} else {
|
||||
return new UnpooledDirectByteBuf(ALLOC, buffer, buffer.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer which wraps the specified buffer's readable bytes.
|
||||
* A modification on the specified buffer's content will be visible to the
|
||||
* returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(ByteBuf buffer) {
|
||||
if (buffer.isReadable()) {
|
||||
return buffer.slice();
|
||||
} else {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the specified
|
||||
* arrays without copying them. A modification on the specified arrays'
|
||||
* content will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(byte[]... arrays) {
|
||||
return wrappedBuffer(16, arrays);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the readable bytes of the
|
||||
* specified buffers without copying them. A modification on the content
|
||||
* of the specified buffers will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(ByteBuf... buffers) {
|
||||
return wrappedBuffer(16, buffers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the slices of the specified
|
||||
* NIO buffers without copying them. A modification on the content of the
|
||||
* specified buffers will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {
|
||||
return wrappedBuffer(16, buffers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the specified
|
||||
* arrays without copying them. A modification on the specified arrays'
|
||||
* content will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) {
|
||||
switch (arrays.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
if (arrays[0].length != 0) {
|
||||
return wrappedBuffer(arrays[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Get the list of the component, while guessing the byte order.
|
||||
final List<ByteBuf> components = new ArrayList<ByteBuf>(arrays.length);
|
||||
for (byte[] a: arrays) {
|
||||
if (a == null) {
|
||||
break;
|
||||
}
|
||||
if (a.length > 0) {
|
||||
components.add(wrappedBuffer(a));
|
||||
}
|
||||
}
|
||||
|
||||
if (!components.isEmpty()) {
|
||||
return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the readable bytes of the
|
||||
* specified buffers without copying them. A modification on the content
|
||||
* of the specified buffers will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) {
|
||||
switch (buffers.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
if (buffers[0].isReadable()) {
|
||||
return wrappedBuffer(buffers[0].order(BIG_ENDIAN));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (ByteBuf b: buffers) {
|
||||
if (b.isReadable()) {
|
||||
return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian composite buffer which wraps the slices of the specified
|
||||
* NIO buffers without copying them. A modification on the content of the
|
||||
* specified buffers will be visible to the returned buffer.
|
||||
*/
|
||||
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers) {
|
||||
switch (buffers.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
if (buffers[0].hasRemaining()) {
|
||||
return wrappedBuffer(buffers[0].order(BIG_ENDIAN));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Get the list of the component, while guessing the byte order.
|
||||
final List<ByteBuf> components = new ArrayList<ByteBuf>(buffers.length);
|
||||
for (ByteBuffer b: buffers) {
|
||||
if (b == null) {
|
||||
break;
|
||||
}
|
||||
if (b.remaining() > 0) {
|
||||
components.add(wrappedBuffer(b.order(BIG_ENDIAN)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!components.isEmpty()) {
|
||||
return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new big-endian composite buffer with no components.
|
||||
*/
|
||||
public static CompositeByteBuf compositeBuffer() {
|
||||
return compositeBuffer(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new big-endian composite buffer with no components.
|
||||
*/
|
||||
public static CompositeByteBuf compositeBuffer(int maxNumComponents) {
|
||||
return new CompositeByteBuf(ALLOC, false, maxNumComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is a copy of the
|
||||
* specified {@code array}. The new buffer's {@code readerIndex} and
|
||||
* {@code writerIndex} are {@code 0} and {@code array.length} respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(byte[] array) {
|
||||
if (array.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
return wrappedBuffer(array.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is a copy of the
|
||||
* specified {@code array}'s sub-region. The new buffer's
|
||||
* {@code readerIndex} and {@code writerIndex} are {@code 0} and
|
||||
* the specified {@code length} respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(byte[] array, int offset, int length) {
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
byte[] copy = new byte[length];
|
||||
System.arraycopy(array, offset, copy, 0, length);
|
||||
return wrappedBuffer(copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer whose content is a copy of the specified
|
||||
* {@code buffer}'s current slice. The new buffer's {@code readerIndex}
|
||||
* and {@code writerIndex} are {@code 0} and {@code buffer.remaining}
|
||||
* respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(ByteBuffer buffer) {
|
||||
int length = buffer.remaining();
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
byte[] copy = new byte[length];
|
||||
int position = buffer.position();
|
||||
try {
|
||||
buffer.get(copy);
|
||||
} finally {
|
||||
buffer.position(position);
|
||||
}
|
||||
return wrappedBuffer(copy).order(buffer.order());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer whose content is a copy of the specified
|
||||
* {@code buffer}'s readable bytes. The new buffer's {@code readerIndex}
|
||||
* and {@code writerIndex} are {@code 0} and {@code buffer.readableBytes}
|
||||
* respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(ByteBuf buffer) {
|
||||
int readable = buffer.readableBytes();
|
||||
if (readable > 0) {
|
||||
ByteBuf copy = buffer(readable);
|
||||
copy.writeBytes(buffer, buffer.readerIndex(), readable);
|
||||
return copy;
|
||||
} else {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is a merged copy of
|
||||
* the specified {@code arrays}. The new buffer's {@code readerIndex}
|
||||
* and {@code writerIndex} are {@code 0} and the sum of all arrays'
|
||||
* {@code length} respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(byte[]... arrays) {
|
||||
switch (arrays.length) {
|
||||
case 0:
|
||||
return EMPTY_BUFFER;
|
||||
case 1:
|
||||
if (arrays[0].length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
} else {
|
||||
return copiedBuffer(arrays[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the specified arrays into one array.
|
||||
int length = 0;
|
||||
for (byte[] a: arrays) {
|
||||
if (Integer.MAX_VALUE - length < a.length) {
|
||||
throw new IllegalArgumentException(
|
||||
"The total length of the specified arrays is too big.");
|
||||
}
|
||||
length += a.length;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
byte[] mergedArray = new byte[length];
|
||||
for (int i = 0, j = 0; i < arrays.length; i ++) {
|
||||
byte[] a = arrays[i];
|
||||
System.arraycopy(a, 0, mergedArray, j, a.length);
|
||||
j += a.length;
|
||||
}
|
||||
|
||||
return wrappedBuffer(mergedArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer whose content is a merged copy of the specified
|
||||
* {@code buffers}' readable bytes. The new buffer's {@code readerIndex}
|
||||
* and {@code writerIndex} are {@code 0} and the sum of all buffers'
|
||||
* {@code readableBytes} respectively.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if the specified buffers' endianness are different from each
|
||||
* other
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(ByteBuf... buffers) {
|
||||
switch (buffers.length) {
|
||||
case 0:
|
||||
return EMPTY_BUFFER;
|
||||
case 1:
|
||||
return copiedBuffer(buffers[0]);
|
||||
}
|
||||
|
||||
// Merge the specified buffers into one buffer.
|
||||
ByteOrder order = null;
|
||||
int length = 0;
|
||||
for (ByteBuf b: buffers) {
|
||||
int bLen = b.readableBytes();
|
||||
if (bLen <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (Integer.MAX_VALUE - length < bLen) {
|
||||
throw new IllegalArgumentException(
|
||||
"The total length of the specified buffers is too big.");
|
||||
}
|
||||
length += bLen;
|
||||
if (order != null) {
|
||||
if (!order.equals(b.order())) {
|
||||
throw new IllegalArgumentException("inconsistent byte order");
|
||||
}
|
||||
} else {
|
||||
order = b.order();
|
||||
}
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
byte[] mergedArray = new byte[length];
|
||||
for (int i = 0, j = 0; i < buffers.length; i ++) {
|
||||
ByteBuf b = buffers[i];
|
||||
int bLen = b.readableBytes();
|
||||
b.getBytes(b.readerIndex(), mergedArray, j, bLen);
|
||||
j += bLen;
|
||||
}
|
||||
|
||||
return wrappedBuffer(mergedArray).order(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new buffer whose content is a merged copy of the specified
|
||||
* {@code buffers}' slices. The new buffer's {@code readerIndex} and
|
||||
* {@code writerIndex} are {@code 0} and the sum of all buffers'
|
||||
* {@code remaining} respectively.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if the specified buffers' endianness are different from each
|
||||
* other
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(ByteBuffer... buffers) {
|
||||
switch (buffers.length) {
|
||||
case 0:
|
||||
return EMPTY_BUFFER;
|
||||
case 1:
|
||||
return copiedBuffer(buffers[0]);
|
||||
}
|
||||
|
||||
// Merge the specified buffers into one buffer.
|
||||
ByteOrder order = null;
|
||||
int length = 0;
|
||||
for (ByteBuffer b: buffers) {
|
||||
int bLen = b.remaining();
|
||||
if (bLen <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (Integer.MAX_VALUE - length < bLen) {
|
||||
throw new IllegalArgumentException(
|
||||
"The total length of the specified buffers is too big.");
|
||||
}
|
||||
length += bLen;
|
||||
if (order != null) {
|
||||
if (!order.equals(b.order())) {
|
||||
throw new IllegalArgumentException("inconsistent byte order");
|
||||
}
|
||||
} else {
|
||||
order = b.order();
|
||||
}
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
byte[] mergedArray = new byte[length];
|
||||
for (int i = 0, j = 0; i < buffers.length; i ++) {
|
||||
ByteBuffer b = buffers[i];
|
||||
int bLen = b.remaining();
|
||||
int oldPos = b.position();
|
||||
b.get(mergedArray, j, bLen);
|
||||
b.position(oldPos);
|
||||
j += bLen;
|
||||
}
|
||||
|
||||
return wrappedBuffer(mergedArray).order(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is the specified
|
||||
* {@code string} encoded in the specified {@code charset}.
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0} and the length of the encoded string respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(CharSequence string, Charset charset) {
|
||||
if (string == null) {
|
||||
throw new NullPointerException("string");
|
||||
}
|
||||
|
||||
if (string instanceof CharBuffer) {
|
||||
return copiedBuffer((CharBuffer) string, charset);
|
||||
}
|
||||
|
||||
return copiedBuffer(CharBuffer.wrap(string), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is a subregion of
|
||||
* the specified {@code string} encoded in the specified {@code charset}.
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0} and the length of the encoded string respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(
|
||||
CharSequence string, int offset, int length, Charset charset) {
|
||||
if (string == null) {
|
||||
throw new NullPointerException("string");
|
||||
}
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
if (string instanceof CharBuffer) {
|
||||
CharBuffer buf = (CharBuffer) string;
|
||||
if (buf.hasArray()) {
|
||||
return copiedBuffer(
|
||||
buf.array(),
|
||||
buf.arrayOffset() + buf.position() + offset,
|
||||
length, charset);
|
||||
}
|
||||
|
||||
buf = buf.slice();
|
||||
buf.limit(length);
|
||||
buf.position(offset);
|
||||
return copiedBuffer(buf, charset);
|
||||
}
|
||||
|
||||
return copiedBuffer(CharBuffer.wrap(string, offset, offset + length), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is the specified
|
||||
* {@code array} encoded in the specified {@code charset}.
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0} and the length of the encoded string respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(char[] array, Charset charset) {
|
||||
if (array == null) {
|
||||
throw new NullPointerException("array");
|
||||
}
|
||||
return copiedBuffer(array, 0, array.length, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new big-endian buffer whose content is a subregion of
|
||||
* the specified {@code array} encoded in the specified {@code charset}.
|
||||
* The new buffer's {@code readerIndex} and {@code writerIndex} are
|
||||
* {@code 0} and the length of the encoded string respectively.
|
||||
*/
|
||||
public static ByteBuf copiedBuffer(char[] array, int offset, int length, Charset charset) {
|
||||
if (array == null) {
|
||||
throw new NullPointerException("array");
|
||||
}
|
||||
if (length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
return copiedBuffer(CharBuffer.wrap(array, offset, length), charset);
|
||||
}
|
||||
|
||||
private static ByteBuf copiedBuffer(CharBuffer buffer, Charset charset) {
|
||||
return ByteBufUtil.encodeString0(ALLOC, true, buffer, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read-only buffer which disallows any modification operations
|
||||
* on the specified {@code buffer}. The new buffer has the same
|
||||
* {@code readerIndex} and {@code writerIndex} with the specified
|
||||
* {@code buffer}.
|
||||
*/
|
||||
public static ByteBuf unmodifiableBuffer(ByteBuf buffer) {
|
||||
ByteOrder endianness = buffer.order();
|
||||
if (endianness == BIG_ENDIAN) {
|
||||
return new ReadOnlyByteBuf(buffer);
|
||||
}
|
||||
|
||||
return new ReadOnlyByteBuf(buffer.order(BIG_ENDIAN)).order(LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 4-byte big-endian buffer that holds the specified 32-bit integer.
|
||||
*/
|
||||
public static ByteBuf copyInt(int value) {
|
||||
ByteBuf buf = buffer(4);
|
||||
buf.writeInt(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a big-endian buffer that holds a sequence of the specified 32-bit integers.
|
||||
*/
|
||||
public static ByteBuf copyInt(int... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 4);
|
||||
for (int v: values) {
|
||||
buffer.writeInt(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 2-byte big-endian buffer that holds the specified 16-bit integer.
|
||||
*/
|
||||
public static ByteBuf copyShort(int value) {
|
||||
ByteBuf buf = buffer(2);
|
||||
buf.writeShort(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 16-bit integers.
|
||||
*/
|
||||
public static ByteBuf copyShort(short... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 2);
|
||||
for (int v: values) {
|
||||
buffer.writeShort(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 16-bit integers.
|
||||
*/
|
||||
public static ByteBuf copyShort(int... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 2);
|
||||
for (int v: values) {
|
||||
buffer.writeShort(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 3-byte big-endian buffer that holds the specified 24-bit integer.
|
||||
*/
|
||||
public static ByteBuf copyMedium(int value) {
|
||||
ByteBuf buf = buffer(3);
|
||||
buf.writeMedium(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 24-bit integers.
|
||||
*/
|
||||
public static ByteBuf copyMedium(int... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 3);
|
||||
for (int v: values) {
|
||||
buffer.writeMedium(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 8-byte big-endian buffer that holds the specified 64-bit integer.
|
||||
*/
|
||||
public static ByteBuf copyLong(long value) {
|
||||
ByteBuf buf = buffer(8);
|
||||
buf.writeLong(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 64-bit integers.
|
||||
*/
|
||||
public static ByteBuf copyLong(long... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 8);
|
||||
for (long v: values) {
|
||||
buffer.writeLong(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new single-byte big-endian buffer that holds the specified boolean value.
|
||||
*/
|
||||
public static ByteBuf copyBoolean(boolean value) {
|
||||
ByteBuf buf = buffer(1);
|
||||
buf.writeBoolean(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified boolean values.
|
||||
*/
|
||||
public static ByteBuf copyBoolean(boolean... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length);
|
||||
for (boolean v: values) {
|
||||
buffer.writeBoolean(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 4-byte big-endian buffer that holds the specified 32-bit floating point number.
|
||||
*/
|
||||
public static ByteBuf copyFloat(float value) {
|
||||
ByteBuf buf = buffer(4);
|
||||
buf.writeFloat(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 32-bit floating point numbers.
|
||||
*/
|
||||
public static ByteBuf copyFloat(float... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 4);
|
||||
for (float v: values) {
|
||||
buffer.writeFloat(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new 8-byte big-endian buffer that holds the specified 64-bit floating point number.
|
||||
*/
|
||||
public static ByteBuf copyDouble(double value) {
|
||||
ByteBuf buf = buffer(8);
|
||||
buf.writeDouble(value);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new big-endian buffer that holds a sequence of the specified 64-bit floating point numbers.
|
||||
*/
|
||||
public static ByteBuf copyDouble(double... values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf buffer = buffer(values.length * 8);
|
||||
for (double v: values) {
|
||||
buffer.writeDouble(v);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unreleasable view on the given {@link ByteBuf} which will just ignore release and retain calls.
|
||||
*/
|
||||
public static ByteBuf unreleasableBuffer(ByteBuf buf) {
|
||||
return new UnreleasableByteBuf(buf);
|
||||
}
|
||||
|
||||
private Unpooled() {
|
||||
// Unused
|
||||
}
|
||||
}
|
62
common/src/common/net/buffer/UnpooledByteBufAllocator.java
Normal file
62
common/src/common/net/buffer/UnpooledByteBufAllocator.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* Simplistic {@link ByteBufAllocator} implementation that does not pool anything.
|
||||
*/
|
||||
public final class UnpooledByteBufAllocator extends AbstractByteBufAllocator {
|
||||
|
||||
/**
|
||||
* Default instance
|
||||
*/
|
||||
public static final UnpooledByteBufAllocator DEFAULT =
|
||||
new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred());
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param preferDirect {@code true} if {@link #buffer(int)} should try to allocate a direct buffer rather than
|
||||
* a heap buffer
|
||||
*/
|
||||
public UnpooledByteBufAllocator(boolean preferDirect) {
|
||||
super(preferDirect);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
|
||||
return new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
|
||||
ByteBuf buf;
|
||||
if (PlatformDependent.hasUnsafe()) {
|
||||
buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
|
||||
} else {
|
||||
buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
return toLeakAwareBuffer(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectBufferPooled() {
|
||||
return false;
|
||||
}
|
||||
}
|
604
common/src/common/net/buffer/UnpooledDirectByteBuf.java
Normal file
604
common/src/common/net/buffer/UnpooledDirectByteBuf.java
Normal file
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)}
|
||||
* and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the
|
||||
* constructor explicitly.
|
||||
*/
|
||||
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
|
||||
|
||||
private final ByteBufAllocator alloc;
|
||||
|
||||
private ByteBuffer buffer;
|
||||
private ByteBuffer tmpNioBuf;
|
||||
private int capacity;
|
||||
private boolean doNotFree;
|
||||
|
||||
/**
|
||||
* Creates a new direct buffer.
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the underlying direct buffer
|
||||
* @param maxCapacity the maximum capacity of the underlying direct buffer
|
||||
*/
|
||||
protected UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
if (initialCapacity < 0) {
|
||||
throw new IllegalArgumentException("initialCapacity: " + initialCapacity);
|
||||
}
|
||||
if (maxCapacity < 0) {
|
||||
throw new IllegalArgumentException("maxCapacity: " + maxCapacity);
|
||||
}
|
||||
if (initialCapacity > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
setByteBuffer(ByteBuffer.allocateDirect(initialCapacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new direct buffer by wrapping the specified initial buffer.
|
||||
*
|
||||
* @param maxCapacity the maximum capacity of the underlying direct buffer
|
||||
*/
|
||||
protected UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
if (initialBuffer == null) {
|
||||
throw new NullPointerException("initialBuffer");
|
||||
}
|
||||
if (!initialBuffer.isDirect()) {
|
||||
throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
|
||||
}
|
||||
if (initialBuffer.isReadOnly()) {
|
||||
throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
|
||||
}
|
||||
|
||||
int initialCapacity = initialBuffer.remaining();
|
||||
if (initialCapacity > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
doNotFree = true;
|
||||
setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN));
|
||||
writerIndex(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new direct {@link ByteBuffer} with the given initialCapacity.
|
||||
*/
|
||||
protected ByteBuffer allocateDirect(int initialCapacity) {
|
||||
return ByteBuffer.allocateDirect(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a direct {@link ByteBuffer}
|
||||
*/
|
||||
protected void freeDirect(ByteBuffer buffer) {
|
||||
PlatformDependent.freeDirectBuffer(buffer);
|
||||
}
|
||||
|
||||
private void setByteBuffer(ByteBuffer buffer) {
|
||||
ByteBuffer oldBuffer = this.buffer;
|
||||
if (oldBuffer != null) {
|
||||
if (doNotFree) {
|
||||
doNotFree = false;
|
||||
} else {
|
||||
freeDirect(oldBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
this.buffer = buffer;
|
||||
tmpNioBuf = null;
|
||||
capacity = buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
ensureAccessible();
|
||||
if (newCapacity < 0 || newCapacity > maxCapacity()) {
|
||||
throw new IllegalArgumentException("newCapacity: " + newCapacity);
|
||||
}
|
||||
|
||||
int readerIndex = readerIndex();
|
||||
int writerIndex = writerIndex();
|
||||
|
||||
int oldCapacity = capacity;
|
||||
if (newCapacity > oldCapacity) {
|
||||
ByteBuffer oldBuffer = buffer;
|
||||
ByteBuffer newBuffer = allocateDirect(newCapacity);
|
||||
oldBuffer.position(0).limit(oldBuffer.capacity());
|
||||
newBuffer.position(0).limit(oldBuffer.capacity());
|
||||
newBuffer.put(oldBuffer);
|
||||
newBuffer.clear();
|
||||
setByteBuffer(newBuffer);
|
||||
} else if (newCapacity < oldCapacity) {
|
||||
ByteBuffer oldBuffer = buffer;
|
||||
ByteBuffer newBuffer = allocateDirect(newCapacity);
|
||||
if (readerIndex < newCapacity) {
|
||||
if (writerIndex > newCapacity) {
|
||||
writerIndex(writerIndex = newCapacity);
|
||||
}
|
||||
oldBuffer.position(readerIndex).limit(writerIndex);
|
||||
newBuffer.position(readerIndex).limit(writerIndex);
|
||||
newBuffer.put(oldBuffer);
|
||||
newBuffer.clear();
|
||||
} else {
|
||||
setIndex(newCapacity, newCapacity);
|
||||
}
|
||||
setByteBuffer(newBuffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return alloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
ensureAccessible();
|
||||
return _getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return buffer.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
ensureAccessible();
|
||||
return _getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return buffer.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
ensureAccessible();
|
||||
return _getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
ensureAccessible();
|
||||
return _getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return buffer.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
ensureAccessible();
|
||||
return _getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return buffer.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.capacity());
|
||||
if (dst.hasArray()) {
|
||||
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else if (dst.nioBufferCount() > 0) {
|
||||
for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
|
||||
int bbLen = bb.remaining();
|
||||
getBytes(index, bb);
|
||||
index += bbLen;
|
||||
}
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
getBytes(index, dst, dstIndex, length, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
|
||||
checkDstIndex(index, length, dstIndex, dst.length);
|
||||
|
||||
if (dstIndex < 0 || dstIndex > dst.length - length) {
|
||||
throw new IndexOutOfBoundsException(String.format(
|
||||
"dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
tmpBuf.get(dst, dstIndex, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, dstIndex, length, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
getBytes(index, dst, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, ByteBuffer dst, boolean internal) {
|
||||
checkIndex(index);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
int length = dst.remaining();
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
buffer.put(index, (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
buffer.putShort(index, (short) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setMedium(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
setByte(index, (byte) (value >>> 16));
|
||||
setByte(index + 1, (byte) (value >>> 8));
|
||||
setByte(index + 2, (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
buffer.putInt(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
ensureAccessible();
|
||||
_setLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
buffer.putLong(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.capacity());
|
||||
if (src.nioBufferCount() > 0) {
|
||||
for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) {
|
||||
int bbLen = bb.remaining();
|
||||
setBytes(index, bb);
|
||||
index += bbLen;
|
||||
}
|
||||
} else {
|
||||
src.getBytes(srcIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.length);
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
tmpBuf.put(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
ensureAccessible();
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
if (src == tmpBuf) {
|
||||
src = src.duplicate();
|
||||
}
|
||||
|
||||
tmpBuf.clear().position(index).limit(index + src.remaining());
|
||||
tmpBuf.put(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
getBytes(index, out, length, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.hasArray()) {
|
||||
out.write(buffer.array(), index + buffer.arrayOffset(), length);
|
||||
} else {
|
||||
byte[] tmp = new byte[length];
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index);
|
||||
tmpBuf.get(tmp);
|
||||
out.write(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, out, length, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
return out.write(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
if (buffer.hasArray()) {
|
||||
return in.read(buffer.array(), buffer.arrayOffset() + index, length);
|
||||
} else {
|
||||
byte[] tmp = new byte[length];
|
||||
int readBytes = in.read(tmp);
|
||||
if (readBytes <= 0) {
|
||||
return readBytes;
|
||||
}
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index);
|
||||
tmpBuf.put(tmp, 0, readBytes);
|
||||
return readBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
try {
|
||||
return in.read(tmpNioBuf);
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
ensureAccessible();
|
||||
ByteBuffer src;
|
||||
try {
|
||||
src = (ByteBuffer) buffer.duplicate().clear().position(index).limit(index + length);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length));
|
||||
}
|
||||
|
||||
return alloc().directBuffer(length, maxCapacity()).writeBytes(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
private ByteBuffer internalNioBuffer() {
|
||||
ByteBuffer tmpNioBuf = this.tmpNioBuf;
|
||||
if (tmpNioBuf == null) {
|
||||
this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
|
||||
}
|
||||
return tmpNioBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
ByteBuffer buffer = this.buffer;
|
||||
if (buffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buffer = null;
|
||||
|
||||
if (!doNotFree) {
|
||||
freeDirect(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
}
|
449
common/src/common/net/buffer/UnpooledHeapByteBuf.java
Normal file
449
common/src/common/net/buffer/UnpooledHeapByteBuf.java
Normal file
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* Big endian Java heap buffer implementation.
|
||||
*/
|
||||
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
|
||||
|
||||
private final ByteBufAllocator alloc;
|
||||
private byte[] array;
|
||||
private ByteBuffer tmpNioBuf;
|
||||
|
||||
/**
|
||||
* Creates a new heap buffer with a newly allocated byte array.
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the underlying byte array
|
||||
* @param maxCapacity the max capacity of the underlying byte array
|
||||
*/
|
||||
protected UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
|
||||
this(alloc, new byte[initialCapacity], 0, 0, maxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new heap buffer with an existing byte array.
|
||||
*
|
||||
* @param initialArray the initial underlying byte array
|
||||
* @param maxCapacity the max capacity of the underlying byte array
|
||||
*/
|
||||
protected UnpooledHeapByteBuf(ByteBufAllocator alloc, byte[] initialArray, int maxCapacity) {
|
||||
this(alloc, initialArray, 0, initialArray.length, maxCapacity);
|
||||
}
|
||||
|
||||
private UnpooledHeapByteBuf(
|
||||
ByteBufAllocator alloc, byte[] initialArray, int readerIndex, int writerIndex, int maxCapacity) {
|
||||
|
||||
super(maxCapacity);
|
||||
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
if (initialArray == null) {
|
||||
throw new NullPointerException("initialArray");
|
||||
}
|
||||
if (initialArray.length > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity(%d) > maxCapacity(%d)", initialArray.length, maxCapacity));
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
setArray(initialArray);
|
||||
setIndex(readerIndex, writerIndex);
|
||||
}
|
||||
|
||||
private void setArray(byte[] initialArray) {
|
||||
array = initialArray;
|
||||
tmpNioBuf = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return alloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
ensureAccessible();
|
||||
return array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
ensureAccessible();
|
||||
if (newCapacity < 0 || newCapacity > maxCapacity()) {
|
||||
throw new IllegalArgumentException("newCapacity: " + newCapacity);
|
||||
}
|
||||
|
||||
int oldCapacity = array.length;
|
||||
if (newCapacity > oldCapacity) {
|
||||
byte[] newArray = new byte[newCapacity];
|
||||
System.arraycopy(array, 0, newArray, 0, array.length);
|
||||
setArray(newArray);
|
||||
} else if (newCapacity < oldCapacity) {
|
||||
byte[] newArray = new byte[newCapacity];
|
||||
int readerIndex = readerIndex();
|
||||
if (readerIndex < newCapacity) {
|
||||
int writerIndex = writerIndex();
|
||||
if (writerIndex > newCapacity) {
|
||||
writerIndex(writerIndex = newCapacity);
|
||||
}
|
||||
System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
|
||||
} else {
|
||||
setIndex(newCapacity, newCapacity);
|
||||
}
|
||||
setArray(newArray);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
ensureAccessible();
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.capacity());
|
||||
if (dst.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(array, index, dst.memoryAddress() + dstIndex, length);
|
||||
} else if (dst.hasArray()) {
|
||||
getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else {
|
||||
dst.setBytes(dstIndex, array, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkDstIndex(index, length, dstIndex, dst.length);
|
||||
System.arraycopy(array, index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
ensureAccessible();
|
||||
dst.put(array, index, Math.min(capacity() - index, dst.remaining()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
out.write(array, index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
ensureAccessible();
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = ByteBuffer.wrap(array);
|
||||
}
|
||||
return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.capacity());
|
||||
if (src.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, array, index, length);
|
||||
} else if (src.hasArray()) {
|
||||
setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
|
||||
} else {
|
||||
src.getBytes(srcIndex, array, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkSrcIndex(index, length, srcIndex, src.length);
|
||||
System.arraycopy(src, srcIndex, array, index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
ensureAccessible();
|
||||
src.get(array, index, src.remaining());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
return in.read(array, index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
try {
|
||||
return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length));
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
ensureAccessible();
|
||||
return ByteBuffer.wrap(array, index, length).slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
ensureAccessible();
|
||||
return _getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return array[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
ensureAccessible();
|
||||
return _getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
return (short) (array[index] << 8 | array[index + 1] & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
ensureAccessible();
|
||||
return _getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
return (array[index] & 0xff) << 16 |
|
||||
(array[index + 1] & 0xff) << 8 |
|
||||
array[index + 2] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
ensureAccessible();
|
||||
return _getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
return (array[index] & 0xff) << 24 |
|
||||
(array[index + 1] & 0xff) << 16 |
|
||||
(array[index + 2] & 0xff) << 8 |
|
||||
array[index + 3] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
ensureAccessible();
|
||||
return _getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
return ((long) array[index] & 0xff) << 56 |
|
||||
((long) array[index + 1] & 0xff) << 48 |
|
||||
((long) array[index + 2] & 0xff) << 40 |
|
||||
((long) array[index + 3] & 0xff) << 32 |
|
||||
((long) array[index + 4] & 0xff) << 24 |
|
||||
((long) array[index + 5] & 0xff) << 16 |
|
||||
((long) array[index + 6] & 0xff) << 8 |
|
||||
(long) array[index + 7] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
array[index] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
array[index] = (byte) (value >>> 8);
|
||||
array[index + 1] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setMedium(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
array[index] = (byte) (value >>> 16);
|
||||
array[index + 1] = (byte) (value >>> 8);
|
||||
array[index + 2] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
ensureAccessible();
|
||||
_setInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
array[index] = (byte) (value >>> 24);
|
||||
array[index + 1] = (byte) (value >>> 16);
|
||||
array[index + 2] = (byte) (value >>> 8);
|
||||
array[index + 3] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
ensureAccessible();
|
||||
_setLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
array[index] = (byte) (value >>> 56);
|
||||
array[index + 1] = (byte) (value >>> 48);
|
||||
array[index + 2] = (byte) (value >>> 40);
|
||||
array[index + 3] = (byte) (value >>> 32);
|
||||
array[index + 4] = (byte) (value >>> 24);
|
||||
array[index + 5] = (byte) (value >>> 16);
|
||||
array[index + 6] = (byte) (value >>> 8);
|
||||
array[index + 7] = (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
byte[] copiedArray = new byte[length];
|
||||
System.arraycopy(array, index, copiedArray, 0, length);
|
||||
return new UnpooledHeapByteBuf(alloc(), copiedArray, maxCapacity());
|
||||
}
|
||||
|
||||
private ByteBuffer internalNioBuffer() {
|
||||
ByteBuffer tmpNioBuf = this.tmpNioBuf;
|
||||
if (tmpNioBuf == null) {
|
||||
this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array);
|
||||
}
|
||||
return tmpNioBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
array = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
}
|
524
common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java
Normal file
524
common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java
Normal file
|
@ -0,0 +1,524 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)}
|
||||
* and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the
|
||||
* constructor explicitly.
|
||||
*/
|
||||
public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf {
|
||||
|
||||
private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
|
||||
private final ByteBufAllocator alloc;
|
||||
|
||||
private long memoryAddress;
|
||||
private ByteBuffer buffer;
|
||||
private ByteBuffer tmpNioBuf;
|
||||
private int capacity;
|
||||
private boolean doNotFree;
|
||||
|
||||
/**
|
||||
* Creates a new direct buffer.
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the underlying direct buffer
|
||||
* @param maxCapacity the maximum capacity of the underlying direct buffer
|
||||
*/
|
||||
protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
if (initialCapacity < 0) {
|
||||
throw new IllegalArgumentException("initialCapacity: " + initialCapacity);
|
||||
}
|
||||
if (maxCapacity < 0) {
|
||||
throw new IllegalArgumentException("maxCapacity: " + maxCapacity);
|
||||
}
|
||||
if (initialCapacity > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
setByteBuffer(allocateDirect(initialCapacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new direct buffer by wrapping the specified initial buffer.
|
||||
*
|
||||
* @param maxCapacity the maximum capacity of the underlying direct buffer
|
||||
*/
|
||||
protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
|
||||
super(maxCapacity);
|
||||
if (alloc == null) {
|
||||
throw new NullPointerException("alloc");
|
||||
}
|
||||
if (initialBuffer == null) {
|
||||
throw new NullPointerException("initialBuffer");
|
||||
}
|
||||
if (!initialBuffer.isDirect()) {
|
||||
throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
|
||||
}
|
||||
if (initialBuffer.isReadOnly()) {
|
||||
throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
|
||||
}
|
||||
|
||||
int initialCapacity = initialBuffer.remaining();
|
||||
if (initialCapacity > maxCapacity) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
|
||||
}
|
||||
|
||||
this.alloc = alloc;
|
||||
doNotFree = true;
|
||||
setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN));
|
||||
writerIndex(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new direct {@link ByteBuffer} with the given initialCapacity.
|
||||
*/
|
||||
protected ByteBuffer allocateDirect(int initialCapacity) {
|
||||
return ByteBuffer.allocateDirect(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a direct {@link ByteBuffer}
|
||||
*/
|
||||
protected void freeDirect(ByteBuffer buffer) {
|
||||
PlatformDependent.freeDirectBuffer(buffer);
|
||||
}
|
||||
|
||||
private void setByteBuffer(ByteBuffer buffer) {
|
||||
ByteBuffer oldBuffer = this.buffer;
|
||||
if (oldBuffer != null) {
|
||||
if (doNotFree) {
|
||||
doNotFree = false;
|
||||
} else {
|
||||
freeDirect(oldBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
this.buffer = buffer;
|
||||
memoryAddress = PlatformDependent.directBufferAddress(buffer);
|
||||
tmpNioBuf = null;
|
||||
capacity = buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
ensureAccessible();
|
||||
if (newCapacity < 0 || newCapacity > maxCapacity()) {
|
||||
throw new IllegalArgumentException("newCapacity: " + newCapacity);
|
||||
}
|
||||
|
||||
int readerIndex = readerIndex();
|
||||
int writerIndex = writerIndex();
|
||||
|
||||
int oldCapacity = capacity;
|
||||
if (newCapacity > oldCapacity) {
|
||||
ByteBuffer oldBuffer = buffer;
|
||||
ByteBuffer newBuffer = allocateDirect(newCapacity);
|
||||
oldBuffer.position(0).limit(oldBuffer.capacity());
|
||||
newBuffer.position(0).limit(oldBuffer.capacity());
|
||||
newBuffer.put(oldBuffer);
|
||||
newBuffer.clear();
|
||||
setByteBuffer(newBuffer);
|
||||
} else if (newCapacity < oldCapacity) {
|
||||
ByteBuffer oldBuffer = buffer;
|
||||
ByteBuffer newBuffer = allocateDirect(newCapacity);
|
||||
if (readerIndex < newCapacity) {
|
||||
if (writerIndex > newCapacity) {
|
||||
writerIndex(writerIndex = newCapacity);
|
||||
}
|
||||
oldBuffer.position(readerIndex).limit(writerIndex);
|
||||
newBuffer.position(readerIndex).limit(writerIndex);
|
||||
newBuffer.put(oldBuffer);
|
||||
newBuffer.clear();
|
||||
} else {
|
||||
setIndex(newCapacity, newCapacity);
|
||||
}
|
||||
setByteBuffer(newBuffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return alloc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
throw new UnsupportedOperationException("direct buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return memoryAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int index) {
|
||||
return PlatformDependent.getByte(addr(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int index) {
|
||||
short v = PlatformDependent.getShort(addr(index));
|
||||
return NATIVE_ORDER? v : Short.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int index) {
|
||||
long addr = addr(index);
|
||||
return (PlatformDependent.getByte(addr) & 0xff) << 16 |
|
||||
(PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
|
||||
PlatformDependent.getByte(addr + 2) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int index) {
|
||||
int v = PlatformDependent.getInt(addr(index));
|
||||
return NATIVE_ORDER? v : Integer.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int index) {
|
||||
long v = PlatformDependent.getLong(addr(index));
|
||||
return NATIVE_ORDER? v : Long.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
|
||||
}
|
||||
|
||||
if (dst.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
|
||||
} else if (dst.hasArray()) {
|
||||
PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
|
||||
} else {
|
||||
dst.setBytes(dstIndex, this, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
if (dstIndex < 0 || dstIndex > dst.length - length) {
|
||||
throw new IndexOutOfBoundsException(String.format(
|
||||
"dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
getBytes(index, dst, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void getBytes(int index, ByteBuffer dst, boolean internal) {
|
||||
checkIndex(index);
|
||||
if (dst == null) {
|
||||
throw new NullPointerException("dst");
|
||||
}
|
||||
|
||||
int bytesToCopy = Math.min(capacity() - index, dst.remaining());
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index).limit(index + bytesToCopy);
|
||||
dst.put(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
int length = dst.remaining();
|
||||
checkReadableBytes(length);
|
||||
getBytes(readerIndex, dst, true);
|
||||
readerIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
PlatformDependent.putByte(addr(index), (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
long addr = addr(index);
|
||||
PlatformDependent.putByte(addr, (byte) (value >>> 16));
|
||||
PlatformDependent.putByte(addr + 1, (byte) (value >>> 8));
|
||||
PlatformDependent.putByte(addr + 2, (byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (src == null) {
|
||||
throw new NullPointerException("src");
|
||||
}
|
||||
if (srcIndex < 0 || srcIndex > src.capacity() - length) {
|
||||
throw new IndexOutOfBoundsException("srcIndex: " + srcIndex);
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
if (src.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length);
|
||||
} else if (src.hasArray()) {
|
||||
PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length);
|
||||
} else {
|
||||
src.getBytes(srcIndex, this, index, length);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
checkIndex(index, length);
|
||||
if (length != 0) {
|
||||
PlatformDependent.copyMemory(src, srcIndex, addr(index), length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
ensureAccessible();
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
if (src == tmpBuf) {
|
||||
src = src.duplicate();
|
||||
}
|
||||
|
||||
tmpBuf.clear().position(index).limit(index + src.remaining());
|
||||
tmpBuf.put(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length != 0) {
|
||||
byte[] tmp = new byte[length];
|
||||
PlatformDependent.copyMemory(addr(index), tmp, 0, length);
|
||||
out.write(tmp);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return getBytes(index, out, length, false);
|
||||
}
|
||||
|
||||
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
|
||||
ensureAccessible();
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ByteBuffer tmpBuf;
|
||||
if (internal) {
|
||||
tmpBuf = internalNioBuffer();
|
||||
} else {
|
||||
tmpBuf = buffer.duplicate();
|
||||
}
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
return out.write(tmpBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
checkReadableBytes(length);
|
||||
int readBytes = getBytes(readerIndex, out, length, true);
|
||||
readerIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
checkIndex(index, length);
|
||||
byte[] tmp = new byte[length];
|
||||
int readBytes = in.read(tmp);
|
||||
if (readBytes > 0) {
|
||||
PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes);
|
||||
}
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ensureAccessible();
|
||||
ByteBuffer tmpBuf = internalNioBuffer();
|
||||
tmpBuf.clear().position(index).limit(index + length);
|
||||
try {
|
||||
return in.read(tmpBuf);
|
||||
} catch (ClosedChannelException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return new ByteBuffer[] { nioBuffer(index, length) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
ByteBuf copy = alloc().directBuffer(length, maxCapacity());
|
||||
if (length != 0) {
|
||||
if (copy.hasMemoryAddress()) {
|
||||
PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
|
||||
copy.setIndex(0, length);
|
||||
} else {
|
||||
copy.writeBytes(this, index, length);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
|
||||
}
|
||||
|
||||
private ByteBuffer internalNioBuffer() {
|
||||
ByteBuffer tmpNioBuf = this.tmpNioBuf;
|
||||
if (tmpNioBuf == null) {
|
||||
this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
|
||||
}
|
||||
return tmpNioBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
ByteBuffer buffer = this.buffer;
|
||||
if (buffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buffer = null;
|
||||
|
||||
if (!doNotFree) {
|
||||
freeDirect(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
long addr(int index) {
|
||||
return memoryAddress + index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SwappedByteBuf newSwappedByteBuf() {
|
||||
return new UnsafeDirectSwappedByteBuf(this);
|
||||
}
|
||||
}
|
87
common/src/common/net/buffer/UnreleasableByteBuf.java
Normal file
87
common/src/common/net/buffer/UnreleasableByteBuf.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* A {@link ByteBuf} implementation that wraps another buffer to prevent a user from increasing or decreasing the
|
||||
* wrapped buffer's reference count.
|
||||
*/
|
||||
final class UnreleasableByteBuf extends WrappedByteBuf {
|
||||
|
||||
private SwappedByteBuf swappedBuf;
|
||||
|
||||
UnreleasableByteBuf(ByteBuf buf) {
|
||||
super(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
if (endianness == null) {
|
||||
throw new NullPointerException("endianness");
|
||||
}
|
||||
if (endianness == order()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
SwappedByteBuf swappedBuf = this.swappedBuf;
|
||||
if (swappedBuf == null) {
|
||||
this.swappedBuf = swappedBuf = new SwappedByteBuf(this);
|
||||
}
|
||||
return swappedBuf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
return new UnreleasableByteBuf(buf.readSlice(length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
return new UnreleasableByteBuf(buf.slice());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return new UnreleasableByteBuf(buf.slice(index, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return new UnreleasableByteBuf(buf.duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return false;
|
||||
}
|
||||
}
|
186
common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java
Normal file
186
common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* Special {@link SwappedByteBuf} for {@link ByteBuf}s that are backed by a {@code memoryAddress}.
|
||||
*/
|
||||
final class UnsafeDirectSwappedByteBuf extends SwappedByteBuf {
|
||||
private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
private final boolean nativeByteOrder;
|
||||
private final AbstractByteBuf wrapped;
|
||||
|
||||
UnsafeDirectSwappedByteBuf(AbstractByteBuf buf) {
|
||||
super(buf);
|
||||
wrapped = buf;
|
||||
nativeByteOrder = NATIVE_ORDER == (order() == ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
private long addr(int index) {
|
||||
// We need to call wrapped.memoryAddress() everytime and NOT cache it as it may change if the buffer expand.
|
||||
// See:
|
||||
// - https://github.com/netty/netty/issues/2587
|
||||
// - https://github.com/netty/netty/issues/2580
|
||||
return wrapped.memoryAddress() + index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
wrapped.checkIndex(index, 8);
|
||||
long v = PlatformDependent.getLong(addr(index));
|
||||
return nativeByteOrder? v : Long.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
return Float.intBitsToFloat(getInt(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
return Double.longBitsToDouble(getLong(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
return (char) getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int index) {
|
||||
return getInt(index) & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
wrapped.checkIndex(index, 4);
|
||||
int v = PlatformDependent.getInt(addr(index));
|
||||
return nativeByteOrder? v : Integer.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int index) {
|
||||
return getShort(index) & 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
wrapped.checkIndex(index, 2);
|
||||
short v = PlatformDependent.getShort(addr(index));
|
||||
return nativeByteOrder? v : Short.reverseBytes(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
wrapped.checkIndex(index, 2);
|
||||
_setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
wrapped.checkIndex(index, 4);
|
||||
_setInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
wrapped.checkIndex(index, 8);
|
||||
_setLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int index, int value) {
|
||||
setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setFloat(int index, float value) {
|
||||
setInt(index, Float.floatToRawIntBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setDouble(int index, double value) {
|
||||
setLong(index, Double.doubleToRawLongBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShort(int value) {
|
||||
wrapped.ensureAccessible();
|
||||
wrapped.ensureWritable(2);
|
||||
_setShort(wrapped.writerIndex, value);
|
||||
wrapped.writerIndex += 2;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int value) {
|
||||
wrapped.ensureAccessible();
|
||||
wrapped.ensureWritable(4);
|
||||
_setInt(wrapped.writerIndex, value);
|
||||
wrapped.writerIndex += 4;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long value) {
|
||||
wrapped.ensureAccessible();
|
||||
wrapped.ensureWritable(8);
|
||||
_setLong(wrapped.writerIndex, value);
|
||||
wrapped.writerIndex += 8;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int value) {
|
||||
writeShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeFloat(float value) {
|
||||
writeInt(Float.floatToRawIntBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeDouble(double value) {
|
||||
writeLong(Double.doubleToRawLongBits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void _setShort(int index, int value) {
|
||||
PlatformDependent.putShort(addr(index), nativeByteOrder ? (short) value : Short.reverseBytes((short) value));
|
||||
}
|
||||
|
||||
private void _setInt(int index, int value) {
|
||||
PlatformDependent.putInt(addr(index), nativeByteOrder ? value : Integer.reverseBytes(value));
|
||||
}
|
||||
|
||||
private void _setLong(int index, long value) {
|
||||
PlatformDependent.putLong(addr(index), nativeByteOrder ? value : Long.reverseBytes(value));
|
||||
}
|
||||
}
|
827
common/src/common/net/buffer/WrappedByteBuf.java
Normal file
827
common/src/common/net/buffer/WrappedByteBuf.java
Normal file
|
@ -0,0 +1,827 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
class WrappedByteBuf extends ByteBuf {
|
||||
|
||||
protected final ByteBuf buf;
|
||||
|
||||
protected WrappedByteBuf(ByteBuf buf) {
|
||||
if (buf == null) {
|
||||
throw new NullPointerException("buf");
|
||||
}
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return buf.hasMemoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return buf.memoryAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return buf.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int newCapacity) {
|
||||
buf.capacity(newCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxCapacity() {
|
||||
return buf.maxCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return buf.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return buf.order();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf order(ByteOrder endianness) {
|
||||
return buf.order(endianness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return buf.isDirect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readerIndex() {
|
||||
return buf.readerIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readerIndex(int readerIndex) {
|
||||
buf.readerIndex(readerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writerIndex() {
|
||||
return buf.writerIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writerIndex(int writerIndex) {
|
||||
buf.writerIndex(writerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setIndex(int readerIndex, int writerIndex) {
|
||||
buf.setIndex(readerIndex, writerIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableBytes() {
|
||||
return buf.readableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writableBytes() {
|
||||
return buf.writableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxWritableBytes() {
|
||||
return buf.maxWritableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
return buf.isReadable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return buf.isWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf clear() {
|
||||
buf.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markReaderIndex() {
|
||||
buf.markReaderIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetReaderIndex() {
|
||||
buf.resetReaderIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf markWriterIndex() {
|
||||
buf.markWriterIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf resetWriterIndex() {
|
||||
buf.resetWriterIndex();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardReadBytes() {
|
||||
buf.discardReadBytes();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf discardSomeReadBytes() {
|
||||
buf.discardSomeReadBytes();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ensureWritable(int minWritableBytes) {
|
||||
buf.ensureWritable(minWritableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ensureWritable(int minWritableBytes, boolean force) {
|
||||
return buf.ensureWritable(minWritableBytes, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int index) {
|
||||
return buf.getBoolean(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return buf.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getUnsignedByte(int index) {
|
||||
return buf.getUnsignedByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int index) {
|
||||
return buf.getShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int index) {
|
||||
return buf.getUnsignedShort(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMedium(int index) {
|
||||
return buf.getMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int index) {
|
||||
return buf.getUnsignedMedium(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return buf.getInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int index) {
|
||||
return buf.getUnsignedInt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int index) {
|
||||
return buf.getLong(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int index) {
|
||||
return buf.getChar(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int index) {
|
||||
return buf.getFloat(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
return buf.getDouble(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int length) {
|
||||
buf.getBytes(index, dst, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
buf.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
buf.getBytes(index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
buf.getBytes(index, dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
|
||||
buf.getBytes(index, out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
return buf.getBytes(index, out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBoolean(int index, boolean value) {
|
||||
buf.setBoolean(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setByte(int index, int value) {
|
||||
buf.setByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShort(int index, int value) {
|
||||
buf.setShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int index, int value) {
|
||||
buf.setMedium(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int index, int value) {
|
||||
buf.setInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int index, long value) {
|
||||
buf.setLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int index, int value) {
|
||||
buf.setChar(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setFloat(int index, float value) {
|
||||
buf.setFloat(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setDouble(int index, double value) {
|
||||
buf.setDouble(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int length) {
|
||||
buf.setBytes(index, src, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
buf.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
buf.setBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
buf.setBytes(index, src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
return buf.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
return buf.setBytes(index, in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setZero(int index, int length) {
|
||||
buf.setZero(index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
return buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
return buf.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readUnsignedByte() {
|
||||
return buf.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
return buf.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
return buf.readUnsignedShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMedium() {
|
||||
return buf.readMedium();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMedium() {
|
||||
return buf.readUnsignedMedium();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
return buf.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedInt() {
|
||||
return buf.readUnsignedInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
return buf.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
return buf.readChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
return buf.readFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
return buf.readDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(int length) {
|
||||
return buf.readBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(int length) {
|
||||
return buf.readSlice(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int length) {
|
||||
buf.readBytes(dst, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
|
||||
buf.readBytes(dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
|
||||
buf.readBytes(dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuffer dst) {
|
||||
buf.readBytes(dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(OutputStream out, int length) throws IOException {
|
||||
buf.readBytes(out, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(GatheringByteChannel out, int length) throws IOException {
|
||||
return buf.readBytes(out, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf skipBytes(int length) {
|
||||
buf.skipBytes(length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBoolean(boolean value) {
|
||||
buf.writeBoolean(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeByte(int value) {
|
||||
buf.writeByte(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShort(int value) {
|
||||
buf.writeShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMedium(int value) {
|
||||
buf.writeMedium(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int value) {
|
||||
buf.writeInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long value) {
|
||||
buf.writeLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int value) {
|
||||
buf.writeChar(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeFloat(float value) {
|
||||
buf.writeFloat(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeDouble(double value) {
|
||||
buf.writeDouble(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int length) {
|
||||
buf.writeBytes(src, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
|
||||
buf.writeBytes(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
|
||||
buf.writeBytes(src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeBytes(ByteBuffer src) {
|
||||
buf.writeBytes(src);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(InputStream in, int length) throws IOException {
|
||||
return buf.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
|
||||
return buf.writeBytes(in, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeZero(int length) {
|
||||
buf.writeZero(length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int fromIndex, int toIndex, byte value) {
|
||||
return buf.indexOf(fromIndex, toIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(byte value) {
|
||||
return buf.bytesBefore(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int length, byte value) {
|
||||
return buf.bytesBefore(length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bytesBefore(int index, int length, byte value) {
|
||||
return buf.bytesBefore(index, length, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(ByteBufProcessor processor) {
|
||||
return buf.forEachByte(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int index, int length, ByteBufProcessor processor) {
|
||||
return buf.forEachByte(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(ByteBufProcessor processor) {
|
||||
return buf.forEachByteDesc(processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
|
||||
return buf.forEachByteDesc(index, length, processor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy() {
|
||||
return buf.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
return buf.copy(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice() {
|
||||
return buf.slice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int index, int length) {
|
||||
return buf.slice(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return buf.duplicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return buf.nioBufferCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer() {
|
||||
return buf.nioBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int index, int length) {
|
||||
return buf.nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers() {
|
||||
return buf.nioBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int index, int length) {
|
||||
return buf.nioBuffers(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int index, int length) {
|
||||
return buf.internalNioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return buf.hasArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return buf.arrayOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
return buf.toString(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
return buf.toString(index, length, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return buf.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ByteBuf buffer) {
|
||||
return buf.compareTo(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(this) + '(' + buf.toString() + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int increment) {
|
||||
buf.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
buf.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(int size) {
|
||||
return buf.isReadable(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(int size) {
|
||||
return buf.isWritable(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return buf.refCnt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return buf.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return buf.release(decrement);
|
||||
}
|
||||
}
|
873
common/src/common/net/channel/AbstractChannel.java
Normal file
873
common/src/common/net/channel/AbstractChannel.java
Normal file
|
@ -0,0 +1,873 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.util.DefaultAttributeMap;
|
||||
import common.net.util.ReferenceCountUtil;
|
||||
import common.net.util.internal.EmptyArrays;
|
||||
import common.net.util.internal.OneTimeTask;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.ThreadLocalRandom;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* A skeletal {@link Channel} implementation.
|
||||
*/
|
||||
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);
|
||||
|
||||
static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
|
||||
static final NotYetConnectedException NOT_YET_CONNECTED_EXCEPTION = new NotYetConnectedException();
|
||||
|
||||
static {
|
||||
CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
||||
NOT_YET_CONNECTED_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
||||
}
|
||||
|
||||
private MessageSizeEstimator.Handle estimatorHandle;
|
||||
|
||||
private final Channel parent;
|
||||
private final long hashCode = ThreadLocalRandom.current().nextLong();
|
||||
private final Unsafe unsafe;
|
||||
private final DefaultChannelPipeline pipeline;
|
||||
private final ChannelFuture succeededFuture = new SucceededChannelFuture(this, null);
|
||||
private final VoidChannelPromise voidPromise = new VoidChannelPromise(this, true);
|
||||
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
|
||||
private final CloseFuture closeFuture = new CloseFuture(this);
|
||||
|
||||
private volatile SocketAddress localAddress;
|
||||
private volatile SocketAddress remoteAddress;
|
||||
private volatile EventLoop eventLoop;
|
||||
private volatile boolean registered;
|
||||
|
||||
/** Cache for the string representation of this channel */
|
||||
private boolean strValActive;
|
||||
private String strVal;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param parent
|
||||
* the parent of this channel. {@code null} if there's no parent.
|
||||
*/
|
||||
protected AbstractChannel(Channel parent) {
|
||||
this.parent = parent;
|
||||
unsafe = newUnsafe();
|
||||
pipeline = new DefaultChannelPipeline(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
|
||||
return buf != null && buf.isWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel parent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return config().getAllocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
EventLoop eventLoop = this.eventLoop;
|
||||
if (eventLoop == null) {
|
||||
throw new IllegalStateException("channel not registered to an event loop");
|
||||
}
|
||||
return eventLoop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress localAddress() {
|
||||
SocketAddress localAddress = this.localAddress;
|
||||
if (localAddress == null) {
|
||||
try {
|
||||
this.localAddress = localAddress = unsafe().localAddress();
|
||||
} catch (Throwable t) {
|
||||
// Sometimes fails on a closed socket in Windows.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
protected void invalidateLocalAddress() {
|
||||
localAddress = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress remoteAddress() {
|
||||
SocketAddress remoteAddress = this.remoteAddress;
|
||||
if (remoteAddress == null) {
|
||||
try {
|
||||
this.remoteAddress = remoteAddress = unsafe().remoteAddress();
|
||||
} catch (Throwable t) {
|
||||
// Sometimes fails on a closed socket in Windows.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the stored remoteAddress
|
||||
*/
|
||||
protected void invalidateRemoteAddress() {
|
||||
remoteAddress = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress localAddress) {
|
||||
return pipeline.bind(localAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
return pipeline.connect(remoteAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||
return pipeline.connect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect() {
|
||||
return pipeline.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close() {
|
||||
return pipeline.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister() {
|
||||
return pipeline.deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel flush() {
|
||||
pipeline.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||
return pipeline.bind(localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
|
||||
return pipeline.connect(remoteAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||
return pipeline.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect(ChannelPromise promise) {
|
||||
return pipeline.disconnect(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close(ChannelPromise promise) {
|
||||
return pipeline.close(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister(ChannelPromise promise) {
|
||||
return pipeline.deregister(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel read() {
|
||||
pipeline.read();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object msg) {
|
||||
return pipeline.write(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object msg, ChannelPromise promise) {
|
||||
return pipeline.write(msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object msg) {
|
||||
return pipeline.writeAndFlush(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
||||
return pipeline.writeAndFlush(msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise newPromise() {
|
||||
return new DefaultChannelPromise(this);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public ChannelProgressivePromise newProgressivePromise() {
|
||||
// return new DefaultChannelProgressivePromise(this);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public ChannelFuture newSucceededFuture() {
|
||||
return succeededFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newFailedFuture(Throwable cause) {
|
||||
return new FailedChannelFuture(this, null, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture closeFuture() {
|
||||
return closeFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unsafe unsafe() {
|
||||
return unsafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractUnsafe} instance which will be used for the life-time of the {@link Channel}
|
||||
*/
|
||||
protected abstract AbstractUnsafe newUnsafe();
|
||||
|
||||
/**
|
||||
* Returns the ID of this channel.
|
||||
*/
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return (int) hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the specified object is identical
|
||||
* with this channel (i.e: {@code this == o}).
|
||||
*/
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int compareTo(Channel o) {
|
||||
if (this == o) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long ret = hashCode - o.hashCode();
|
||||
if (ret > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = System.identityHashCode(this) - System.identityHashCode(o);
|
||||
if (ret != 0) {
|
||||
return (int) ret;
|
||||
}
|
||||
|
||||
// Jackpot! - different objects with same hashes
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String} representation of this channel. The returned
|
||||
* string contains the {@linkplain #hashCode()} ID}, {@linkplain #localAddress() local address},
|
||||
* and {@linkplain #remoteAddress() remote address} of this channel for
|
||||
* easier identification.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
boolean active = isActive();
|
||||
if (strValActive == active && strVal != null) {
|
||||
return strVal;
|
||||
}
|
||||
|
||||
SocketAddress remoteAddr = remoteAddress();
|
||||
SocketAddress localAddr = localAddress();
|
||||
if (remoteAddr != null) {
|
||||
SocketAddress srcAddr;
|
||||
SocketAddress dstAddr;
|
||||
if (parent == null) {
|
||||
srcAddr = localAddr;
|
||||
dstAddr = remoteAddr;
|
||||
} else {
|
||||
srcAddr = remoteAddr;
|
||||
dstAddr = localAddr;
|
||||
}
|
||||
strVal = String.format("[id: 0x%08x, %s %s %s]", (int) hashCode, srcAddr, active? "=>" : ":>", dstAddr);
|
||||
} else if (localAddr != null) {
|
||||
strVal = String.format("[id: 0x%08x, %s]", (int) hashCode, localAddr);
|
||||
} else {
|
||||
strVal = String.format("[id: 0x%08x]", (int) hashCode);
|
||||
}
|
||||
|
||||
strValActive = active;
|
||||
return strVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChannelPromise voidPromise() {
|
||||
return voidPromise;
|
||||
}
|
||||
|
||||
final MessageSizeEstimator.Handle estimatorHandle() {
|
||||
if (estimatorHandle == null) {
|
||||
estimatorHandle = config().getMessageSizeEstimator().newHandle();
|
||||
}
|
||||
return estimatorHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Unsafe} implementation which sub-classes must extend and use.
|
||||
*/
|
||||
protected abstract class AbstractUnsafe implements Unsafe {
|
||||
|
||||
private ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this);
|
||||
private boolean inFlush0;
|
||||
|
||||
@Override
|
||||
public final ChannelOutboundBuffer outboundBuffer() {
|
||||
return outboundBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SocketAddress localAddress() {
|
||||
return localAddress0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SocketAddress remoteAddress() {
|
||||
return remoteAddress0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
|
||||
if (eventLoop == null) {
|
||||
throw new NullPointerException("eventLoop");
|
||||
}
|
||||
if (isRegistered()) {
|
||||
promise.setFailure(new IllegalStateException("registered to an event loop already"));
|
||||
return;
|
||||
}
|
||||
if (!isCompatible(eventLoop)) {
|
||||
promise.setFailure(
|
||||
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractChannel.this.eventLoop = eventLoop;
|
||||
|
||||
if (eventLoop.inEventLoop()) {
|
||||
register0(promise);
|
||||
} else {
|
||||
try {
|
||||
eventLoop.execute(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
register0(promise);
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
logger.warn(
|
||||
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
|
||||
AbstractChannel.this, t);
|
||||
closeForcibly();
|
||||
closeFuture.setClosed();
|
||||
safeSetFailure(promise, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void register0(ChannelPromise promise) {
|
||||
try {
|
||||
// check if the channel is still open as it could be closed in the mean time when the register
|
||||
// call was outside of the eventLoop
|
||||
if (!promise.setUncancellable() || !ensureOpen(promise)) {
|
||||
return;
|
||||
}
|
||||
doRegister();
|
||||
registered = true;
|
||||
safeSetSuccess(promise);
|
||||
pipeline.fireChannelRegistered();
|
||||
if (isActive()) {
|
||||
pipeline.fireChannelActive();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Close the channel directly to avoid FD leak.
|
||||
closeForcibly();
|
||||
closeFuture.setClosed();
|
||||
safeSetFailure(promise, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable() || !ensureOpen(promise)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See: https://github.com/netty/netty/issues/576
|
||||
if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
|
||||
Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
|
||||
localAddress instanceof InetSocketAddress &&
|
||||
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
|
||||
// Warn a user about the fact that a non-root user can't receive a
|
||||
// broadcast packet on *nix if the socket is bound on non-wildcard address.
|
||||
logger.warn(
|
||||
"A non-root user can't receive a broadcast packet if the socket " +
|
||||
"is not bound to a wildcard address; binding to a non-wildcard " +
|
||||
"address (" + localAddress + ") anyway as requested.");
|
||||
}
|
||||
|
||||
boolean wasActive = isActive();
|
||||
try {
|
||||
doBind(localAddress);
|
||||
} catch (Throwable t) {
|
||||
safeSetFailure(promise, t);
|
||||
closeIfClosed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasActive && isActive()) {
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
pipeline.fireChannelActive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
safeSetSuccess(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void disconnect(final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasActive = isActive();
|
||||
try {
|
||||
doDisconnect();
|
||||
} catch (Throwable t) {
|
||||
safeSetFailure(promise, t);
|
||||
closeIfClosed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (wasActive && !isActive()) {
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
pipeline.fireChannelInactive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
safeSetSuccess(promise);
|
||||
closeIfClosed(); // doDisconnect() might have closed the channel
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void close(final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inFlush0) {
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
close(promise);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (closeFuture.isDone()) {
|
||||
// Closed already.
|
||||
safeSetSuccess(promise);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasActive = isActive();
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
|
||||
|
||||
try {
|
||||
doClose();
|
||||
closeFuture.setClosed();
|
||||
safeSetSuccess(promise);
|
||||
} catch (Throwable t) {
|
||||
closeFuture.setClosed();
|
||||
safeSetFailure(promise, t);
|
||||
}
|
||||
|
||||
// Fail all the queued messages
|
||||
try {
|
||||
outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION);
|
||||
outboundBuffer.close(CLOSED_CHANNEL_EXCEPTION);
|
||||
} finally {
|
||||
|
||||
if (wasActive && !isActive()) {
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
pipeline.fireChannelInactive();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deregister(voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void closeForcibly() {
|
||||
try {
|
||||
doClose();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to close a channel.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void deregister(final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!registered) {
|
||||
safeSetSuccess(promise);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
doDeregister();
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unexpected exception occurred while deregistering a channel.", t);
|
||||
} finally {
|
||||
if (registered) {
|
||||
registered = false;
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
pipeline.fireChannelUnregistered();
|
||||
}
|
||||
});
|
||||
safeSetSuccess(promise);
|
||||
} else {
|
||||
// Some transports like local and AIO does not allow the deregistration of
|
||||
// an open channel. Their doDeregister() calls close(). Consequently,
|
||||
// close() calls deregister() again - no need to fire channelUnregistered.
|
||||
safeSetSuccess(promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void beginRead() {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
doBeginRead();
|
||||
} catch (final Exception e) {
|
||||
invokeLater(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
pipeline.fireExceptionCaught(e);
|
||||
}
|
||||
});
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object msg, ChannelPromise promise) {
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null) {
|
||||
// If the outboundBuffer is null we know the channel was closed and so
|
||||
// need to fail the future right away. If it is not null the handling of the rest
|
||||
// will be done in flush0()
|
||||
// See https://github.com/netty/netty/issues/2362
|
||||
safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);
|
||||
// release message now to prevent resource-leak
|
||||
ReferenceCountUtil.release(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
int size;
|
||||
try {
|
||||
msg = filterOutboundMessage(msg);
|
||||
size = estimatorHandle().size(msg);
|
||||
if (size < 0) {
|
||||
size = 0;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
safeSetFailure(promise, t);
|
||||
ReferenceCountUtil.release(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
outboundBuffer.addMessage(msg, size, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void flush() {
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
outboundBuffer.addFlush();
|
||||
flush0();
|
||||
}
|
||||
|
||||
protected void flush0() {
|
||||
if (inFlush0) {
|
||||
// Avoid re-entrance
|
||||
return;
|
||||
}
|
||||
|
||||
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null || outboundBuffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
inFlush0 = true;
|
||||
|
||||
// Mark all pending write requests as failure if the channel is inactive.
|
||||
if (!isActive()) {
|
||||
try {
|
||||
if (isOpen()) {
|
||||
outboundBuffer.failFlushed(NOT_YET_CONNECTED_EXCEPTION);
|
||||
} else {
|
||||
outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION);
|
||||
}
|
||||
} finally {
|
||||
inFlush0 = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
doWrite(outboundBuffer);
|
||||
} catch (Throwable t) {
|
||||
outboundBuffer.failFlushed(t);
|
||||
if (t instanceof IOException && config().isAutoClose()) {
|
||||
close(voidPromise());
|
||||
}
|
||||
} finally {
|
||||
inFlush0 = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ChannelPromise voidPromise() {
|
||||
return unsafeVoidPromise;
|
||||
}
|
||||
|
||||
protected final boolean ensureOpen(ChannelPromise promise) {
|
||||
if (isOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the specified {@code promise} as success. If the {@code promise} is done already, log a message.
|
||||
*/
|
||||
protected final void safeSetSuccess(ChannelPromise promise) {
|
||||
if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) {
|
||||
logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the specified {@code promise} as failure. If the {@code promise} is done already, log a message.
|
||||
*/
|
||||
protected final void safeSetFailure(ChannelPromise promise, Throwable cause) {
|
||||
if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) {
|
||||
logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void closeIfClosed() {
|
||||
if (isOpen()) {
|
||||
return;
|
||||
}
|
||||
close(voidPromise());
|
||||
}
|
||||
|
||||
private void invokeLater(Runnable task) {
|
||||
try {
|
||||
// This method is used by outbound operation implementations to trigger an inbound event later.
|
||||
// They do not trigger an inbound event immediately because an outbound operation might have been
|
||||
// triggered by another inbound event handler method. If fired immediately, the call stack
|
||||
// will look like this for example:
|
||||
//
|
||||
// handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
|
||||
// -> handlerA.ctx.close()
|
||||
// -> channel.unsafe.close()
|
||||
// -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
|
||||
//
|
||||
// which means the execution of two inbound handler methods of the same handler overlap undesirably.
|
||||
eventLoop().execute(task);
|
||||
} catch (RejectedExecutionException e) {
|
||||
logger.warn("Can't invoke task later as EventLoop rejected it", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the given {@link EventLoop} is compatible with this instance.
|
||||
*/
|
||||
protected abstract boolean isCompatible(EventLoop loop);
|
||||
|
||||
/**
|
||||
* Returns the {@link SocketAddress} which is bound locally.
|
||||
*/
|
||||
protected abstract SocketAddress localAddress0();
|
||||
|
||||
/**
|
||||
* Return the {@link SocketAddress} which the {@link Channel} is connected to.
|
||||
*/
|
||||
protected abstract SocketAddress remoteAddress0();
|
||||
|
||||
/**
|
||||
* Is called after the {@link Channel} is registered with its {@link EventLoop} as part of the register process.
|
||||
*
|
||||
* Sub-classes may override this method
|
||||
*/
|
||||
protected void doRegister() throws Exception {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the {@link Channel} to the {@link SocketAddress}
|
||||
*/
|
||||
protected abstract void doBind(SocketAddress localAddress) throws Exception;
|
||||
|
||||
/**
|
||||
* Disconnect this {@link Channel} from its remote peer
|
||||
*/
|
||||
protected abstract void doDisconnect() throws Exception;
|
||||
|
||||
/**
|
||||
* Close the {@link Channel}
|
||||
*/
|
||||
protected abstract void doClose() throws Exception;
|
||||
|
||||
/**
|
||||
* Deregister the {@link Channel} from its {@link EventLoop}.
|
||||
*
|
||||
* Sub-classes may override this method
|
||||
*/
|
||||
protected void doDeregister() throws Exception {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a read operation.
|
||||
*/
|
||||
protected abstract void doBeginRead() throws Exception;
|
||||
|
||||
/**
|
||||
* Flush the content of the given buffer to the remote peer.
|
||||
*/
|
||||
protected abstract void doWrite(ChannelOutboundBuffer in) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when a new message is added to a {@link ChannelOutboundBuffer} of this {@link AbstractChannel}, so that
|
||||
* the {@link Channel} implementation converts the message to another. (e.g. heap buffer -> direct buffer)
|
||||
*/
|
||||
protected Object filterOutboundMessage(Object msg) throws Exception {
|
||||
return msg;
|
||||
}
|
||||
|
||||
static final class CloseFuture extends DefaultChannelPromise {
|
||||
|
||||
CloseFuture(AbstractChannel ch) {
|
||||
super(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise setSuccess() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise setFailure(Throwable cause) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trySuccess() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryFailure(Throwable cause) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
boolean setClosed() {
|
||||
return super.trySuccess();
|
||||
}
|
||||
}
|
||||
}
|
1000
common/src/common/net/channel/AbstractChannelHandlerContext.java
Normal file
1000
common/src/common/net/channel/AbstractChannelHandlerContext.java
Normal file
File diff suppressed because it is too large
Load diff
83
common/src/common/net/channel/AbstractServerChannel.java
Normal file
83
common/src/common/net/channel/AbstractServerChannel.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* A skeletal server-side {@link Channel} implementation. A server-side
|
||||
* {@link Channel} does not allow the following operations:
|
||||
* <ul>
|
||||
* <li>{@link #connect(SocketAddress, ChannelPromise)}</li>
|
||||
* <li>{@link #disconnect(ChannelPromise)}</li>
|
||||
* <li>{@link #write(Object, ChannelPromise)}</li>
|
||||
* <li>{@link #flush()}</li>
|
||||
* <li>and the shortcut methods which calls the methods mentioned above
|
||||
* </ul>
|
||||
*/
|
||||
public abstract class AbstractServerChannel extends AbstractChannel implements ServerChannel {
|
||||
|
||||
private static final ChannelMetadata METADATA = new ChannelMetadata(false);
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
protected AbstractServerChannel() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelMetadata metadata() {
|
||||
return METADATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress remoteAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SocketAddress remoteAddress0() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDisconnect() throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractUnsafe newUnsafe() {
|
||||
return new DefaultServerUnsafe();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Object filterOutboundMessage(Object msg) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private final class DefaultServerUnsafe extends AbstractUnsafe {
|
||||
@Override
|
||||
public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||
safeSetFailure(promise, new UnsupportedOperationException());
|
||||
}
|
||||
}
|
||||
}
|
182
common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java
Normal file
182
common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
|
||||
/**
|
||||
* The {@link RecvByteBufAllocator} that automatically increases and
|
||||
* decreases the predicted buffer size on feed back.
|
||||
* <p>
|
||||
* It gradually increases the expected number of readable bytes if the previous
|
||||
* read fully filled the allocated buffer. It gradually decreases the expected
|
||||
* number of readable bytes if the read operation was not able to fill a certain
|
||||
* amount of the allocated buffer two times consecutively. Otherwise, it keeps
|
||||
* returning the same prediction.
|
||||
*/
|
||||
public class AdaptiveRecvByteBufAllocator implements RecvByteBufAllocator {
|
||||
|
||||
static final int DEFAULT_MINIMUM = 64;
|
||||
static final int DEFAULT_INITIAL = 1024;
|
||||
static final int DEFAULT_MAXIMUM = 65536;
|
||||
|
||||
private static final int INDEX_INCREMENT = 4;
|
||||
private static final int INDEX_DECREMENT = 1;
|
||||
|
||||
private static final int[] SIZE_TABLE;
|
||||
|
||||
static {
|
||||
List<Integer> sizeTable = new ArrayList<Integer>();
|
||||
for (int i = 16; i < 512; i += 16) {
|
||||
sizeTable.add(i);
|
||||
}
|
||||
|
||||
for (int i = 512; i > 0; i <<= 1) {
|
||||
sizeTable.add(i);
|
||||
}
|
||||
|
||||
SIZE_TABLE = new int[sizeTable.size()];
|
||||
for (int i = 0; i < SIZE_TABLE.length; i ++) {
|
||||
SIZE_TABLE[i] = sizeTable.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();
|
||||
|
||||
private static int getSizeTableIndex(final int size) {
|
||||
for (int low = 0, high = SIZE_TABLE.length - 1;;) {
|
||||
if (high < low) {
|
||||
return low;
|
||||
}
|
||||
if (high == low) {
|
||||
return high;
|
||||
}
|
||||
|
||||
int mid = low + high >>> 1;
|
||||
int a = SIZE_TABLE[mid];
|
||||
int b = SIZE_TABLE[mid + 1];
|
||||
if (size > b) {
|
||||
low = mid + 1;
|
||||
} else if (size < a) {
|
||||
high = mid - 1;
|
||||
} else if (size == a) {
|
||||
return mid;
|
||||
} else {
|
||||
return mid + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HandleImpl implements Handle {
|
||||
private final int minIndex;
|
||||
private final int maxIndex;
|
||||
private int index;
|
||||
private int nextReceiveBufferSize;
|
||||
private boolean decreaseNow;
|
||||
|
||||
HandleImpl(int minIndex, int maxIndex, int initial) {
|
||||
this.minIndex = minIndex;
|
||||
this.maxIndex = maxIndex;
|
||||
|
||||
index = getSizeTableIndex(initial);
|
||||
nextReceiveBufferSize = SIZE_TABLE[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf allocate(ByteBufAllocator alloc) {
|
||||
return alloc.ioBuffer(nextReceiveBufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int guess() {
|
||||
return nextReceiveBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void record(int actualReadBytes) {
|
||||
if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {
|
||||
if (decreaseNow) {
|
||||
index = Math.max(index - INDEX_DECREMENT, minIndex);
|
||||
nextReceiveBufferSize = SIZE_TABLE[index];
|
||||
decreaseNow = false;
|
||||
} else {
|
||||
decreaseNow = true;
|
||||
}
|
||||
} else if (actualReadBytes >= nextReceiveBufferSize) {
|
||||
index = Math.min(index + INDEX_INCREMENT, maxIndex);
|
||||
nextReceiveBufferSize = SIZE_TABLE[index];
|
||||
decreaseNow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final int minIndex;
|
||||
private final int maxIndex;
|
||||
private final int initial;
|
||||
|
||||
/**
|
||||
* Creates a new predictor with the default parameters. With the default
|
||||
* parameters, the expected buffer size starts from {@code 1024}, does not
|
||||
* go down below {@code 64}, and does not go up above {@code 65536}.
|
||||
*/
|
||||
private AdaptiveRecvByteBufAllocator() {
|
||||
this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new predictor with the specified parameters.
|
||||
*
|
||||
* @param minimum the inclusive lower bound of the expected buffer size
|
||||
* @param initial the initial buffer size when no feed back was received
|
||||
* @param maximum the inclusive upper bound of the expected buffer size
|
||||
*/
|
||||
public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
|
||||
if (minimum <= 0) {
|
||||
throw new IllegalArgumentException("minimum: " + minimum);
|
||||
}
|
||||
if (initial < minimum) {
|
||||
throw new IllegalArgumentException("initial: " + initial);
|
||||
}
|
||||
if (maximum < initial) {
|
||||
throw new IllegalArgumentException("maximum: " + maximum);
|
||||
}
|
||||
|
||||
int minIndex = getSizeTableIndex(minimum);
|
||||
if (SIZE_TABLE[minIndex] < minimum) {
|
||||
this.minIndex = minIndex + 1;
|
||||
} else {
|
||||
this.minIndex = minIndex;
|
||||
}
|
||||
|
||||
int maxIndex = getSizeTableIndex(maximum);
|
||||
if (SIZE_TABLE[maxIndex] > maximum) {
|
||||
this.maxIndex = maxIndex - 1;
|
||||
} else {
|
||||
this.maxIndex = maxIndex;
|
||||
}
|
||||
|
||||
this.initial = initial;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handle newHandle() {
|
||||
return new HandleImpl(minIndex, maxIndex, initial);
|
||||
}
|
||||
}
|
511
common/src/common/net/channel/Channel.java
Normal file
511
common/src/common/net/channel/Channel.java
Normal file
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.util.AttributeMap;
|
||||
|
||||
|
||||
/**
|
||||
* A nexus to a network socket or a component which is capable of I/O
|
||||
* operations such as read, write, connect, and bind.
|
||||
* <p>
|
||||
* A channel provides a user:
|
||||
* <ul>
|
||||
* <li>the current state of the channel (e.g. is it open? is it connected?),</li>
|
||||
* <li>the {@linkplain ChannelConfig configuration parameters} of the channel (e.g. receive buffer size),</li>
|
||||
* <li>the I/O operations that the channel supports (e.g. read, write, connect, and bind), and</li>
|
||||
* <li>the {@link ChannelPipeline} which handles all I/O events and requests
|
||||
* associated with the channel.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>All I/O operations are asynchronous.</h3>
|
||||
* <p>
|
||||
* All I/O operations in Netty are asynchronous. It means any I/O calls will
|
||||
* return immediately with no guarantee that the requested I/O operation has
|
||||
* been completed at the end of the call. Instead, you will be returned with
|
||||
* a {@link ChannelFuture} instance which will notify you when the requested I/O
|
||||
* operation has succeeded, failed, or canceled.
|
||||
*
|
||||
* <h3>Channels are hierarchical</h3>
|
||||
* <p>
|
||||
* A {@link Channel} can have a {@linkplain #parent() parent} depending on
|
||||
* how it was created. For instance, a {@link SocketChannel}, that was accepted
|
||||
* by {@link ServerSocketChannel}, will return the {@link ServerSocketChannel}
|
||||
* as its parent on {@link #parent()}.
|
||||
* <p>
|
||||
* The semantics of the hierarchical structure depends on the transport
|
||||
* implementation where the {@link Channel} belongs to. For example, you could
|
||||
* write a new {@link Channel} implementation that creates the sub-channels that
|
||||
* share one socket connection, as <a href="http://beepcore.org/">BEEP</a> and
|
||||
* <a href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a> do.
|
||||
*
|
||||
* <h3>Downcast to access transport-specific operations</h3>
|
||||
* <p>
|
||||
* Some transports exposes additional operations that is specific to the
|
||||
* transport. Down-cast the {@link Channel} to sub-type to invoke such
|
||||
* operations. For example, with the old I/O datagram transport, multicast
|
||||
* join / leave operations are provided by {@link DatagramChannel}.
|
||||
*
|
||||
* <h3>Release resources</h3>
|
||||
* <p>
|
||||
* It is important to call {@link #close()} or {@link #close(ChannelPromise)} to release all
|
||||
* resources once you are done with the {@link Channel}. This ensures all resources are
|
||||
* released in a proper way, i.e. filehandles.
|
||||
*/
|
||||
public interface Channel extends AttributeMap, Comparable<Channel> {
|
||||
|
||||
/**
|
||||
* Return the {@link EventLoop} this {@link Channel} was registered too.
|
||||
*/
|
||||
EventLoop eventLoop();
|
||||
|
||||
/**
|
||||
* Returns the parent of this channel.
|
||||
*
|
||||
* @return the parent channel.
|
||||
* {@code null} if this channel does not have a parent channel.
|
||||
*/
|
||||
Channel parent();
|
||||
|
||||
/**
|
||||
* Returns the configuration of this channel.
|
||||
*/
|
||||
ChannelConfig config();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the {@link Channel} is open an may get active later
|
||||
*/
|
||||
boolean isOpen();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}.
|
||||
*/
|
||||
boolean isRegistered();
|
||||
|
||||
/**
|
||||
* Return {@code true} if the {@link Channel} is active and so connected.
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}.
|
||||
*/
|
||||
ChannelMetadata metadata();
|
||||
|
||||
/**
|
||||
* Returns the local address where this channel is bound to. The returned
|
||||
* {@link SocketAddress} is supposed to be down-cast into more concrete
|
||||
* type such as {@link InetSocketAddress} to retrieve the detailed
|
||||
* information.
|
||||
*
|
||||
* @return the local address of this channel.
|
||||
* {@code null} if this channel is not bound.
|
||||
*/
|
||||
SocketAddress localAddress();
|
||||
|
||||
/**
|
||||
* Returns the remote address where this channel is connected to. The
|
||||
* returned {@link SocketAddress} is supposed to be down-cast into more
|
||||
* concrete type such as {@link InetSocketAddress} to retrieve the detailed
|
||||
* information.
|
||||
*
|
||||
* @return the remote address of this channel.
|
||||
* {@code null} if this channel is not connected.
|
||||
* If this channel is not connected but it can receive messages
|
||||
* from arbitrary remote addresses (e.g. {@link DatagramChannel},
|
||||
* use {@link DatagramPacket#recipient()} to determine
|
||||
* the origination of the received message as this method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
SocketAddress remoteAddress();
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelFuture} which will be notified when this
|
||||
* channel is closed. This method always returns the same future instance.
|
||||
*/
|
||||
ChannelFuture closeFuture();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the I/O thread will perform the
|
||||
* requested write operation immediately. Any write requests made when
|
||||
* this method returns {@code false} are queued until the I/O thread is
|
||||
* ready to process the queued write requests.
|
||||
*/
|
||||
boolean isWritable();
|
||||
|
||||
/**
|
||||
* Returns an <em>internal-use-only</em> object that provides unsafe operations.
|
||||
*/
|
||||
Unsafe unsafe();
|
||||
|
||||
/**
|
||||
* Return the assigned {@link ChannelPipeline}
|
||||
*/
|
||||
ChannelPipeline pipeline();
|
||||
|
||||
/**
|
||||
* Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s.
|
||||
*/
|
||||
ByteBufAllocator alloc();
|
||||
|
||||
/**
|
||||
* Return a new {@link ChannelPromise}.
|
||||
*/
|
||||
ChannelPromise newPromise();
|
||||
|
||||
/**
|
||||
* Return an new {@link ChannelProgressivePromise}
|
||||
*/
|
||||
// ChannelProgressivePromise newProgressivePromise();
|
||||
|
||||
/**
|
||||
* Create a new {@link ChannelFuture} which is marked as succeeded already. So {@link ChannelFuture#isSuccess()}
|
||||
* will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also
|
||||
* every call of blocking methods will just return without blocking.
|
||||
*/
|
||||
ChannelFuture newSucceededFuture();
|
||||
|
||||
/**
|
||||
* Create a new {@link ChannelFuture} which is marked as failed already. So {@link ChannelFuture#isSuccess()}
|
||||
* will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also
|
||||
* every call of blocking methods will just return without blocking.
|
||||
*/
|
||||
ChannelFuture newFailedFuture(Throwable cause);
|
||||
|
||||
/**
|
||||
* Return a special ChannelPromise which can be reused for different operations.
|
||||
* <p>
|
||||
* It's only supported to use
|
||||
* it for {@link Channel#write(Object, ChannelPromise)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Be aware that the returned {@link ChannelPromise} will not support most operations and should only be used
|
||||
* if you want to save an object allocation for every write operation. You will not be able to detect if the
|
||||
* operation was complete, only if it failed as the implementation will call
|
||||
* {@link ChannelPipeline#fireExceptionCaught(Throwable)} in this case.
|
||||
* </p>
|
||||
* <strong>Be aware this is an expert feature and should be used with care!</strong>
|
||||
*/
|
||||
ChannelPromise voidPromise();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect();
|
||||
|
||||
/**
|
||||
* Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close();
|
||||
|
||||
/**
|
||||
* Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*
|
||||
*/
|
||||
ChannelFuture deregister();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelFuture} will be notified.
|
||||
*
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified and also returned.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture deregister(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to Read data from the {@link Channel} into the first inbound buffer, triggers an
|
||||
* {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was
|
||||
* read, and triggers a
|
||||
* {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the
|
||||
* handler can decide to continue reading. If there's a pending read operation already, this method does nothing.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#read(ChannelHandlerContext)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
Channel read();
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link Channel} through the {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg);
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link Channel} through the {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to flush all pending messages.
|
||||
*/
|
||||
Channel flush();
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg);
|
||||
|
||||
/**
|
||||
* <em>Unsafe</em> operations that should <em>never</em> be called from user-code. These methods
|
||||
* are only provided to implement the actual transport, and must be invoked from an I/O thread except for the
|
||||
* following methods:
|
||||
* <ul>
|
||||
* <li>{@link #localAddress()}</li>
|
||||
* <li>{@link #remoteAddress()}</li>
|
||||
* <li>{@link #closeForcibly()}</li>
|
||||
* <li>{@link #register(EventLoop, ChannelPromise)}</li>
|
||||
* <li>{@link #voidPromise()}</li>
|
||||
* </ul>
|
||||
*/
|
||||
interface Unsafe {
|
||||
/**
|
||||
* Return the {@link SocketAddress} to which is bound local or
|
||||
* {@code null} if none.
|
||||
*/
|
||||
SocketAddress localAddress();
|
||||
|
||||
/**
|
||||
* Return the {@link SocketAddress} to which is bound remote or
|
||||
* {@code null} if none is bound yet.
|
||||
*/
|
||||
SocketAddress remoteAddress();
|
||||
|
||||
/**
|
||||
* Register the {@link Channel} of the {@link ChannelPromise} with the {@link EventLoop} and notify
|
||||
* the {@link ChannelFuture} once the registration was complete.
|
||||
*/
|
||||
void register(EventLoop eventLoop, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Bind the {@link SocketAddress} to the {@link Channel} of the {@link ChannelPromise} and notify
|
||||
* it once its done.
|
||||
*/
|
||||
void bind(SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Connect the {@link Channel} of the given {@link ChannelFuture} with the given remote {@link SocketAddress}.
|
||||
* If a specific local {@link SocketAddress} should be used it need to be given as argument. Otherwise just
|
||||
* pass {@code null} to it.
|
||||
*
|
||||
* The {@link ChannelPromise} will get notified once the connect operation was complete.
|
||||
*/
|
||||
void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Disconnect the {@link Channel} of the {@link ChannelFuture} and notify the {@link ChannelPromise} once the
|
||||
* operation was complete.
|
||||
*/
|
||||
void disconnect(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Close the {@link Channel} of the {@link ChannelPromise} and notify the {@link ChannelPromise} once the
|
||||
* operation was complete.
|
||||
*/
|
||||
void close(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Closes the {@link Channel} immediately without firing any events. Probably only useful
|
||||
* when registration attempt failed.
|
||||
*/
|
||||
void closeForcibly();
|
||||
|
||||
/**
|
||||
* Deregister the {@link Channel} of the {@link ChannelPromise} from {@link EventLoop} and notify the
|
||||
* {@link ChannelPromise} once the operation was complete.
|
||||
*/
|
||||
void deregister(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Schedules a read operation that fills the inbound buffer of the first {@link ChannelInboundHandler} in the
|
||||
* {@link ChannelPipeline}. If there's already a pending read operation, this method does nothing.
|
||||
*/
|
||||
void beginRead();
|
||||
|
||||
/**
|
||||
* Schedules a write operation.
|
||||
*/
|
||||
void write(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Flush out all write operations scheduled via {@link #write(Object, ChannelPromise)}.
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* Return a special ChannelPromise which can be reused and passed to the operations in {@link Unsafe}.
|
||||
* It will never be notified of a success or error and so is only a placeholder for operations
|
||||
* that take a {@link ChannelPromise} as argument but for which you not want to get notified.
|
||||
*/
|
||||
ChannelPromise voidPromise();
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelOutboundBuffer} of the {@link Channel} where the pending write requests are stored.
|
||||
*/
|
||||
ChannelOutboundBuffer outboundBuffer();
|
||||
}
|
||||
}
|
249
common/src/common/net/channel/ChannelConfig.java
Normal file
249
common/src/common/net/channel/ChannelConfig.java
Normal file
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
|
||||
/**
|
||||
* A set of configuration properties of a {@link Channel}.
|
||||
* <p>
|
||||
* Please down-cast to more specific configuration type such as
|
||||
* {@link SocketChannelConfig} or use {@link #setOptions(Map)} to set the
|
||||
* transport-specific properties:
|
||||
* <pre>
|
||||
* {@link Channel} ch = ...;
|
||||
* {@link SocketChannelConfig} cfg = <strong>({@link SocketChannelConfig}) ch.getConfig();</strong>
|
||||
* cfg.setTcpNoDelay(false);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Option map</h3>
|
||||
*
|
||||
* An option map property is a dynamic write-only property which allows
|
||||
* the configuration of a {@link Channel} without down-casting its associated
|
||||
* {@link ChannelConfig}. To update an option map, please call {@link #setOptions(Map)}.
|
||||
* <p>
|
||||
* All {@link ChannelConfig} has the following options:
|
||||
*
|
||||
* <table border="1" cellspacing="0" cellpadding="6">
|
||||
* <tr>
|
||||
* <th>Name</th><th>Associated setter method</th>
|
||||
* </tr><tr>
|
||||
* <td>{@link ChannelOption#CONNECT_TIMEOUT_MILLIS}</td><td>{@link #setConnectTimeoutMillis(int)}</td>
|
||||
* </tr><tr>
|
||||
* <td>{@link ChannelOption#WRITE_SPIN_COUNT}</td><td>{@link #setWriteSpinCount(int)}</td>
|
||||
* </tr><tr>
|
||||
* <td>{@link ChannelOption#ALLOCATOR}</td><td>{@link #setAllocator(ByteBufAllocator)}</td>
|
||||
* </tr><tr>
|
||||
* <td>{@link ChannelOption#AUTO_READ}</td><td>{@link #setAutoRead(boolean)}</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* <p>
|
||||
* More options are available in the sub-types of {@link ChannelConfig}. For
|
||||
* example, you can configure the parameters which are specific to a TCP/IP
|
||||
* socket as explained in {@link SocketChannelConfig}.
|
||||
*/
|
||||
public interface ChannelConfig {
|
||||
|
||||
/**
|
||||
* Return all set {@link ChannelOption}'s.
|
||||
*/
|
||||
Map<ChannelOption<?>, Object> getOptions();
|
||||
|
||||
/**
|
||||
* Sets the configuration properties from the specified {@link Map}.
|
||||
*/
|
||||
boolean setOptions(Map<ChannelOption<?>, ?> options);
|
||||
|
||||
/**
|
||||
* Return the value of the given {@link ChannelOption}
|
||||
*/
|
||||
<T> T getOption(ChannelOption<T> option);
|
||||
|
||||
/**
|
||||
* Sets a configuration property with the specified name and value.
|
||||
* To override this method properly, you must call the super class:
|
||||
* <pre>
|
||||
* public boolean setOption(ChannelOption<T> option, T value) {
|
||||
* if (super.setOption(option, value)) {
|
||||
* return true;
|
||||
* }
|
||||
*
|
||||
* if (option.equals(additionalOption)) {
|
||||
* ....
|
||||
* return true;
|
||||
* }
|
||||
*
|
||||
* return false;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return {@code true} if and only if the property has been set
|
||||
*/
|
||||
<T> boolean setOption(ChannelOption<T> option, T value);
|
||||
|
||||
/**
|
||||
* Returns the connect timeout of the channel in milliseconds. If the
|
||||
* {@link Channel} does not support connect operation, this property is not
|
||||
* used at all, and therefore will be ignored.
|
||||
*
|
||||
* @return the connect timeout in milliseconds. {@code 0} if disabled.
|
||||
*/
|
||||
int getConnectTimeoutMillis();
|
||||
|
||||
/**
|
||||
* Sets the connect timeout of the channel in milliseconds. If the
|
||||
* {@link Channel} does not support connect operation, this property is not
|
||||
* used at all, and therefore will be ignored.
|
||||
*
|
||||
* @param connectTimeoutMillis the connect timeout in milliseconds.
|
||||
* {@code 0} to disable.
|
||||
*/
|
||||
ChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
|
||||
|
||||
/**
|
||||
* Returns the maximum number of messages to read per read loop.
|
||||
* a {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object) channelRead()} event.
|
||||
* If this value is greater than 1, an event loop might attempt to read multiple times to procure multiple messages.
|
||||
*/
|
||||
int getMaxMessagesPerRead();
|
||||
|
||||
/**
|
||||
* Sets the maximum number of messages to read per read loop.
|
||||
* If this value is greater than 1, an event loop might attempt to read multiple times to procure multiple messages.
|
||||
*/
|
||||
ChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
|
||||
|
||||
/**
|
||||
* Returns the maximum loop count for a write operation until
|
||||
* {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value.
|
||||
* It is similar to what a spin lock is used for in concurrency programming.
|
||||
* It improves memory utilization and write throughput depending on
|
||||
* the platform that JVM runs on. The default value is {@code 16}.
|
||||
*/
|
||||
int getWriteSpinCount();
|
||||
|
||||
/**
|
||||
* Sets the maximum loop count for a write operation until
|
||||
* {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value.
|
||||
* It is similar to what a spin lock is used for in concurrency programming.
|
||||
* It improves memory utilization and write throughput depending on
|
||||
* the platform that JVM runs on. The default value is {@code 16}.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if the specified value is {@code 0} or less than {@code 0}
|
||||
*/
|
||||
ChannelConfig setWriteSpinCount(int writeSpinCount);
|
||||
|
||||
/**
|
||||
* Returns {@link ByteBufAllocator} which is used for the channel
|
||||
* to allocate buffers.
|
||||
*/
|
||||
ByteBufAllocator getAllocator();
|
||||
|
||||
/**
|
||||
* Set the {@link ByteBufAllocator} which is used for the channel
|
||||
* to allocate buffers.
|
||||
*/
|
||||
ChannelConfig setAllocator(ByteBufAllocator allocator);
|
||||
|
||||
/**
|
||||
* Returns {@link RecvByteBufAllocator} which is used for the channel
|
||||
* to allocate receive buffers.
|
||||
*/
|
||||
RecvByteBufAllocator getRecvByteBufAllocator();
|
||||
|
||||
/**
|
||||
* Set the {@link ByteBufAllocator} which is used for the channel
|
||||
* to allocate receive buffers.
|
||||
*/
|
||||
ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if {@link ChannelHandlerContext#read()} will be invoked automatically so that
|
||||
* a user application doesn't need to call it at all. The default value is {@code true}.
|
||||
*/
|
||||
boolean isAutoRead();
|
||||
|
||||
/**
|
||||
* Sets if {@link ChannelHandlerContext#read()} will be invoked automatically so that a user application doesn't
|
||||
* need to call it at all. The default value is {@code true}.
|
||||
*/
|
||||
ChannelConfig setAutoRead(boolean autoRead);
|
||||
|
||||
/**
|
||||
* @deprecated From version 5.0, {@link Channel} will not be closed on write failure.
|
||||
*
|
||||
* Returns {@code true} if and only if the {@link Channel} will be closed automatically and immediately on
|
||||
* write failure. The default is {@code false}.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isAutoClose();
|
||||
|
||||
/**
|
||||
* @deprecated From version 5.0, {@link Channel} will not be closed on write failure.
|
||||
*
|
||||
* Sets whether the {@link Channel} should be closed automatically and immediately on write faillure.
|
||||
* The default is {@code false}.
|
||||
*/
|
||||
@Deprecated
|
||||
ChannelConfig setAutoClose(boolean autoClose);
|
||||
|
||||
/**
|
||||
* Returns the high water mark of the write buffer. If the number of bytes
|
||||
* queued in the write buffer exceeds this value, {@link Channel#isWritable()}
|
||||
* will start to return {@code false}.
|
||||
*/
|
||||
int getWriteBufferHighWaterMark();
|
||||
|
||||
/**
|
||||
* Sets the high water mark of the write buffer. If the number of bytes
|
||||
* queued in the write buffer exceeds this value, {@link Channel#isWritable()}
|
||||
* will start to return {@code false}.
|
||||
*/
|
||||
ChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||
|
||||
/**
|
||||
* Returns the low water mark of the write buffer. Once the number of bytes
|
||||
* queued in the write buffer exceeded the
|
||||
* {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then
|
||||
* dropped down below this value, {@link Channel#isWritable()} will start to return
|
||||
* {@code true} again.
|
||||
*/
|
||||
int getWriteBufferLowWaterMark();
|
||||
|
||||
/**
|
||||
* Sets the low water mark of the write buffer. Once the number of bytes
|
||||
* queued in the write buffer exceeded the
|
||||
* {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then
|
||||
* dropped down below this value, {@link Channel#isWritable()} will start to return
|
||||
* {@code true} again.
|
||||
*/
|
||||
ChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||
|
||||
/**
|
||||
* Returns {@link MessageSizeEstimator} which is used for the channel
|
||||
* to detect the size of a message.
|
||||
*/
|
||||
MessageSizeEstimator getMessageSizeEstimator();
|
||||
|
||||
/**
|
||||
* Set the {@link ByteBufAllocator} which is used for the channel
|
||||
* to detect the size of a message.
|
||||
*/
|
||||
ChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
|
||||
}
|
51
common/src/common/net/channel/ChannelException.java
Normal file
51
common/src/common/net/channel/ChannelException.java
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} which is thrown when an I/O operation fails.
|
||||
*/
|
||||
public class ChannelException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 2908618315971075004L;
|
||||
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public ChannelException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public ChannelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public ChannelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public ChannelException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
192
common/src/common/net/channel/ChannelFuture.java
Normal file
192
common/src/common/net/channel/ChannelFuture.java
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
|
||||
|
||||
/**
|
||||
* The result of an asynchronous {@link Channel} I/O operation.
|
||||
* <p>
|
||||
* All I/O operations in Netty are asynchronous. It means any I/O calls will
|
||||
* return immediately with no guarantee that the requested I/O operation has
|
||||
* been completed at the end of the call. Instead, you will be returned with
|
||||
* a {@link ChannelFuture} instance which gives you the information about the
|
||||
* result or status of the I/O operation.
|
||||
* <p>
|
||||
* A {@link ChannelFuture} is either <em>uncompleted</em> or <em>completed</em>.
|
||||
* When an I/O operation begins, a new future object is created. The new future
|
||||
* is uncompleted initially - it is neither succeeded, failed, nor cancelled
|
||||
* because the I/O operation is not finished yet. If the I/O operation is
|
||||
* finished either successfully, with failure, or by cancellation, the future is
|
||||
* marked as completed with more specific information, such as the cause of the
|
||||
* failure. Please note that even failure and cancellation belong to the
|
||||
* completed state.
|
||||
* <pre>
|
||||
* +---------------------------+
|
||||
* | Completed successfully |
|
||||
* +---------------------------+
|
||||
* +----> isDone() = <b>true</b> |
|
||||
* +--------------------------+ | | isSuccess() = <b>true</b> |
|
||||
* | Uncompleted | | +===========================+
|
||||
* +--------------------------+ | | Completed with failure |
|
||||
* | isDone() = <b>false</b> | | +---------------------------+
|
||||
* | isSuccess() = false |----+----> isDone() = <b>true</b> |
|
||||
* | isCancelled() = false | | | cause() = <b>non-null</b> |
|
||||
* | cause() = null | | +===========================+
|
||||
* +--------------------------+ | | Completed by cancellation |
|
||||
* | +---------------------------+
|
||||
* +----> isDone() = <b>true</b> |
|
||||
* | isCancelled() = <b>true</b> |
|
||||
* +---------------------------+
|
||||
* </pre>
|
||||
*
|
||||
* Various methods are provided to let you check if the I/O operation has been
|
||||
* completed, wait for the completion, and retrieve the result of the I/O
|
||||
* operation. It also allows you to add {@link ChannelFutureListener}s so you
|
||||
* can get notified when the I/O operation is completed.
|
||||
*
|
||||
* <h3>Prefer {@link #addListener(GenericFutureListener)} to {@link #await()}</h3>
|
||||
*
|
||||
* It is recommended to prefer {@link #addListener(GenericFutureListener)} to
|
||||
* {@link #await()} wherever possible to get notified when an I/O operation is
|
||||
* done and to do any follow-up tasks.
|
||||
* <p>
|
||||
* {@link #addListener(GenericFutureListener)} is non-blocking. It simply adds
|
||||
* the specified {@link ChannelFutureListener} to the {@link ChannelFuture}, and
|
||||
* I/O thread will notify the listeners when the I/O operation associated with
|
||||
* the future is done. {@link ChannelFutureListener} yields the best
|
||||
* performance and resource utilization because it does not block at all, but
|
||||
* it could be tricky to implement a sequential logic if you are not used to
|
||||
* event-driven programming.
|
||||
* <p>
|
||||
* By contrast, {@link #await()} is a blocking operation. Once called, the
|
||||
* caller thread blocks until the operation is done. It is easier to implement
|
||||
* a sequential logic with {@link #await()}, but the caller thread blocks
|
||||
* unnecessarily until the I/O operation is done and there's relatively
|
||||
* expensive cost of inter-thread notification. Moreover, there's a chance of
|
||||
* dead lock in a particular circumstance, which is described below.
|
||||
*
|
||||
* <h3>Do not call {@link #await()} inside {@link ChannelHandler}</h3>
|
||||
* <p>
|
||||
* The event handler methods in {@link ChannelHandler} is usually called by
|
||||
* an I/O thread. If {@link #await()} is called by an event handler
|
||||
* method, which is called by the I/O thread, the I/O operation it is waiting
|
||||
* for might never be complete because {@link #await()} can block the I/O
|
||||
* operation it is waiting for, which is a dead lock.
|
||||
* <pre>
|
||||
* // BAD - NEVER DO THIS
|
||||
* {@code @Override}
|
||||
* public void channelRead({@link ChannelHandlerContext} ctx, GoodByeMessage msg) {
|
||||
* {@link ChannelFuture} future = ctx.channel().close();
|
||||
* future.awaitUninterruptibly();
|
||||
* // Perform post-closure operation
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* // GOOD
|
||||
* {@code @Override}
|
||||
* public void channelRead({@link ChannelHandlerContext} ctx, GoodByeMessage msg) {
|
||||
* {@link ChannelFuture} future = ctx.channel().close();
|
||||
* future.addListener(new {@link ChannelFutureListener}() {
|
||||
* public void operationComplete({@link ChannelFuture} future) {
|
||||
* // Perform post-closure operation
|
||||
* // ...
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* In spite of the disadvantages mentioned above, there are certainly the cases
|
||||
* where it is more convenient to call {@link #await()}. In such a case, please
|
||||
* make sure you do not call {@link #await()} in an I/O thread. Otherwise,
|
||||
* {@link BlockingOperationException} will be raised to prevent a dead lock.
|
||||
*
|
||||
* <h3>Do not confuse I/O timeout and await timeout</h3>
|
||||
*
|
||||
* The timeout value you specify with {@link #await(long)},
|
||||
* {@link #await(long, TimeUnit)}, {@link #awaitUninterruptibly(long)}, or
|
||||
* {@link #awaitUninterruptibly(long, TimeUnit)} are not related with I/O
|
||||
* timeout at all. If an I/O operation times out, the future will be marked as
|
||||
* 'completed with failure,' as depicted in the diagram above. For example,
|
||||
* connect timeout should be configured via a transport-specific option:
|
||||
* <pre>
|
||||
* // BAD - NEVER DO THIS
|
||||
* {@link Bootstrap} b = ...;
|
||||
* {@link ChannelFuture} f = b.connect(...);
|
||||
* f.awaitUninterruptibly(10, TimeUnit.SECONDS);
|
||||
* if (f.isCancelled()) {
|
||||
* // Connection attempt cancelled by user
|
||||
* } else if (!f.isSuccess()) {
|
||||
* // You might get a NullPointerException here because the future
|
||||
* // might not be completed yet.
|
||||
* f.cause().printStackTrace();
|
||||
* } else {
|
||||
* // Connection established successfully
|
||||
* }
|
||||
*
|
||||
* // GOOD
|
||||
* {@link Bootstrap} b = ...;
|
||||
* // Configure the connect timeout option.
|
||||
* <b>b.option({@link ChannelOption}.CONNECT_TIMEOUT_MILLIS, 10000);</b>
|
||||
* {@link ChannelFuture} f = b.connect(...);
|
||||
* f.awaitUninterruptibly();
|
||||
*
|
||||
* // Now we are sure the future is completed.
|
||||
* assert f.isDone();
|
||||
*
|
||||
* if (f.isCancelled()) {
|
||||
* // Connection attempt cancelled by user
|
||||
* } else if (!f.isSuccess()) {
|
||||
* f.cause().printStackTrace();
|
||||
* } else {
|
||||
* // Connection established successfully
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface ChannelFuture extends Future<Void> {
|
||||
|
||||
/**
|
||||
* Returns a channel where the I/O operation associated with this
|
||||
* future takes place.
|
||||
*/
|
||||
Channel channel();
|
||||
|
||||
@Override
|
||||
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelFuture sync() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelFuture syncUninterruptibly();
|
||||
|
||||
@Override
|
||||
ChannelFuture await() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelFuture awaitUninterruptibly();
|
||||
}
|
74
common/src/common/net/channel/ChannelFutureListener.java
Normal file
74
common/src/common/net/channel/ChannelFutureListener.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
|
||||
|
||||
/**
|
||||
* Listens to the result of a {@link ChannelFuture}. The result of the
|
||||
* asynchronous {@link Channel} I/O operation is notified once this listener
|
||||
* is added by calling {@link ChannelFuture#addListener(GenericFutureListener)}.
|
||||
*
|
||||
* <h3>Return the control to the caller quickly</h3>
|
||||
*
|
||||
* {@link #operationComplete(Future)} is directly called by an I/O
|
||||
* thread. Therefore, performing a time consuming task or a blocking operation
|
||||
* in the handler method can cause an unexpected pause during I/O. If you need
|
||||
* to perform a blocking operation on I/O completion, try to execute the
|
||||
* operation in a different thread using a thread pool.
|
||||
*/
|
||||
public interface ChannelFutureListener extends GenericFutureListener<ChannelFuture> {
|
||||
|
||||
/**
|
||||
* A {@link ChannelFutureListener} that closes the {@link Channel} which is
|
||||
* associated with the specified {@link ChannelFuture}.
|
||||
*/
|
||||
ChannelFutureListener CLOSE = new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
future.channel().close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link ChannelFutureListener} that closes the {@link Channel} when the
|
||||
* operation ended up with a failure or cancellation rather than a success.
|
||||
*/
|
||||
ChannelFutureListener CLOSE_ON_FAILURE = new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (!future.isSuccess()) {
|
||||
future.channel().close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link ChannelFutureListener} that forwards the {@link Throwable} of the {@link ChannelFuture} into the
|
||||
* {@link ChannelPipeline}. This mimics the old behavior of Netty 3.
|
||||
*/
|
||||
ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (!future.isSuccess()) {
|
||||
future.channel().pipeline().fireExceptionCaught(future.cause());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Just a type alias
|
||||
}
|
211
common/src/common/net/channel/ChannelHandler.java
Normal file
211
common/src/common/net/channel/ChannelHandler.java
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Handles or intercepts a {@link ChannelInboundInvoker} or {@link ChannelOutboundInvoker} operation, and forwards it
|
||||
* to the next handler in a {@link ChannelPipeline}.
|
||||
*
|
||||
* <h3>Sub-types</h3>
|
||||
* <p>
|
||||
* {@link ChannelHandler} itself does not provide many methods. To handle a
|
||||
* a {@link ChannelInboundInvoker} or {@link ChannelOutboundInvoker} operation
|
||||
* you need to implement its sub-interfaces. There are many different sub-interfaces
|
||||
* which handles inbound and outbound operations.
|
||||
*
|
||||
* But the most useful for developers may be:
|
||||
* <ul>
|
||||
* <li>{@link ChannelInboundHandlerAdapter} handles and intercepts inbound operations</li>
|
||||
* <li>{@link ChannelOutboundHandlerAdapter} handles and intercepts outbound operations</li>
|
||||
* </ul>
|
||||
*
|
||||
* You will also find more detailed explanation from the documentation of
|
||||
* each sub-interface on how an event is interpreted when it goes upstream and
|
||||
* downstream respectively.
|
||||
*
|
||||
* <h3>The context object</h3>
|
||||
* <p>
|
||||
* A {@link ChannelHandler} is provided with a {@link ChannelHandlerContext}
|
||||
* object. A {@link ChannelHandler} is supposed to interact with the
|
||||
* {@link ChannelPipeline} it belongs to via a context object. Using the
|
||||
* context object, the {@link ChannelHandler} can pass events upstream or
|
||||
* downstream, modify the pipeline dynamically, or store the information
|
||||
* (using {@link AttributeKey}s) which is specific to the handler.
|
||||
*
|
||||
* <h3>State management</h3>
|
||||
*
|
||||
* A {@link ChannelHandler} often needs to store some stateful information.
|
||||
* The simplest and recommended approach is to use member variables:
|
||||
* <pre>
|
||||
* public interface Message {
|
||||
* // your methods here
|
||||
* }
|
||||
*
|
||||
* public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
|
||||
*
|
||||
* <b>private boolean loggedIn;</b>
|
||||
*
|
||||
* {@code @Override}
|
||||
* public void channelRead0({@link ChannelHandlerContext} ctx, Message message) {
|
||||
* {@link Channel} ch = e.getChannel();
|
||||
* if (message instanceof LoginMessage) {
|
||||
* authenticate((LoginMessage) message);
|
||||
* <b>loggedIn = true;</b>
|
||||
* } else (message instanceof GetDataMessage) {
|
||||
* if (<b>loggedIn</b>) {
|
||||
* ch.write(fetchSecret((GetDataMessage) message));
|
||||
* } else {
|
||||
* fail();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
* Because the handler instance has a state variable which is dedicated to
|
||||
* one connection, you have to create a new handler instance for each new
|
||||
* channel to avoid a race condition where a unauthenticated client can get
|
||||
* the confidential information:
|
||||
* <pre>
|
||||
* // Create a new handler instance per channel.
|
||||
* // See {@link ChannelInitializer#initChannel(Channel)}.
|
||||
* public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
|
||||
* {@code @Override}
|
||||
* public void initChannel({@link Channel} channel) {
|
||||
* channel.pipeline().addLast("handler", <b>new DataServerHandler()</b>);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* <h4>Using {@link AttributeKey}</h4>
|
||||
*
|
||||
* Although it's recommended to use member variables to store the state of a
|
||||
* handler, for some reason you might not want to create many handler instances.
|
||||
* In such a case, you can use {@link AttributeKey}s which is provided by
|
||||
* {@link ChannelHandlerContext}:
|
||||
* <pre>
|
||||
* public interface Message {
|
||||
* // your methods here
|
||||
* }
|
||||
*
|
||||
* {@code @Sharable}
|
||||
* public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
|
||||
* private final {@link AttributeKey}<{@link Boolean}> auth =
|
||||
* {@link AttributeKey#valueOf(String) AttributeKey.valueOf("auth")};
|
||||
*
|
||||
* {@code @Override}
|
||||
* public void channelRead({@link ChannelHandlerContext} ctx, Message message) {
|
||||
* {@link Attribute}<{@link Boolean}> attr = ctx.attr(auth);
|
||||
* {@link Channel} ch = ctx.channel();
|
||||
* if (message instanceof LoginMessage) {
|
||||
* authenticate((LoginMessage) o);
|
||||
* <b>attr.set(true)</b>;
|
||||
* } else (message instanceof GetDataMessage) {
|
||||
* if (<b>Boolean.TRUE.equals(attr.get())</b>) {
|
||||
* ch.write(fetchSecret((GetDataMessage) o));
|
||||
* } else {
|
||||
* fail();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
* Now that the state of the handler isattached to the {@link ChannelHandlerContext}, you can add the
|
||||
* same handler instance to different pipelines:
|
||||
* <pre>
|
||||
* public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
|
||||
*
|
||||
* private static final DataServerHandler <b>SHARED</b> = new DataServerHandler();
|
||||
*
|
||||
* {@code @Override}
|
||||
* public void initChannel({@link Channel} channel) {
|
||||
* channel.pipeline().addLast("handler", <b>SHARED</b>);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <h4>The {@code @Sharable} annotation</h4>
|
||||
* <p>
|
||||
* In the example above which used an {@link AttributeKey},
|
||||
* you might have noticed the {@code @Sharable} annotation.
|
||||
* <p>
|
||||
* If a {@link ChannelHandler} is annotated with the {@code @Sharable}
|
||||
* annotation, it means you can create an instance of the handler just once and
|
||||
* add it to one or more {@link ChannelPipeline}s multiple times without
|
||||
* a race condition.
|
||||
* <p>
|
||||
* If this annotation is not specified, you have to create a new handler
|
||||
* instance every time you add it to a pipeline because it has unshared state
|
||||
* such as member variables.
|
||||
* <p>
|
||||
* This annotation is provided for documentation purpose, just like
|
||||
* <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
|
||||
*
|
||||
* <h3>Additional resources worth reading</h3>
|
||||
* <p>
|
||||
* Please refer to the {@link ChannelHandler}, and
|
||||
* {@link ChannelPipeline} to find out more about inbound and outbound operations,
|
||||
* what fundamental differences they have, how they flow in a pipeline, and how to handle
|
||||
* the operation in your application.
|
||||
*/
|
||||
public interface ChannelHandler {
|
||||
|
||||
/**
|
||||
* Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
|
||||
*/
|
||||
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
|
||||
* anymore.
|
||||
*/
|
||||
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Gets called if a {@link Throwable} was thrown.
|
||||
*/
|
||||
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
|
||||
|
||||
/**
|
||||
* Indicates that the same instance of the annotated {@link ChannelHandler}
|
||||
* can be added to one or more {@link ChannelPipeline}s multiple times
|
||||
* without a race condition.
|
||||
* <p>
|
||||
* If this annotation is not specified, you have to create a new handler
|
||||
* instance every time you add it to a pipeline because it has unshared
|
||||
* state such as member variables.
|
||||
* <p>
|
||||
* This annotation is provided for documentation purpose, just like
|
||||
* <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Sharable {
|
||||
// no value
|
||||
}
|
||||
}
|
80
common/src/common/net/channel/ChannelHandlerAdapter.java
Normal file
80
common/src/common/net/channel/ChannelHandlerAdapter.java
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import common.net.util.internal.InternalThreadLocalMap;
|
||||
|
||||
/**
|
||||
* Skelton implementation of a {@link ChannelHandler}.
|
||||
*/
|
||||
public abstract class ChannelHandlerAdapter implements ChannelHandler {
|
||||
|
||||
// Not using volatile because it's used only for a sanity check.
|
||||
boolean added;
|
||||
|
||||
/**
|
||||
* Return {@code true} if the implementation is {@link Sharable} and so can be added
|
||||
* to different {@link ChannelPipeline}s.
|
||||
*/
|
||||
public boolean isSharable() {
|
||||
/**
|
||||
* Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a
|
||||
* {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different
|
||||
* {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of
|
||||
* {@link Thread}s are quite limited anyway.
|
||||
*
|
||||
* See <a href="See https://github.com/netty/netty/issues/2289">#2289</a>.
|
||||
*/
|
||||
Class<?> clazz = getClass();
|
||||
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
|
||||
Boolean sharable = cache.get(clazz);
|
||||
if (sharable == null) {
|
||||
sharable = clazz.isAnnotationPresent(Sharable.class);
|
||||
cache.put(clazz, sharable);
|
||||
}
|
||||
return sharable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing by default, sub-classes may override this method.
|
||||
*/
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing by default, sub-classes may override this method.
|
||||
*/
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward
|
||||
* to the next {@link ChannelHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
489
common/src/common/net/channel/ChannelHandlerContext.java
Normal file
489
common/src/common/net/channel/ChannelHandlerContext.java
Normal file
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.util.AttributeMap;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
|
||||
/**
|
||||
* Enables a {@link ChannelHandler} to interact with its {@link ChannelPipeline}
|
||||
* and other handlers. A handler can notify the next {@link ChannelHandler} in the {@link ChannelPipeline},
|
||||
* modify the {@link ChannelPipeline} it belongs to dynamically.
|
||||
*
|
||||
* <h3>Notify</h3>
|
||||
*
|
||||
* You can notify the closest handler in the
|
||||
* same {@link ChannelPipeline} by calling one of the various methods provided here.
|
||||
* Please refer to {@link ChannelPipeline} to understand how an event flows.
|
||||
*
|
||||
* <h3>Modifying a pipeline</h3>
|
||||
*
|
||||
* You can get the {@link ChannelPipeline} your handler belongs to by calling
|
||||
* {@link #pipeline()}. A non-trivial application could insert, remove, or
|
||||
* replace handlers in the pipeline dynamically at runtime.
|
||||
*
|
||||
* <h3>Retrieving for later use</h3>
|
||||
*
|
||||
* You can keep the {@link ChannelHandlerContext} for later use, such as
|
||||
* triggering an event outside the handler methods, even from a different thread.
|
||||
* <pre>
|
||||
* public class MyHandler extends {@link ChannelDuplexHandler} {
|
||||
*
|
||||
* <b>private {@link ChannelHandlerContext} ctx;</b>
|
||||
*
|
||||
* public void beforeAdd({@link ChannelHandlerContext} ctx) {
|
||||
* <b>this.ctx = ctx;</b>
|
||||
* }
|
||||
*
|
||||
* public void login(String username, password) {
|
||||
* ctx.write(new LoginMessage(username, password));
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Storing stateful information</h3>
|
||||
*
|
||||
* {@link #attr(AttributeKey)} allow you to
|
||||
* store and access stateful information that is related with a handler and its
|
||||
* context. Please refer to {@link ChannelHandler} to learn various recommended
|
||||
* ways to manage stateful information.
|
||||
*
|
||||
* <h3>A handler can have more than one context</h3>
|
||||
*
|
||||
* Please note that a {@link ChannelHandler} instance can be added to more than
|
||||
* one {@link ChannelPipeline}. It means a single {@link ChannelHandler}
|
||||
* instance can have more than one {@link ChannelHandlerContext} and therefore
|
||||
* the single instance can be invoked with different
|
||||
* {@link ChannelHandlerContext}s if it is added to one or more
|
||||
* {@link ChannelPipeline}s more than once.
|
||||
* <p>
|
||||
* For example, the following handler will have as many independent {@link AttributeKey}s
|
||||
* as how many times it is added to pipelines, regardless if it is added to the
|
||||
* same pipeline multiple times or added to different pipelines multiple times:
|
||||
* <pre>
|
||||
* public class FactorialHandler extends {@link ChannelInboundHandlerAdapter}<{@link Integer}> {
|
||||
*
|
||||
* private final {@link AttributeKey}<{@link Integer}> counter =
|
||||
* new {@link AttributeKey}<{@link Integer}>("counter");
|
||||
*
|
||||
* // This handler will receive a sequence of increasing integers starting
|
||||
* // from 1.
|
||||
* {@code @Override}
|
||||
* public void channelRead({@link ChannelHandlerContext} ctx, {@link Integer} integer) {
|
||||
* {@link Attribute}<{@link Integer}> attr = ctx.getAttr(counter);
|
||||
* Integer a = ctx.getAttr(counter).get();
|
||||
*
|
||||
* if (a == null) {
|
||||
* a = 1;
|
||||
* }
|
||||
*
|
||||
* attr.set(a * integer));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Different context objects are given to "f1", "f2", "f3", and "f4" even if
|
||||
* // they refer to the same handler instance. Because the FactorialHandler
|
||||
* // stores its state in a context object (as an (using an {@link AttributeKey}), the factorial is
|
||||
* // calculated correctly 4 times once the two pipelines (p1 and p2) are active.
|
||||
* FactorialHandler fh = new FactorialHandler();
|
||||
*
|
||||
* {@link ChannelPipeline} p1 = {@link Channels}.pipeline();
|
||||
* p1.addLast("f1", fh);
|
||||
* p1.addLast("f2", fh);
|
||||
*
|
||||
* {@link ChannelPipeline} p2 = {@link Channels}.pipeline();
|
||||
* p2.addLast("f3", fh);
|
||||
* p2.addLast("f4", fh);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Additional resources worth reading</h3>
|
||||
* <p>
|
||||
* Please refer to the {@link ChannelHandler}, and
|
||||
* {@link ChannelPipeline} to find out more about inbound and outbound operations,
|
||||
* what fundamental differences they have, how they flow in a pipeline, and how to handle
|
||||
* the operation in your application.
|
||||
*/
|
||||
public interface ChannelHandlerContext
|
||||
extends AttributeMap {
|
||||
|
||||
/**
|
||||
* Return the {@link Channel} which is bound to the {@link ChannelHandlerContext}.
|
||||
*/
|
||||
Channel channel();
|
||||
|
||||
/**
|
||||
* The {@link EventExecutor} that is used to dispatch the events. This can also be used to directly
|
||||
* submit tasks that get executed in the event loop. For more information please refer to the
|
||||
* {@link EventExecutor} javadoc.
|
||||
*/
|
||||
EventExecutor executor();
|
||||
|
||||
/**
|
||||
* The unique name of the {@link ChannelHandlerContext}.The name was used when then {@link ChannelHandler}
|
||||
* was added to the {@link ChannelPipeline}. This name can also be used to access the registered
|
||||
* {@link ChannelHandler} from the {@link ChannelPipeline}.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The {@link ChannelHandler} that is bound this {@link ChannelHandlerContext}.
|
||||
*/
|
||||
ChannelHandler handler();
|
||||
|
||||
/**
|
||||
* Return {@code true} if the {@link ChannelHandler} which belongs to this {@link ChannelHandler} was removed
|
||||
* from the {@link ChannelPipeline}. Note that this method is only meant to be called from with in the
|
||||
* {@link EventLoop}.
|
||||
*/
|
||||
boolean isRemoved();
|
||||
|
||||
/**
|
||||
* A {@link Channel} was registered to its {@link EventLoop}.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelRegistered(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelRegistered();
|
||||
|
||||
/**
|
||||
* A {@link Channel} was unregistered from its {@link EventLoop}.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelUnregistered(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelUnregistered();
|
||||
|
||||
/**
|
||||
* A {@link Channel} is active now, which means it is connected.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelActive();
|
||||
|
||||
/**
|
||||
* A {@link Channel} is inactive now, which means it is closed.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelInactive(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelInactive();
|
||||
|
||||
/**
|
||||
* A {@link Channel} received an {@link Throwable} in one of its inbound operations.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#exceptionCaught(ChannelHandlerContext, Throwable)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireExceptionCaught(Throwable cause);
|
||||
|
||||
/**
|
||||
* A {@link Channel} received an user defined event.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireUserEventTriggered(Object event);
|
||||
|
||||
/**
|
||||
* A {@link Channel} received a message.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelRead(Object msg);
|
||||
|
||||
/**
|
||||
* Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)}
|
||||
* event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelReadComplete();
|
||||
|
||||
/**
|
||||
* Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)}
|
||||
* event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*/
|
||||
ChannelHandlerContext fireChannelWritabilityChanged();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect();
|
||||
|
||||
/**
|
||||
* Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close();
|
||||
|
||||
/**
|
||||
* Request to deregister from the previous assigned {@link EventExecutor} and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*
|
||||
*/
|
||||
ChannelFuture deregister();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelFuture} will be notified.
|
||||
*
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified and also returned.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to deregister from the previous assigned {@link EventExecutor} and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture deregister(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to Read data from the {@link Channel} into the first inbound buffer, triggers an
|
||||
* {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was
|
||||
* read, and triggers a
|
||||
* {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the
|
||||
* handler can decide to continue reading. If there's a pending read operation already, this method does nothing.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#read(ChannelHandlerContext)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelHandlerContext read();
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link ChannelHandlerContext} through the {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg);
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link ChannelHandlerContext} through the {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to flush all pending messages via this ChannelOutboundInvoker.
|
||||
*/
|
||||
ChannelHandlerContext flush();
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg);
|
||||
|
||||
/**
|
||||
* Return the assigned {@link ChannelPipeline}
|
||||
*/
|
||||
ChannelPipeline pipeline();
|
||||
|
||||
/**
|
||||
* Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s.
|
||||
*/
|
||||
ByteBufAllocator alloc();
|
||||
|
||||
/**
|
||||
* Return a new {@link ChannelPromise}.
|
||||
*/
|
||||
ChannelPromise newPromise();
|
||||
|
||||
/**
|
||||
* Return an new {@link ChannelProgressivePromise}
|
||||
*/
|
||||
// ChannelProgressivePromise newProgressivePromise();
|
||||
|
||||
/**
|
||||
* Create a new {@link ChannelFuture} which is marked as succeeded already. So {@link ChannelFuture#isSuccess()}
|
||||
* will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also
|
||||
* every call of blocking methods will just return without blocking.
|
||||
*/
|
||||
ChannelFuture newSucceededFuture();
|
||||
|
||||
/**
|
||||
* Create a new {@link ChannelFuture} which is marked as failed already. So {@link ChannelFuture#isSuccess()}
|
||||
* will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also
|
||||
* every call of blocking methods will just return without blocking.
|
||||
*/
|
||||
ChannelFuture newFailedFuture(Throwable cause);
|
||||
|
||||
/**
|
||||
* Return a special ChannelPromise which can be reused for different operations.
|
||||
* <p>
|
||||
* It's only supported to use
|
||||
* it for {@link ChannelHandlerContext#write(Object, ChannelPromise)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Be aware that the returned {@link ChannelPromise} will not support most operations and should only be used
|
||||
* if you want to save an object allocation for every write operation. You will not be able to detect if the
|
||||
* operation was complete, only if it failed as the implementation will call
|
||||
* {@link ChannelPipeline#fireExceptionCaught(Throwable)} in this case.
|
||||
* </p>
|
||||
* <strong>Be aware this is an expert feature and should be used with care!</strong>
|
||||
*/
|
||||
ChannelPromise voidPromise();
|
||||
|
||||
}
|
74
common/src/common/net/channel/ChannelInboundHandler.java
Normal file
74
common/src/common/net/channel/ChannelInboundHandler.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* {@link ChannelHandler} which adds callbacks for state changes. This allows the user
|
||||
* to hook in to state changes easily.
|
||||
*/
|
||||
public interface ChannelInboundHandler extends ChannelHandler {
|
||||
|
||||
/**
|
||||
* The {@link Channel} of the {@link ChannelHandlerContext} was registered with its {@link EventLoop}
|
||||
*/
|
||||
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* The {@link Channel} of the {@link ChannelHandlerContext} was unregistered from its {@link EventLoop}
|
||||
*/
|
||||
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* The {@link Channel} of the {@link ChannelHandlerContext} is now active
|
||||
*/
|
||||
void channelActive(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* The {@link Channel} of the {@link ChannelHandlerContext} was registered is now inactive and reached its
|
||||
* end of lifetime.
|
||||
*/
|
||||
void channelInactive(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when the current {@link Channel} has read a message from the peer.
|
||||
*/
|
||||
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when the last message read by the current read operation has been consumed by
|
||||
* {@link #channelRead(ChannelHandlerContext, Object)}. If {@link ChannelOption#AUTO_READ} is off, no further
|
||||
* attempt to read an inbound data from the current {@link Channel} will be made until
|
||||
* {@link ChannelHandlerContext#read()} is called.
|
||||
*/
|
||||
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Gets called if an user event was triggered.
|
||||
*/
|
||||
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
|
||||
|
||||
/**
|
||||
* Gets called once the writable state of a {@link Channel} changed. You can check the state with
|
||||
* {@link Channel#isWritable()}.
|
||||
*/
|
||||
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Gets called if a {@link Throwable} was thrown.
|
||||
*/
|
||||
@Override
|
||||
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
|
||||
}
|
133
common/src/common/net/channel/ChannelInboundHandlerAdapter.java
Normal file
133
common/src/common/net/channel/ChannelInboundHandlerAdapter.java
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ChannelInboundHandler} implementations which provide
|
||||
* implementations of all of their methods.
|
||||
*
|
||||
* <p>
|
||||
* This implementation just forward the operation to the next {@link ChannelHandler} in the
|
||||
* {@link ChannelPipeline}. Sub-classes may override a method implementation to change this.
|
||||
* </p>
|
||||
* <p>
|
||||
* Be aware that messages are not released after the {@link #channelRead(ChannelHandlerContext, Object)}
|
||||
* method returns automatically. If you are looking for a {@link ChannelInboundHandler} implementation that
|
||||
* releases the received messages automatically, please see {@link SimpleChannelInboundHandler}.
|
||||
* </p>
|
||||
*/
|
||||
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelRegistered()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelRegistered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelUnregistered()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelUnregistered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelActive()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelInactive()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelReadComplete()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelReadComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireChannelWritabilityChanged()} to forward
|
||||
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.fireChannelWritabilityChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward
|
||||
* to the next {@link ChannelHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
|
||||
throws Exception {
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
82
common/src/common/net/channel/ChannelInitializer.java
Normal file
82
common/src/common/net/channel/ChannelInitializer.java
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.channel.ChannelHandler.Sharable;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* A special {@link ChannelInboundHandler} which offers an easy way to initialize a {@link Channel} once it was
|
||||
* registered to its {@link EventLoop}.
|
||||
*
|
||||
* Implementations are most often used in the context of {@link Bootstrap#handler(ChannelHandler)} ,
|
||||
* {@link ServerBootstrap#handler(ChannelHandler)} and {@link ServerBootstrap#childHandler(ChannelHandler)} to
|
||||
* setup the {@link ChannelPipeline} of a {@link Channel}.
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* public class MyChannelInitializer extends {@link ChannelInitializer} {
|
||||
* public void initChannel({@link Channel} channel) {
|
||||
* channel.pipeline().addLast("myHandler", new MyHandler());
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* {@link ServerBootstrap} bootstrap = ...;
|
||||
* ...
|
||||
* bootstrap.childHandler(new MyChannelInitializer());
|
||||
* ...
|
||||
* </pre>
|
||||
* Be aware that this class is marked as {@link Sharable} and so the implementation must be safe to be re-used.
|
||||
*
|
||||
* @param <C> A sub-type of {@link Channel}
|
||||
*/
|
||||
@Sharable
|
||||
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
|
||||
|
||||
/**
|
||||
* This method will be called once the {@link Channel} was registered. After the method returns this instance
|
||||
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
|
||||
*
|
||||
* @param ch the {@link Channel} which was registered.
|
||||
* @throws Exception is thrown if an error occurs. In that case the {@link Channel} will be closed.
|
||||
*/
|
||||
protected abstract void initChannel(C ch) throws Exception;
|
||||
|
||||
@Override
|
||||
|
||||
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
boolean success = false;
|
||||
try {
|
||||
initChannel((C) ctx.channel());
|
||||
pipeline.remove(this);
|
||||
ctx.fireChannelRegistered();
|
||||
success = true;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);
|
||||
} finally {
|
||||
if (pipeline.context(this) != null) {
|
||||
pipeline.remove(this);
|
||||
}
|
||||
if (!success) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
common/src/common/net/channel/ChannelMetadata.java
Normal file
44
common/src/common/net/channel/ChannelMetadata.java
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* Represents the properties of a {@link Channel} implementation.
|
||||
*/
|
||||
public final class ChannelMetadata {
|
||||
|
||||
private final boolean hasDisconnect;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param hasDisconnect {@code true} if and only if the channel has the {@code disconnect()} operation
|
||||
* that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)}
|
||||
* again, such as UDP/IP.
|
||||
*/
|
||||
public ChannelMetadata(boolean hasDisconnect) {
|
||||
this.hasDisconnect = hasDisconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the channel has the {@code disconnect()} operation
|
||||
* that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)} again,
|
||||
* such as UDP/IP.
|
||||
*/
|
||||
public boolean hasDisconnect() {
|
||||
return hasDisconnect;
|
||||
}
|
||||
}
|
111
common/src/common/net/channel/ChannelOption.java
Normal file
111
common/src/common/net/channel/ChannelOption.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.util.UniqueName;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* A {@link ChannelOption} allows to configure a {@link ChannelConfig} in a type-safe
|
||||
* way. Which {@link ChannelOption} is supported depends on the actual implementation
|
||||
* of {@link ChannelConfig} and may depend on the nature of the transport it belongs
|
||||
* to.
|
||||
*
|
||||
* @param <T> the type of the value which is valid for the {@link ChannelOption}
|
||||
*/
|
||||
|
||||
public class ChannelOption<T> extends UniqueName {
|
||||
|
||||
private static final ConcurrentMap<String, Boolean> names = PlatformDependent.newConcurrentHashMap();
|
||||
|
||||
public static final ChannelOption<ByteBufAllocator> ALLOCATOR = valueOf("ALLOCATOR");
|
||||
public static final ChannelOption<RecvByteBufAllocator> RCVBUF_ALLOCATOR = valueOf("RCVBUF_ALLOCATOR");
|
||||
public static final ChannelOption<MessageSizeEstimator> MESSAGE_SIZE_ESTIMATOR = valueOf("MESSAGE_SIZE_ESTIMATOR");
|
||||
|
||||
public static final ChannelOption<Integer> CONNECT_TIMEOUT_MILLIS = valueOf("CONNECT_TIMEOUT_MILLIS");
|
||||
public static final ChannelOption<Integer> MAX_MESSAGES_PER_READ = valueOf("MAX_MESSAGES_PER_READ");
|
||||
public static final ChannelOption<Integer> WRITE_SPIN_COUNT = valueOf("WRITE_SPIN_COUNT");
|
||||
public static final ChannelOption<Integer> WRITE_BUFFER_HIGH_WATER_MARK = valueOf("WRITE_BUFFER_HIGH_WATER_MARK");
|
||||
public static final ChannelOption<Integer> WRITE_BUFFER_LOW_WATER_MARK = valueOf("WRITE_BUFFER_LOW_WATER_MARK");
|
||||
|
||||
public static final ChannelOption<Boolean> ALLOW_HALF_CLOSURE = valueOf("ALLOW_HALF_CLOSURE");
|
||||
public static final ChannelOption<Boolean> AUTO_READ = valueOf("AUTO_READ");
|
||||
|
||||
/**
|
||||
* @deprecated From version 5.0, {@link Channel} will not be closed on write failure.
|
||||
*
|
||||
* {@code true} if and only if the {@link Channel} is closed automatically and immediately on write failure.
|
||||
* The default is {@code false}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final ChannelOption<Boolean> AUTO_CLOSE = valueOf("AUTO_CLOSE");
|
||||
|
||||
public static final ChannelOption<Boolean> SO_BROADCAST = valueOf("SO_BROADCAST");
|
||||
public static final ChannelOption<Boolean> SO_KEEPALIVE = valueOf("SO_KEEPALIVE");
|
||||
public static final ChannelOption<Integer> SO_SNDBUF = valueOf("SO_SNDBUF");
|
||||
public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");
|
||||
public static final ChannelOption<Boolean> SO_REUSEADDR = valueOf("SO_REUSEADDR");
|
||||
public static final ChannelOption<Integer> SO_LINGER = valueOf("SO_LINGER");
|
||||
public static final ChannelOption<Integer> SO_BACKLOG = valueOf("SO_BACKLOG");
|
||||
public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");
|
||||
|
||||
public static final ChannelOption<Integer> IP_TOS = valueOf("IP_TOS");
|
||||
public static final ChannelOption<InetAddress> IP_MULTICAST_ADDR = valueOf("IP_MULTICAST_ADDR");
|
||||
public static final ChannelOption<NetworkInterface> IP_MULTICAST_IF = valueOf("IP_MULTICAST_IF");
|
||||
public static final ChannelOption<Integer> IP_MULTICAST_TTL = valueOf("IP_MULTICAST_TTL");
|
||||
public static final ChannelOption<Boolean> IP_MULTICAST_LOOP_DISABLED = valueOf("IP_MULTICAST_LOOP_DISABLED");
|
||||
|
||||
public static final ChannelOption<Boolean> TCP_NODELAY = valueOf("TCP_NODELAY");
|
||||
|
||||
@Deprecated
|
||||
public static final ChannelOption<Long> AIO_READ_TIMEOUT = valueOf("AIO_READ_TIMEOUT");
|
||||
@Deprecated
|
||||
public static final ChannelOption<Long> AIO_WRITE_TIMEOUT = valueOf("AIO_WRITE_TIMEOUT");
|
||||
|
||||
@Deprecated
|
||||
public static final ChannelOption<Boolean> DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION =
|
||||
valueOf("DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION");
|
||||
|
||||
/**
|
||||
* Creates a new {@link ChannelOption} with the specified {@code name}.
|
||||
*/
|
||||
public static <T> ChannelOption<T> valueOf(String name) {
|
||||
return new ChannelOption<T>(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #valueOf(String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected ChannelOption(String name) {
|
||||
super(names, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the value which is set for the {@link ChannelOption}. Sub-classes
|
||||
* may override this for special checks.
|
||||
*/
|
||||
public void validate(T value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
}
|
||||
}
|
650
common/src/common/net/channel/ChannelOutboundBuffer.java
Normal file
650
common/src/common/net/channel/ChannelOutboundBuffer.java
Normal file
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
import common.net.buffer.Unpooled;
|
||||
import common.net.util.Recycler;
|
||||
import common.net.util.ReferenceCountUtil;
|
||||
import common.net.util.Recycler.Handle;
|
||||
import common.net.util.concurrent.FastThreadLocal;
|
||||
import common.net.util.internal.InternalThreadLocalMap;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* (Transport implementors only) an internal data structure used by {@link AbstractChannel} to store its pending
|
||||
* outbound write requests.
|
||||
*
|
||||
* All the methods should only be called by the {@link EventLoop} of the {@link Channel}.
|
||||
*/
|
||||
public final class ChannelOutboundBuffer {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelOutboundBuffer.class);
|
||||
|
||||
private static final FastThreadLocal<ByteBuffer[]> NIO_BUFFERS = new FastThreadLocal<ByteBuffer[]>() {
|
||||
@Override
|
||||
protected ByteBuffer[] initialValue() throws Exception {
|
||||
return new ByteBuffer[1024];
|
||||
}
|
||||
};
|
||||
|
||||
private final Channel channel;
|
||||
|
||||
// Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
|
||||
//
|
||||
// The Entry that is the first in the linked-list structure that was flushed
|
||||
private Entry flushedEntry;
|
||||
// The Entry which is the first unflushed in the linked-list structure
|
||||
private Entry unflushedEntry;
|
||||
// The Entry which represents the tail of the buffer
|
||||
private Entry tailEntry;
|
||||
// The number of flushed entries that are not written yet
|
||||
private int flushed;
|
||||
|
||||
private int nioBufferCount;
|
||||
private long nioBufferSize;
|
||||
|
||||
private boolean inFail;
|
||||
|
||||
private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER;
|
||||
|
||||
|
||||
private volatile long totalPendingSize;
|
||||
|
||||
private static final AtomicIntegerFieldUpdater<ChannelOutboundBuffer> WRITABLE_UPDATER;
|
||||
|
||||
|
||||
private volatile int writable = 1;
|
||||
|
||||
static {
|
||||
AtomicIntegerFieldUpdater<ChannelOutboundBuffer> writableUpdater =
|
||||
PlatformDependent.newAtomicIntegerFieldUpdater(ChannelOutboundBuffer.class, "writable");
|
||||
if (writableUpdater == null) {
|
||||
writableUpdater = AtomicIntegerFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "writable");
|
||||
}
|
||||
WRITABLE_UPDATER = writableUpdater;
|
||||
|
||||
AtomicLongFieldUpdater<ChannelOutboundBuffer> pendingSizeUpdater =
|
||||
PlatformDependent.newAtomicLongFieldUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
|
||||
if (pendingSizeUpdater == null) {
|
||||
pendingSizeUpdater = AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
|
||||
}
|
||||
TOTAL_PENDING_SIZE_UPDATER = pendingSizeUpdater;
|
||||
}
|
||||
|
||||
ChannelOutboundBuffer(AbstractChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once
|
||||
* the message was written.
|
||||
*/
|
||||
public void addMessage(Object msg, int size, ChannelPromise promise) {
|
||||
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
|
||||
if (tailEntry == null) {
|
||||
flushedEntry = null;
|
||||
tailEntry = entry;
|
||||
} else {
|
||||
Entry tail = tailEntry;
|
||||
tail.next = entry;
|
||||
tailEntry = entry;
|
||||
}
|
||||
if (unflushedEntry == null) {
|
||||
unflushedEntry = entry;
|
||||
}
|
||||
|
||||
// increment pending bytes after adding message to the unflushed arrays.
|
||||
// See https://github.com/netty/netty/issues/1619
|
||||
incrementPendingOutboundBytes(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a flush to this {@link ChannelOutboundBuffer}. This means all previous added messages are marked as flushed
|
||||
* and so you will be able to handle them.
|
||||
*/
|
||||
public void addFlush() {
|
||||
// There is no need to process all entries if there was already a flush before and no new messages
|
||||
// where added in the meantime.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2577
|
||||
Entry entry = unflushedEntry;
|
||||
if (entry != null) {
|
||||
if (flushedEntry == null) {
|
||||
// there is no flushedEntry yet, so start with the entry
|
||||
flushedEntry = entry;
|
||||
}
|
||||
do {
|
||||
flushed ++;
|
||||
if (!entry.promise.setUncancellable()) {
|
||||
// Was cancelled so make sure we free up memory and notify about the freed bytes
|
||||
int pending = entry.cancel();
|
||||
decrementPendingOutboundBytes(pending);
|
||||
}
|
||||
entry = entry.next;
|
||||
} while (entry != null);
|
||||
|
||||
// All flushed so reset unflushedEntry
|
||||
unflushedEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the pending bytes which will be written at some point.
|
||||
* This method is thread-safe!
|
||||
*/
|
||||
void incrementPendingOutboundBytes(long size) {
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
|
||||
if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
|
||||
if (WRITABLE_UPDATER.compareAndSet(this, 1, 0)) {
|
||||
channel.pipeline().fireChannelWritabilityChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the pending bytes which will be written at some point.
|
||||
* This method is thread-safe!
|
||||
*/
|
||||
void decrementPendingOutboundBytes(long size) {
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
|
||||
if (newWriteBufferSize == 0 || newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) {
|
||||
if (WRITABLE_UPDATER.compareAndSet(this, 0, 1)) {
|
||||
channel.pipeline().fireChannelWritabilityChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long total(Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
return ((ByteBuf) msg).readableBytes();
|
||||
}
|
||||
// if (msg instanceof FileRegion) {
|
||||
// return ((FileRegion) msg).count();
|
||||
// }
|
||||
// if (msg instanceof ByteBufHolder) {
|
||||
// return ((ByteBufHolder) msg).content().readableBytes();
|
||||
// }
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current message to write or {@code null} if nothing was flushed before and so is ready to be written.
|
||||
*/
|
||||
public Object current() {
|
||||
Entry entry = flushedEntry;
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the {@link ChannelPromise} of the current message about writing progress.
|
||||
*/
|
||||
public void progress(long amount) {
|
||||
Entry e = flushedEntry;
|
||||
assert e != null;
|
||||
ChannelPromise p = e.promise;
|
||||
if (p instanceof ChannelProgressivePromise) {
|
||||
long progress = e.progress + amount;
|
||||
e.progress = progress;
|
||||
((ChannelProgressivePromise) p).tryProgress(progress, e.total);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove the current message, mark its {@link ChannelPromise} as success and return {@code true}. If no
|
||||
* flushed message exists at the time this method is called it will return {@code false} to signal that no more
|
||||
* messages are ready to be handled.
|
||||
*/
|
||||
public boolean remove() {
|
||||
Entry e = flushedEntry;
|
||||
if (e == null) {
|
||||
return false;
|
||||
}
|
||||
Object msg = e.msg;
|
||||
|
||||
ChannelPromise promise = e.promise;
|
||||
int size = e.pendingSize;
|
||||
|
||||
removeEntry(e);
|
||||
|
||||
if (!e.cancelled) {
|
||||
// only release message, notify and decrement if it was not canceled before.
|
||||
ReferenceCountUtil.safeRelease(msg);
|
||||
safeSuccess(promise);
|
||||
decrementPendingOutboundBytes(size);
|
||||
}
|
||||
|
||||
// recycle the entry
|
||||
e.recycle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove the current message, mark its {@link ChannelPromise} as failure using the given {@link Throwable}
|
||||
* and return {@code true}. If no flushed message exists at the time this method is called it will return
|
||||
* {@code false} to signal that no more messages are ready to be handled.
|
||||
*/
|
||||
public boolean remove(Throwable cause) {
|
||||
Entry e = flushedEntry;
|
||||
if (e == null) {
|
||||
return false;
|
||||
}
|
||||
Object msg = e.msg;
|
||||
|
||||
ChannelPromise promise = e.promise;
|
||||
int size = e.pendingSize;
|
||||
|
||||
removeEntry(e);
|
||||
|
||||
if (!e.cancelled) {
|
||||
// only release message, fail and decrement if it was not canceled before.
|
||||
ReferenceCountUtil.safeRelease(msg);
|
||||
|
||||
safeFail(promise, cause);
|
||||
decrementPendingOutboundBytes(size);
|
||||
}
|
||||
|
||||
// recycle the entry
|
||||
e.recycle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeEntry(Entry e) {
|
||||
if (-- flushed == 0) {
|
||||
// processed everything
|
||||
flushedEntry = null;
|
||||
if (e == tailEntry) {
|
||||
tailEntry = null;
|
||||
unflushedEntry = null;
|
||||
}
|
||||
} else {
|
||||
flushedEntry = e.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the fully written entries and update the reader index of the partially written entry.
|
||||
* This operation assumes all messages in this buffer is {@link ByteBuf}.
|
||||
*/
|
||||
public void removeBytes(long writtenBytes) {
|
||||
for (;;) {
|
||||
Object msg = current();
|
||||
if (!(msg instanceof ByteBuf)) {
|
||||
assert writtenBytes == 0;
|
||||
break;
|
||||
}
|
||||
|
||||
final ByteBuf buf = (ByteBuf) msg;
|
||||
final int readerIndex = buf.readerIndex();
|
||||
final int readableBytes = buf.writerIndex() - readerIndex;
|
||||
|
||||
if (readableBytes <= writtenBytes) {
|
||||
if (writtenBytes != 0) {
|
||||
progress(readableBytes);
|
||||
writtenBytes -= readableBytes;
|
||||
}
|
||||
remove();
|
||||
} else { // readableBytes > writtenBytes
|
||||
if (writtenBytes != 0) {
|
||||
buf.readerIndex(readerIndex + (int) writtenBytes);
|
||||
progress(writtenBytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of direct NIO buffers if the currently pending messages are made of {@link ByteBuf} only.
|
||||
* {@link #nioBufferCount()} and {@link #nioBufferSize()} will return the number of NIO buffers in the returned
|
||||
* array and the total number of readable bytes of the NIO buffers respectively.
|
||||
* <p>
|
||||
* Note that the returned array is reused and thus should not escape
|
||||
* {@link AbstractChannel#doWrite(ChannelOutboundBuffer)}.
|
||||
* Refer to {@link NioSocketChannel#doWrite(ChannelOutboundBuffer)} for an example.
|
||||
* </p>
|
||||
*/
|
||||
public ByteBuffer[] nioBuffers() {
|
||||
long nioBufferSize = 0;
|
||||
int nioBufferCount = 0;
|
||||
final InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
|
||||
ByteBuffer[] nioBuffers = NIO_BUFFERS.get(threadLocalMap);
|
||||
Entry entry = flushedEntry;
|
||||
while (isFlushedEntry(entry) && entry.msg instanceof ByteBuf) {
|
||||
if (!entry.cancelled) {
|
||||
ByteBuf buf = (ByteBuf) entry.msg;
|
||||
final int readerIndex = buf.readerIndex();
|
||||
final int readableBytes = buf.writerIndex() - readerIndex;
|
||||
|
||||
if (readableBytes > 0) {
|
||||
nioBufferSize += readableBytes;
|
||||
int count = entry.count;
|
||||
if (count == -1) {
|
||||
//noinspection ConstantValueVariableUse
|
||||
entry.count = count = buf.nioBufferCount();
|
||||
}
|
||||
int neededSpace = nioBufferCount + count;
|
||||
if (neededSpace > nioBuffers.length) {
|
||||
nioBuffers = expandNioBufferArray(nioBuffers, neededSpace, nioBufferCount);
|
||||
NIO_BUFFERS.set(threadLocalMap, nioBuffers);
|
||||
}
|
||||
if (count == 1) {
|
||||
ByteBuffer nioBuf = entry.buf;
|
||||
if (nioBuf == null) {
|
||||
// cache ByteBuffer as it may need to create a new ByteBuffer instance if its a
|
||||
// derived buffer
|
||||
entry.buf = nioBuf = buf.internalNioBuffer(readerIndex, readableBytes);
|
||||
}
|
||||
nioBuffers[nioBufferCount ++] = nioBuf;
|
||||
} else {
|
||||
ByteBuffer[] nioBufs = entry.bufs;
|
||||
if (nioBufs == null) {
|
||||
// cached ByteBuffers as they may be expensive to create in terms
|
||||
// of Object allocation
|
||||
entry.bufs = nioBufs = buf.nioBuffers();
|
||||
}
|
||||
nioBufferCount = fillBufferArray(nioBufs, nioBuffers, nioBufferCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
entry = entry.next;
|
||||
}
|
||||
this.nioBufferCount = nioBufferCount;
|
||||
this.nioBufferSize = nioBufferSize;
|
||||
|
||||
return nioBuffers;
|
||||
}
|
||||
|
||||
private static int fillBufferArray(ByteBuffer[] nioBufs, ByteBuffer[] nioBuffers, int nioBufferCount) {
|
||||
for (ByteBuffer nioBuf: nioBufs) {
|
||||
if (nioBuf == null) {
|
||||
break;
|
||||
}
|
||||
nioBuffers[nioBufferCount ++] = nioBuf;
|
||||
}
|
||||
return nioBufferCount;
|
||||
}
|
||||
|
||||
private static ByteBuffer[] expandNioBufferArray(ByteBuffer[] array, int neededSpace, int size) {
|
||||
int newCapacity = array.length;
|
||||
do {
|
||||
// double capacity until it is big enough
|
||||
// See https://github.com/netty/netty/issues/1890
|
||||
newCapacity <<= 1;
|
||||
|
||||
if (newCapacity < 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
} while (neededSpace > newCapacity);
|
||||
|
||||
ByteBuffer[] newArray = new ByteBuffer[newCapacity];
|
||||
System.arraycopy(array, 0, newArray, 0, size);
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link ByteBuffer} that can be written out of the {@link ByteBuffer} array that was
|
||||
* obtained via {@link #nioBuffers()}. This method <strong>MUST</strong> be called after {@link #nioBuffers()}
|
||||
* was called.
|
||||
*/
|
||||
public int nioBufferCount() {
|
||||
return nioBufferCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that can be written out of the {@link ByteBuffer} array that was
|
||||
* obtained via {@link #nioBuffers()}. This method <strong>MUST</strong> be called after {@link #nioBuffers()}
|
||||
* was called.
|
||||
*/
|
||||
public long nioBufferSize() {
|
||||
return nioBufferSize;
|
||||
}
|
||||
|
||||
boolean isWritable() {
|
||||
return writable != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of flushed messages in this {@link ChannelOutboundBuffer}.
|
||||
*/
|
||||
public int size() {
|
||||
return flushed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if there are flushed messages in this {@link ChannelOutboundBuffer} or {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return flushed == 0;
|
||||
}
|
||||
|
||||
void failFlushed(Throwable cause) {
|
||||
// Make sure that this method does not reenter. A listener added to the current promise can be notified by the
|
||||
// current thread in the tryFailure() call of the loop below, and the listener can trigger another fail() call
|
||||
// indirectly (usually by closing the channel.)
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1501
|
||||
if (inFail) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
inFail = true;
|
||||
for (;;) {
|
||||
if (!remove(cause)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
inFail = false;
|
||||
}
|
||||
}
|
||||
|
||||
void close(final ClosedChannelException cause) {
|
||||
if (inFail) {
|
||||
channel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
close(cause);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
inFail = true;
|
||||
|
||||
if (channel.isOpen()) {
|
||||
throw new IllegalStateException("close() must be invoked after the channel is closed.");
|
||||
}
|
||||
|
||||
if (!isEmpty()) {
|
||||
throw new IllegalStateException("close() must be invoked after all flushed writes are handled.");
|
||||
}
|
||||
|
||||
// Release all unflushed messages.
|
||||
try {
|
||||
Entry e = unflushedEntry;
|
||||
while (e != null) {
|
||||
// Just decrease; do not trigger any events via decrementPendingOutboundBytes()
|
||||
int size = e.pendingSize;
|
||||
TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
|
||||
|
||||
if (!e.cancelled) {
|
||||
ReferenceCountUtil.safeRelease(e.msg);
|
||||
safeFail(e.promise, cause);
|
||||
}
|
||||
e = e.recycleAndGetNext();
|
||||
}
|
||||
} finally {
|
||||
inFail = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeSuccess(ChannelPromise promise) {
|
||||
if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) {
|
||||
logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
|
||||
}
|
||||
}
|
||||
|
||||
private static void safeFail(ChannelPromise promise, Throwable cause) {
|
||||
if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) {
|
||||
logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void recycle() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public long totalPendingWriteBytes() {
|
||||
return totalPendingSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link MessageProcessor#processMessage(Object)} for each flushed message
|
||||
* in this {@link ChannelOutboundBuffer} until {@link MessageProcessor#processMessage(Object)}
|
||||
* returns {@code false} or there are no more flushed messages to process.
|
||||
*/
|
||||
public void forEachFlushedMessage(MessageProcessor processor) throws Exception {
|
||||
if (processor == null) {
|
||||
throw new NullPointerException("processor");
|
||||
}
|
||||
|
||||
Entry entry = flushedEntry;
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
if (!entry.cancelled) {
|
||||
if (!processor.processMessage(entry.msg)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
entry = entry.next;
|
||||
} while (isFlushedEntry(entry));
|
||||
}
|
||||
|
||||
private boolean isFlushedEntry(Entry e) {
|
||||
return e != null && e != unflushedEntry;
|
||||
}
|
||||
|
||||
public interface MessageProcessor {
|
||||
/**
|
||||
* Will be called for each flushed message until it either there are no more flushed messages or this
|
||||
* method returns {@code false}.
|
||||
*/
|
||||
boolean processMessage(Object msg) throws Exception;
|
||||
}
|
||||
|
||||
static final class Entry {
|
||||
private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
|
||||
@Override
|
||||
protected Entry newObject(Handle handle) {
|
||||
return new Entry(handle);
|
||||
}
|
||||
};
|
||||
|
||||
private final Handle handle;
|
||||
Entry next;
|
||||
Object msg;
|
||||
ByteBuffer[] bufs;
|
||||
ByteBuffer buf;
|
||||
ChannelPromise promise;
|
||||
long progress;
|
||||
long total;
|
||||
int pendingSize;
|
||||
int count = -1;
|
||||
boolean cancelled;
|
||||
|
||||
private Entry(Handle handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
|
||||
Entry entry = RECYCLER.get();
|
||||
entry.msg = msg;
|
||||
entry.pendingSize = size;
|
||||
entry.total = total;
|
||||
entry.promise = promise;
|
||||
return entry;
|
||||
}
|
||||
|
||||
int cancel() {
|
||||
if (!cancelled) {
|
||||
cancelled = true;
|
||||
int pSize = pendingSize;
|
||||
|
||||
// release message and replace with an empty buffer
|
||||
ReferenceCountUtil.safeRelease(msg);
|
||||
msg = Unpooled.EMPTY_BUFFER;
|
||||
|
||||
pendingSize = 0;
|
||||
total = 0;
|
||||
progress = 0;
|
||||
bufs = null;
|
||||
buf = null;
|
||||
return pSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
next = null;
|
||||
bufs = null;
|
||||
buf = null;
|
||||
msg = null;
|
||||
promise = null;
|
||||
progress = 0;
|
||||
total = 0;
|
||||
pendingSize = 0;
|
||||
count = -1;
|
||||
cancelled = false;
|
||||
RECYCLER.recycle(this, handle);
|
||||
}
|
||||
|
||||
Entry recycleAndGetNext() {
|
||||
Entry next = this.next;
|
||||
recycle();
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
99
common/src/common/net/channel/ChannelOutboundHandler.java
Normal file
99
common/src/common/net/channel/ChannelOutboundHandler.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* {@link ChannelHandler} which will get notified for IO-outbound-operations.
|
||||
*/
|
||||
public interface ChannelOutboundHandler extends ChannelHandler {
|
||||
/**
|
||||
* Called once a bind operation is made.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the bind operation is made
|
||||
* @param localAddress the {@link SocketAddress} to which it should bound
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a connect operation is made.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the connect operation is made
|
||||
* @param remoteAddress the {@link SocketAddress} to which it should connect
|
||||
* @param localAddress the {@link SocketAddress} which is used as source on connect
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void connect(
|
||||
ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
||||
SocketAddress localAddress, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a disconnect operation is made.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the disconnect operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a close operation is made.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the close operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a deregister operation is made from the current registered {@link EventLoop}.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the close operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Intercepts {@link ChannelHandlerContext#read()}.
|
||||
*/
|
||||
void read(ChannelHandlerContext ctx) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a write operation is made. The write operation will write the messages through the
|
||||
* {@link ChannelPipeline}. Those are then ready to be flushed to the actual {@link Channel} once
|
||||
* {@link Channel#flush()} is called
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the write operation is made
|
||||
* @param msg the message to write
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
|
||||
|
||||
/**
|
||||
* Called once a flush operation is made. The flush operation will try to flush out all previous written messages
|
||||
* that are pending.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} for which the flush operation is made
|
||||
* @throws Exception thrown if an error accour
|
||||
*/
|
||||
void flush(ChannelHandlerContext ctx) throws Exception;
|
||||
}
|
117
common/src/common/net/channel/ChannelOutboundHandlerAdapter.java
Normal file
117
common/src/common/net/channel/ChannelOutboundHandlerAdapter.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* Skelton implementation of a {@link ChannelOutboundHandler}. This implementation just forwards each method call via
|
||||
* the {@link ChannelHandlerContext}.
|
||||
*/
|
||||
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
|
||||
ChannelPromise promise) throws Exception {
|
||||
ctx.bind(localAddress, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
||||
SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||
ctx.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#disconnect(ChannelPromise)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
|
||||
throws Exception {
|
||||
ctx.disconnect(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void close(ChannelHandlerContext ctx, ChannelPromise promise)
|
||||
throws Exception {
|
||||
ctx.close(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||
ctx.deregister(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#read()} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void read(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#write(Object)} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
ctx.write(msg, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#flush()} to forward
|
||||
* to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
|
||||
*
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*/
|
||||
@Override
|
||||
public void flush(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.flush();
|
||||
}
|
||||
}
|
872
common/src/common/net/channel/ChannelPipeline.java
Normal file
872
common/src/common/net/channel/ChannelPipeline.java
Normal file
|
@ -0,0 +1,872 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import common.net.util.concurrent.EventExecutorGroup;
|
||||
|
||||
|
||||
/**
|
||||
* A list of {@link ChannelHandler}s which handles or intercepts inbound events and outbound operations of a
|
||||
* {@link Channel}. {@link ChannelPipeline} implements an advanced form of the
|
||||
* <a href="http://www.oracle.com/technetwork/java/interceptingfilter-142169.html">Intercepting Filter</a> pattern
|
||||
* to give a user full control over how an event is handled and how the {@link ChannelHandler}s in a pipeline
|
||||
* interact with each other.
|
||||
*
|
||||
* <h3>Creation of a pipeline</h3>
|
||||
*
|
||||
* Each channel has its own pipeline and it is created automatically when a new channel is created.
|
||||
*
|
||||
* <h3>How an event flows in a pipeline</h3>
|
||||
*
|
||||
* The following diagram describes how I/O events are processed by {@link ChannelHandler}s in a {@link ChannelPipeline}
|
||||
* typically. An I/O event is handled by either a {@link ChannelInboundHandler} or a {@link ChannelOutboundHandler}
|
||||
* and be forwarded to its closest handler by calling the event propagation methods defined in
|
||||
* {@link ChannelHandlerContext}, such as {@link ChannelHandlerContext#fireChannelRead(Object)} and
|
||||
* {@link ChannelHandlerContext#write(Object)}.
|
||||
*
|
||||
* <pre>
|
||||
* I/O Request
|
||||
* via {@link Channel} or
|
||||
* {@link ChannelHandlerContext}
|
||||
* |
|
||||
* +---------------------------------------------------+---------------+
|
||||
* | ChannelPipeline | |
|
||||
* | \|/ |
|
||||
* | +---------------------+ +-----------+----------+ |
|
||||
* | | Inbound Handler N | | Outbound Handler 1 | |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | /|\ | |
|
||||
* | | \|/ |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | | Inbound Handler N-1 | | Outbound Handler 2 | |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | /|\ . |
|
||||
* | . . |
|
||||
* | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
|
||||
* | [ method call] [method call] |
|
||||
* | . . |
|
||||
* | . \|/ |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | | Inbound Handler 2 | | Outbound Handler M-1 | |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | /|\ | |
|
||||
* | | \|/ |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | | Inbound Handler 1 | | Outbound Handler M | |
|
||||
* | +----------+----------+ +-----------+----------+ |
|
||||
* | /|\ | |
|
||||
* +---------------+-----------------------------------+---------------+
|
||||
* | \|/
|
||||
* +---------------+-----------------------------------+---------------+
|
||||
* | | | |
|
||||
* | [ Socket.read() ] [ Socket.write() ] |
|
||||
* | |
|
||||
* | Netty Internal I/O Threads (Transport Implementation) |
|
||||
* +-------------------------------------------------------------------+
|
||||
* </pre>
|
||||
* An inbound event is handled by the inbound handlers in the bottom-up direction as shown on the left side of the
|
||||
* diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the
|
||||
* diagram. The inbound data is often read from a remote peer via the actual input operation such as
|
||||
* {@link SocketChannel#read(ByteBuffer)}. If an inbound event goes beyond the top inbound handler, it is discarded
|
||||
* silently, or logged if it needs your attention.
|
||||
* <p>
|
||||
* An outbound event is handled by the outbound handler in the top-down direction as shown on the right side of the
|
||||
* diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests.
|
||||
* If an outbound event goes beyond the bottom outbound handler, it is handled by an I/O thread associated with the
|
||||
* {@link Channel}. The I/O thread often performs the actual output operation such as
|
||||
* {@link SocketChannel#write(ByteBuffer)}.
|
||||
* <p>
|
||||
* For example, let us assume that we created the following pipeline:
|
||||
* <pre>
|
||||
* {@link ChannelPipeline} p = ...;
|
||||
* p.addLast("1", new InboundHandlerA());
|
||||
* p.addLast("2", new InboundHandlerB());
|
||||
* p.addLast("3", new OutboundHandlerA());
|
||||
* p.addLast("4", new OutboundHandlerB());
|
||||
* p.addLast("5", new InboundOutboundHandlerX());
|
||||
* </pre>
|
||||
* In the example above, the class whose name starts with {@code Inbound} means it is an inbound handler.
|
||||
* The class whose name starts with {@code Outbound} means it is a outbound handler.
|
||||
* <p>
|
||||
* In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound.
|
||||
* When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle, {@link ChannelPipeline} skips
|
||||
* the evaluation of certain handlers to shorten the stack depth:
|
||||
* <ul>
|
||||
* <li>3 and 4 don't implement {@link ChannelInboundHandler}, and therefore the actual evaluation order of an inbound
|
||||
* event will be: 1, 2, and 5.</li>
|
||||
* <li>1 and 2 don't implement {@link ChannelOutboundHandler}, and therefore the actual evaluation order of a
|
||||
* outbound event will be: 5, 4, and 3.</li>
|
||||
* <li>If 5 implements both {@link ChannelInboundHandler} and {@link ChannelOutboundHandler}, the evaluation order of
|
||||
* an inbound and a outbound event could be 125 and 543 respectively.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Forwarding an event to the next handler</h3>
|
||||
*
|
||||
* As you might noticed in the diagram shows, a handler has to invoke the event propagation methods in
|
||||
* {@link ChannelHandlerContext} to forward an event to its next handler. Those methods include:
|
||||
* <ul>
|
||||
* <li>Inbound event propagation methods:
|
||||
* <ul>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelRegistered()}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelActive()}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelRead(Object)}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelReadComplete()}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireExceptionCaught(Throwable)}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireUserEventTriggered(Object)}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelWritabilityChanged()}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelInactive()}</li>
|
||||
* <li>{@link ChannelHandlerContext#fireChannelUnregistered()}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Outbound event propagation methods:
|
||||
* <ul>
|
||||
* <li>{@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)}</li>
|
||||
* <li>{@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)}</li>
|
||||
* <li>{@link ChannelHandlerContext#write(Object, ChannelPromise)}</li>
|
||||
* <li>{@link ChannelHandlerContext#flush()}</li>
|
||||
* <li>{@link ChannelHandlerContext#read()}</li>
|
||||
* <li>{@link ChannelHandlerContext#disconnect(ChannelPromise)}</li>
|
||||
* <li>{@link ChannelHandlerContext#close(ChannelPromise)}</li>
|
||||
* <li>{@link ChannelHandlerContext#deregister(ChannelPromise)}</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* and the following example shows how the event propagation is usually done:
|
||||
*
|
||||
* <pre>
|
||||
* public class MyInboundHandler extends {@link ChannelInboundHandlerAdapter} {
|
||||
* {@code @Override}
|
||||
* public void channelActive({@link ChannelHandlerContext} ctx) {
|
||||
* System.out.println("Connected!");
|
||||
* ctx.fireChannelActive();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* public clas MyOutboundHandler extends {@link ChannelOutboundHandlerAdapter} {
|
||||
* {@code @Override}
|
||||
* public void close({@link ChannelHandlerContext} ctx, {@link ChannelPromise} promise) {
|
||||
* System.out.println("Closing ..");
|
||||
* ctx.close(promise);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Building a pipeline</h3>
|
||||
* <p>
|
||||
* A user is supposed to have one or more {@link ChannelHandler}s in a pipeline to receive I/O events (e.g. read) and
|
||||
* to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers
|
||||
* in each channel's pipeline, but your mileage may vary depending on the complexity and characteristics of the
|
||||
* protocol and business logic:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Protocol Decoder - translates binary data (e.g. {@link ByteBuf}) into a Java object.</li>
|
||||
* <li>Protocol Encoder - translates a Java object into binary data.</li>
|
||||
* <li>Business Logic Handler - performs the actual business logic (e.g. database access).</li>
|
||||
* </ol>
|
||||
*
|
||||
* and it could be represented as shown in the following example:
|
||||
*
|
||||
* <pre>
|
||||
* static final {@link EventExecutorGroup} group = new {@link DefaultEventExecutorGroup}(16);
|
||||
* ...
|
||||
*
|
||||
* {@link ChannelPipeline} pipeline = ch.pipeline();
|
||||
*
|
||||
* pipeline.addLast("decoder", new MyProtocolDecoder());
|
||||
* pipeline.addLast("encoder", new MyProtocolEncoder());
|
||||
*
|
||||
* // Tell the pipeline to run MyBusinessLogicHandler's event handler methods
|
||||
* // in a different thread than an I/O thread so that the I/O thread is not blocked by
|
||||
* // a time-consuming task.
|
||||
* // If your business logic is fully asynchronous or finished very quickly, you don't
|
||||
* // need to specify a group.
|
||||
* pipeline.addLast(group, "handler", new MyBusinessLogicHandler());
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Thread safety</h3>
|
||||
* <p>
|
||||
* A {@link ChannelHandler} can be added or removed at any time because a {@link ChannelPipeline} is thread safe.
|
||||
* For example, you can insert an encryption handler when sensitive information is about to be exchanged, and remove it
|
||||
* after the exchange.
|
||||
*/
|
||||
public interface ChannelPipeline
|
||||
extends Iterable<Entry<String, ChannelHandler>> {
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} at the first position of this pipeline.
|
||||
*
|
||||
* @param name the name of the handler to insert first
|
||||
* @param handler the handler to insert first
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified name or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addFirst(String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} at the first position of this pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
||||
* methods
|
||||
* @param name the name of the handler to insert first
|
||||
* @param handler the handler to insert first
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified name or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Appends a {@link ChannelHandler} at the last position of this pipeline.
|
||||
*
|
||||
* @param name the name of the handler to append
|
||||
* @param handler the handler to append
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified name or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addLast(String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Appends a {@link ChannelHandler} at the last position of this pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
||||
* methods
|
||||
* @param name the name of the handler to append
|
||||
* @param handler the handler to append
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified name or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} before an existing handler of this
|
||||
* pipeline.
|
||||
*
|
||||
* @param baseName the name of the existing handler
|
||||
* @param name the name of the handler to insert before
|
||||
* @param handler the handler to insert before
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such entry with the specified {@code baseName}
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified baseName, name, or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} before an existing handler of this
|
||||
* pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
||||
* methods
|
||||
* @param baseName the name of the existing handler
|
||||
* @param name the name of the handler to insert before
|
||||
* @param handler the handler to insert before
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such entry with the specified {@code baseName}
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified baseName, name, or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} after an existing handler of this
|
||||
* pipeline.
|
||||
*
|
||||
* @param baseName the name of the existing handler
|
||||
* @param name the name of the handler to insert after
|
||||
* @param handler the handler to insert after
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such entry with the specified {@code baseName}
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified baseName, name, or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler} after an existing handler of this
|
||||
* pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
||||
* methods
|
||||
* @param baseName the name of the existing handler
|
||||
* @param name the name of the handler to insert after
|
||||
* @param handler the handler to insert after
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such entry with the specified {@code baseName}
|
||||
* @throws IllegalArgumentException
|
||||
* if there's an entry with the same name already in the pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified baseName, name, or handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler}s at the first position of this pipeline.
|
||||
*
|
||||
* @param handlers the handlers to insert first
|
||||
*
|
||||
*/
|
||||
ChannelPipeline addFirst(ChannelHandler... handlers);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler}s at the first position of this pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s
|
||||
* methods.
|
||||
* @param handlers the handlers to insert first
|
||||
*
|
||||
*/
|
||||
ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler}s at the last position of this pipeline.
|
||||
*
|
||||
* @param handlers the handlers to insert last
|
||||
*
|
||||
*/
|
||||
ChannelPipeline addLast(ChannelHandler... handlers);
|
||||
|
||||
/**
|
||||
* Inserts a {@link ChannelHandler}s at the last position of this pipeline.
|
||||
*
|
||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s
|
||||
* methods.
|
||||
* @param handlers the handlers to insert last
|
||||
*
|
||||
*/
|
||||
ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);
|
||||
|
||||
/**
|
||||
* Removes the specified {@link ChannelHandler} from this pipeline.
|
||||
*
|
||||
* @param handler the {@link ChannelHandler} to remove
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such handler in this pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified handler is {@code null}
|
||||
*/
|
||||
ChannelPipeline remove(ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Removes the {@link ChannelHandler} with the specified name from this pipeline.
|
||||
*
|
||||
* @param name the name under which the {@link ChannelHandler} was stored.
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such handler with the specified name in this pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified name is {@code null}
|
||||
*/
|
||||
ChannelHandler remove(String name);
|
||||
|
||||
/**
|
||||
* Removes the {@link ChannelHandler} of the specified type from this pipeline.
|
||||
*
|
||||
* @param <T> the type of the handler
|
||||
* @param handlerType the type of the handler
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if there's no such handler of the specified type in this pipeline
|
||||
* @throws NullPointerException
|
||||
* if the specified handler type is {@code null}
|
||||
*/
|
||||
<T extends ChannelHandler> T remove(Class<T> handlerType);
|
||||
|
||||
/**
|
||||
* Removes the first {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if this pipeline is empty
|
||||
*/
|
||||
ChannelHandler removeFirst();
|
||||
|
||||
/**
|
||||
* Removes the last {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if this pipeline is empty
|
||||
*/
|
||||
ChannelHandler removeLast();
|
||||
|
||||
/**
|
||||
* Replaces the specified {@link ChannelHandler} with a new handler in this pipeline.
|
||||
*
|
||||
* @param oldHandler the {@link ChannelHandler} to be replaced
|
||||
* @param newName the name under which the replacement should be added
|
||||
* @param newHandler the {@link ChannelHandler} which is used as replacement
|
||||
*
|
||||
* @return itself
|
||||
|
||||
* @throws NoSuchElementException
|
||||
* if the specified old handler does not exist in this pipeline
|
||||
* @throws IllegalArgumentException
|
||||
* if a handler with the specified new name already exists in this
|
||||
* pipeline, except for the handler to be replaced
|
||||
* @throws NullPointerException
|
||||
* if the specified old handler, new name, or new handler is
|
||||
* {@code null}
|
||||
*/
|
||||
ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
|
||||
|
||||
/**
|
||||
* Replaces the {@link ChannelHandler} of the specified name with a new handler in this pipeline.
|
||||
*
|
||||
* @param oldName the name of the {@link ChannelHandler} to be replaced
|
||||
* @param newName the name under which the replacement should be added
|
||||
* @param newHandler the {@link ChannelHandler} which is used as replacement
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if the handler with the specified old name does not exist in this pipeline
|
||||
* @throws IllegalArgumentException
|
||||
* if a handler with the specified new name already exists in this
|
||||
* pipeline, except for the handler to be replaced
|
||||
* @throws NullPointerException
|
||||
* if the specified old handler, new name, or new handler is
|
||||
* {@code null}
|
||||
*/
|
||||
ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
|
||||
|
||||
/**
|
||||
* Replaces the {@link ChannelHandler} of the specified type with a new handler in this pipeline.
|
||||
*
|
||||
* @param oldHandlerType the type of the handler to be removed
|
||||
* @param newName the name under which the replacement should be added
|
||||
* @param newHandler the {@link ChannelHandler} which is used as replacement
|
||||
*
|
||||
* @return the removed handler
|
||||
*
|
||||
* @throws NoSuchElementException
|
||||
* if the handler of the specified old handler type does not exist
|
||||
* in this pipeline
|
||||
* @throws IllegalArgumentException
|
||||
* if a handler with the specified new name already exists in this
|
||||
* pipeline, except for the handler to be replaced
|
||||
* @throws NullPointerException
|
||||
* if the specified old handler, new name, or new handler is
|
||||
* {@code null}
|
||||
*/
|
||||
<T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName,
|
||||
ChannelHandler newHandler);
|
||||
|
||||
/**
|
||||
* Returns the first {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the first handler. {@code null} if this pipeline is empty.
|
||||
*/
|
||||
ChannelHandler first();
|
||||
|
||||
/**
|
||||
* Returns the context of the first {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the context of the first handler. {@code null} if this pipeline is empty.
|
||||
*/
|
||||
ChannelHandlerContext firstContext();
|
||||
|
||||
/**
|
||||
* Returns the last {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the last handler. {@code null} if this pipeline is empty.
|
||||
*/
|
||||
ChannelHandler last();
|
||||
|
||||
/**
|
||||
* Returns the context of the last {@link ChannelHandler} in this pipeline.
|
||||
*
|
||||
* @return the context of the last handler. {@code null} if this pipeline is empty.
|
||||
*/
|
||||
ChannelHandlerContext lastContext();
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelHandler} with the specified name in this
|
||||
* pipeline.
|
||||
*
|
||||
* @return the handler with the specified name.
|
||||
* {@code null} if there's no such handler in this pipeline.
|
||||
*/
|
||||
ChannelHandler get(String name);
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelHandler} of the specified type in this
|
||||
* pipeline.
|
||||
*
|
||||
* @return the handler of the specified handler type.
|
||||
* {@code null} if there's no such handler in this pipeline.
|
||||
*/
|
||||
<T extends ChannelHandler> T get(Class<T> handlerType);
|
||||
|
||||
/**
|
||||
* Returns the context object of the specified {@link ChannelHandler} in
|
||||
* this pipeline.
|
||||
*
|
||||
* @return the context object of the specified handler.
|
||||
* {@code null} if there's no such handler in this pipeline.
|
||||
*/
|
||||
ChannelHandlerContext context(ChannelHandler handler);
|
||||
|
||||
/**
|
||||
* Returns the context object of the {@link ChannelHandler} with the
|
||||
* specified name in this pipeline.
|
||||
*
|
||||
* @return the context object of the handler with the specified name.
|
||||
* {@code null} if there's no such handler in this pipeline.
|
||||
*/
|
||||
ChannelHandlerContext context(String name);
|
||||
|
||||
/**
|
||||
* Returns the context object of the {@link ChannelHandler} of the
|
||||
* specified type in this pipeline.
|
||||
*
|
||||
* @return the context object of the handler of the specified type.
|
||||
* {@code null} if there's no such handler in this pipeline.
|
||||
*/
|
||||
ChannelHandlerContext context(Class<? extends ChannelHandler> handlerType);
|
||||
|
||||
/**
|
||||
* Returns the {@link Channel} that this pipeline is attached to.
|
||||
*
|
||||
* @return the channel. {@code null} if this pipeline is not attached yet.
|
||||
*/
|
||||
Channel channel();
|
||||
|
||||
/**
|
||||
* Returns the {@link List} of the handler names.
|
||||
*/
|
||||
List<String> names();
|
||||
|
||||
/**
|
||||
* Converts this pipeline into an ordered {@link Map} whose keys are
|
||||
* handler names and whose values are handlers.
|
||||
*/
|
||||
Map<String, ChannelHandler> toMap();
|
||||
|
||||
/**
|
||||
* A {@link Channel} was registered to its {@link EventLoop}.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelRegistered(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireChannelRegistered();
|
||||
|
||||
/**
|
||||
* A {@link Channel} was unregistered from its {@link EventLoop}.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelUnregistered(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireChannelUnregistered();
|
||||
|
||||
/**
|
||||
* A {@link Channel} is active now, which means it is connected.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireChannelActive();
|
||||
|
||||
/**
|
||||
* A {@link Channel} is inactive now, which means it is closed.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelInactive(ChannelHandlerContext)} method
|
||||
* called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireChannelInactive();
|
||||
|
||||
/**
|
||||
* A {@link Channel} received an {@link Throwable} in one of its inbound operations.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#exceptionCaught(ChannelHandlerContext, Throwable)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireExceptionCaught(Throwable cause);
|
||||
|
||||
/**
|
||||
* A {@link Channel} received an user defined event.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireUserEventTriggered(Object event);
|
||||
|
||||
/**
|
||||
* A {@link Channel} received a message.
|
||||
*
|
||||
* This will result in having the {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}
|
||||
* method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline fireChannelRead(Object msg);
|
||||
|
||||
/**
|
||||
* Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)}
|
||||
* event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*/
|
||||
ChannelPipeline fireChannelReadComplete();
|
||||
|
||||
/**
|
||||
* Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)}
|
||||
* event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*/
|
||||
ChannelPipeline fireChannelWritabilityChanged();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect();
|
||||
|
||||
/**
|
||||
* Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close();
|
||||
|
||||
/**
|
||||
* Request to deregister the {@link Channel} from the previous assigned {@link EventExecutor} and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*
|
||||
*/
|
||||
ChannelFuture deregister();
|
||||
|
||||
/**
|
||||
* Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
|
||||
* called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
|
||||
* completes, either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelFuture} will be notified.
|
||||
*
|
||||
* <p>
|
||||
* If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
|
||||
* a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
|
||||
* will be used.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
|
||||
* {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified and also returned.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
|
||||
* either because the operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture disconnect(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to close the {@link Channel} bound to this {@link ChannelPipeline} and notify the {@link ChannelFuture}
|
||||
* once the operation completes, either because the operation was successful or because of
|
||||
* an error.
|
||||
*
|
||||
* After it is closed it is not possible to reuse it again.
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture close(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to deregister the {@link Channel} bound this {@link ChannelPipeline} from the previous assigned
|
||||
* {@link EventExecutor} and notify the {@link ChannelFuture} once the operation completes, either because the
|
||||
* operation was successful or because of an error.
|
||||
*
|
||||
* The given {@link ChannelPromise} will be notified.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelFuture deregister(ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to Read data from the {@link Channel} into the first inbound buffer, triggers an
|
||||
* {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was
|
||||
* read, and triggers a
|
||||
* {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the
|
||||
* handler can decide to continue reading. If there's a pending read operation already, this method does nothing.
|
||||
* <p>
|
||||
* This will result in having the
|
||||
* {@link ChannelOutboundHandler#read(ChannelHandlerContext)}
|
||||
* method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
|
||||
* {@link Channel}.
|
||||
*/
|
||||
ChannelPipeline read();
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg);
|
||||
|
||||
/**
|
||||
* Request to write a message via this {@link ChannelPipeline}.
|
||||
* This method will not request to actual flush, so be sure to call {@link #flush()}
|
||||
* once you want to request to flush all pending data to the actual transport.
|
||||
*/
|
||||
ChannelFuture write(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Request to flush all pending messages.
|
||||
*/
|
||||
ChannelPipeline flush();
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Shortcut for call {@link #write(Object)} and {@link #flush()}.
|
||||
*/
|
||||
ChannelFuture writeAndFlush(Object msg);
|
||||
}
|
52
common/src/common/net/channel/ChannelPipelineException.java
Normal file
52
common/src/common/net/channel/ChannelPipelineException.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* A {@link ChannelException} which is thrown when a {@link ChannelPipeline}
|
||||
* failed to execute an operation.
|
||||
*/
|
||||
public class ChannelPipelineException extends ChannelException {
|
||||
|
||||
private static final long serialVersionUID = 3379174210419885980L;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public ChannelPipelineException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public ChannelPipelineException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public ChannelPipelineException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public ChannelPipelineException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
86
common/src/common/net/channel/ChannelProgressivePromise.java
Normal file
86
common/src/common/net/channel/ChannelProgressivePromise.java
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
import common.net.util.concurrent.ProgressiveFuture;
|
||||
import common.net.util.concurrent.ProgressivePromise;
|
||||
|
||||
/**
|
||||
* Special {@link ChannelPromise} which will be notified once the associated bytes is transferring.
|
||||
*/
|
||||
public interface ChannelProgressivePromise extends ProgressivePromise<Void>, ChannelFuture, ProgressiveFuture<Void>, ChannelPromise {
|
||||
// @Override
|
||||
// ChannelProgressiveFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture sync() throws InterruptedException;
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture syncUninterruptibly();
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture await() throws InterruptedException;
|
||||
//
|
||||
// @Override
|
||||
// ChannelProgressiveFuture awaitUninterruptibly();
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise addListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise sync() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise syncUninterruptibly();
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise await() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise awaitUninterruptibly();
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise setSuccess(Void result);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise setSuccess();
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise setFailure(Throwable cause);
|
||||
|
||||
@Override
|
||||
ChannelProgressivePromise setProgress(long progress, long total);
|
||||
}
|
63
common/src/common/net/channel/ChannelPromise.java
Normal file
63
common/src/common/net/channel/ChannelPromise.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
import common.net.util.concurrent.Promise;
|
||||
|
||||
/**
|
||||
* Special {@link ChannelFuture} which is writable.
|
||||
*/
|
||||
public interface ChannelPromise extends ChannelFuture, Promise<Void> {
|
||||
|
||||
@Override
|
||||
Channel channel();
|
||||
|
||||
@Override
|
||||
ChannelPromise setSuccess(Void result);
|
||||
|
||||
ChannelPromise setSuccess();
|
||||
|
||||
boolean trySuccess();
|
||||
|
||||
@Override
|
||||
ChannelPromise setFailure(Throwable cause);
|
||||
|
||||
@Override
|
||||
ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
|
||||
|
||||
@Override
|
||||
ChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
|
||||
|
||||
@Override
|
||||
ChannelPromise sync() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelPromise syncUninterruptibly();
|
||||
|
||||
@Override
|
||||
ChannelPromise await() throws InterruptedException;
|
||||
|
||||
@Override
|
||||
ChannelPromise awaitUninterruptibly();
|
||||
}
|
107
common/src/common/net/channel/CompleteChannelFuture.java
Normal file
107
common/src/common/net/channel/CompleteChannelFuture.java
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.CompleteFuture;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
|
||||
/**
|
||||
* A skeletal {@link ChannelFuture} implementation which represents a
|
||||
* {@link ChannelFuture} which has been completed already.
|
||||
*/
|
||||
abstract class CompleteChannelFuture extends CompleteFuture<Void> implements ChannelFuture {
|
||||
|
||||
private final Channel channel;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel the {@link Channel} associated with this future
|
||||
*/
|
||||
protected CompleteChannelFuture(Channel channel, EventExecutor executor) {
|
||||
super(executor);
|
||||
if (channel == null) {
|
||||
throw new NullPointerException("channel");
|
||||
}
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventExecutor executor() {
|
||||
EventExecutor e = super.executor();
|
||||
if (e == null) {
|
||||
return channel().eventLoop();
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
super.addListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
super.addListeners(listeners);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
super.removeListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
super.removeListeners(listeners);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture syncUninterruptibly() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture sync() throws InterruptedException {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture await() throws InterruptedException {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture awaitUninterruptibly() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void getNow() {
|
||||
return null;
|
||||
}
|
||||
}
|
33
common/src/common/net/channel/ConnectTimeoutException.java
Normal file
33
common/src/common/net/channel/ConnectTimeoutException.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.net.ConnectException;
|
||||
|
||||
/**
|
||||
* {@link ConnectException} which will be thrown if a connection could
|
||||
* not be established because of a connection timeout.
|
||||
*/
|
||||
public class ConnectTimeoutException extends ConnectException {
|
||||
private static final long serialVersionUID = 2317065249988317463L;
|
||||
|
||||
public ConnectTimeoutException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ConnectTimeoutException() {
|
||||
}
|
||||
}
|
355
common/src/common/net/channel/DefaultChannelConfig.java
Normal file
355
common/src/common/net/channel/DefaultChannelConfig.java
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import static common.net.channel.ChannelOption.ALLOCATOR;
|
||||
import static common.net.channel.ChannelOption.AUTO_CLOSE;
|
||||
import static common.net.channel.ChannelOption.AUTO_READ;
|
||||
import static common.net.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS;
|
||||
import static common.net.channel.ChannelOption.MAX_MESSAGES_PER_READ;
|
||||
import static common.net.channel.ChannelOption.MESSAGE_SIZE_ESTIMATOR;
|
||||
import static common.net.channel.ChannelOption.RCVBUF_ALLOCATOR;
|
||||
import static common.net.channel.ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK;
|
||||
import static common.net.channel.ChannelOption.WRITE_BUFFER_LOW_WATER_MARK;
|
||||
import static common.net.channel.ChannelOption.WRITE_SPIN_COUNT;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.channel.nio.AbstractNioByteChannel;
|
||||
|
||||
/**
|
||||
* The default {@link SocketChannelConfig} implementation.
|
||||
*/
|
||||
public class DefaultChannelConfig implements ChannelConfig {
|
||||
|
||||
private static final RecvByteBufAllocator DEFAULT_RCVBUF_ALLOCATOR = AdaptiveRecvByteBufAllocator.DEFAULT;
|
||||
private static final MessageSizeEstimator DEFAULT_MSG_SIZE_ESTIMATOR = DefaultMessageSizeEstimator.DEFAULT;
|
||||
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT = 30000;
|
||||
|
||||
protected final Channel channel;
|
||||
|
||||
private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
|
||||
private volatile RecvByteBufAllocator rcvBufAllocator = DEFAULT_RCVBUF_ALLOCATOR;
|
||||
private volatile MessageSizeEstimator msgSizeEstimator = DEFAULT_MSG_SIZE_ESTIMATOR;
|
||||
|
||||
private volatile int connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT;
|
||||
private volatile int maxMessagesPerRead;
|
||||
private volatile int writeSpinCount = 16;
|
||||
private volatile boolean autoRead = true;
|
||||
private volatile boolean autoClose = true;
|
||||
private volatile int writeBufferHighWaterMark = 64 * 1024;
|
||||
private volatile int writeBufferLowWaterMark = 32 * 1024;
|
||||
|
||||
public DefaultChannelConfig(Channel channel) {
|
||||
if (channel == null) {
|
||||
throw new NullPointerException("channel");
|
||||
}
|
||||
this.channel = channel;
|
||||
|
||||
if (channel instanceof ServerChannel || channel instanceof AbstractNioByteChannel) {
|
||||
// Server channels: Accept as many incoming connections as possible.
|
||||
// NIO byte channels: Implemented to reduce unnecessary system calls even if it's > 1.
|
||||
// See https://github.com/netty/netty/issues/2079
|
||||
// TODO: Add some property to ChannelMetadata so we can remove the ugly instanceof
|
||||
maxMessagesPerRead = 16;
|
||||
} else {
|
||||
maxMessagesPerRead = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Map<ChannelOption<?>, Object> getOptions() {
|
||||
return getOptions(
|
||||
null,
|
||||
CONNECT_TIMEOUT_MILLIS, MAX_MESSAGES_PER_READ, WRITE_SPIN_COUNT,
|
||||
ALLOCATOR, AUTO_READ, AUTO_CLOSE, RCVBUF_ALLOCATOR, WRITE_BUFFER_HIGH_WATER_MARK,
|
||||
WRITE_BUFFER_LOW_WATER_MARK, MESSAGE_SIZE_ESTIMATOR);
|
||||
}
|
||||
|
||||
protected Map<ChannelOption<?>, Object> getOptions(
|
||||
Map<ChannelOption<?>, Object> result, ChannelOption<?>... options) {
|
||||
if (result == null) {
|
||||
result = new IdentityHashMap<ChannelOption<?>, Object>();
|
||||
}
|
||||
for (ChannelOption<?> o: options) {
|
||||
result.put(o, getOption(o));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setOptions(Map<ChannelOption<?>, ?> options) {
|
||||
if (options == null) {
|
||||
throw new NullPointerException("options");
|
||||
}
|
||||
|
||||
boolean setAllOptions = true;
|
||||
for (Entry<ChannelOption<?>, ?> e: options.entrySet()) {
|
||||
if (!setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
|
||||
setAllOptions = false;
|
||||
}
|
||||
}
|
||||
|
||||
return setAllOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public <T> T getOption(ChannelOption<T> option) {
|
||||
if (option == null) {
|
||||
throw new NullPointerException("option");
|
||||
}
|
||||
|
||||
if (option == CONNECT_TIMEOUT_MILLIS) {
|
||||
return (T) Integer.valueOf(getConnectTimeoutMillis());
|
||||
}
|
||||
if (option == MAX_MESSAGES_PER_READ) {
|
||||
return (T) Integer.valueOf(getMaxMessagesPerRead());
|
||||
}
|
||||
if (option == WRITE_SPIN_COUNT) {
|
||||
return (T) Integer.valueOf(getWriteSpinCount());
|
||||
}
|
||||
if (option == ALLOCATOR) {
|
||||
return (T) getAllocator();
|
||||
}
|
||||
if (option == RCVBUF_ALLOCATOR) {
|
||||
return (T) getRecvByteBufAllocator();
|
||||
}
|
||||
if (option == AUTO_READ) {
|
||||
return (T) Boolean.valueOf(isAutoRead());
|
||||
}
|
||||
if (option == AUTO_CLOSE) {
|
||||
return (T) Boolean.valueOf(isAutoClose());
|
||||
}
|
||||
if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
|
||||
return (T) Integer.valueOf(getWriteBufferHighWaterMark());
|
||||
}
|
||||
if (option == WRITE_BUFFER_LOW_WATER_MARK) {
|
||||
return (T) Integer.valueOf(getWriteBufferLowWaterMark());
|
||||
}
|
||||
if (option == MESSAGE_SIZE_ESTIMATOR) {
|
||||
return (T) getMessageSizeEstimator();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public <T> boolean setOption(ChannelOption<T> option, T value) {
|
||||
validate(option, value);
|
||||
|
||||
if (option == CONNECT_TIMEOUT_MILLIS) {
|
||||
setConnectTimeoutMillis((Integer) value);
|
||||
} else if (option == MAX_MESSAGES_PER_READ) {
|
||||
setMaxMessagesPerRead((Integer) value);
|
||||
} else if (option == WRITE_SPIN_COUNT) {
|
||||
setWriteSpinCount((Integer) value);
|
||||
} else if (option == ALLOCATOR) {
|
||||
setAllocator((ByteBufAllocator) value);
|
||||
} else if (option == RCVBUF_ALLOCATOR) {
|
||||
setRecvByteBufAllocator((RecvByteBufAllocator) value);
|
||||
} else if (option == AUTO_READ) {
|
||||
setAutoRead((Boolean) value);
|
||||
} else if (option == AUTO_CLOSE) {
|
||||
setAutoClose((Boolean) value);
|
||||
} else if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
|
||||
setWriteBufferHighWaterMark((Integer) value);
|
||||
} else if (option == WRITE_BUFFER_LOW_WATER_MARK) {
|
||||
setWriteBufferLowWaterMark((Integer) value);
|
||||
} else if (option == MESSAGE_SIZE_ESTIMATOR) {
|
||||
setMessageSizeEstimator((MessageSizeEstimator) value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected <T> void validate(ChannelOption<T> option, T value) {
|
||||
if (option == null) {
|
||||
throw new NullPointerException("option");
|
||||
}
|
||||
option.validate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConnectTimeoutMillis() {
|
||||
return connectTimeoutMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
if (connectTimeoutMillis < 0) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"connectTimeoutMillis: %d (expected: >= 0)", connectTimeoutMillis));
|
||||
}
|
||||
this.connectTimeoutMillis = connectTimeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxMessagesPerRead() {
|
||||
return maxMessagesPerRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
|
||||
if (maxMessagesPerRead <= 0) {
|
||||
throw new IllegalArgumentException("maxMessagesPerRead: " + maxMessagesPerRead + " (expected: > 0)");
|
||||
}
|
||||
this.maxMessagesPerRead = maxMessagesPerRead;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWriteSpinCount() {
|
||||
return writeSpinCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setWriteSpinCount(int writeSpinCount) {
|
||||
if (writeSpinCount <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"writeSpinCount must be a positive integer.");
|
||||
}
|
||||
this.writeSpinCount = writeSpinCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator getAllocator() {
|
||||
return allocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setAllocator(ByteBufAllocator allocator) {
|
||||
if (allocator == null) {
|
||||
throw new NullPointerException("allocator");
|
||||
}
|
||||
this.allocator = allocator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecvByteBufAllocator getRecvByteBufAllocator() {
|
||||
return rcvBufAllocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
|
||||
if (allocator == null) {
|
||||
throw new NullPointerException("allocator");
|
||||
}
|
||||
rcvBufAllocator = allocator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoRead() {
|
||||
return autoRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setAutoRead(boolean autoRead) {
|
||||
boolean oldAutoRead = this.autoRead;
|
||||
this.autoRead = autoRead;
|
||||
if (autoRead && !oldAutoRead) {
|
||||
channel.read();
|
||||
} else if (!autoRead && oldAutoRead) {
|
||||
autoReadCleared();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called once {@link #setAutoRead(boolean)} is called with {@code false} and {@link #isAutoRead()} was
|
||||
* {@code true} before.
|
||||
*/
|
||||
protected void autoReadCleared() { }
|
||||
|
||||
@Override
|
||||
public boolean isAutoClose() {
|
||||
return autoClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setAutoClose(boolean autoClose) {
|
||||
this.autoClose = autoClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWriteBufferHighWaterMark() {
|
||||
return writeBufferHighWaterMark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||
if (writeBufferHighWaterMark < getWriteBufferLowWaterMark()) {
|
||||
throw new IllegalArgumentException(
|
||||
"writeBufferHighWaterMark cannot be less than " +
|
||||
"writeBufferLowWaterMark (" + getWriteBufferLowWaterMark() + "): " +
|
||||
writeBufferHighWaterMark);
|
||||
}
|
||||
if (writeBufferHighWaterMark < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"writeBufferHighWaterMark must be >= 0");
|
||||
}
|
||||
this.writeBufferHighWaterMark = writeBufferHighWaterMark;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWriteBufferLowWaterMark() {
|
||||
return writeBufferLowWaterMark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||
if (writeBufferLowWaterMark > getWriteBufferHighWaterMark()) {
|
||||
throw new IllegalArgumentException(
|
||||
"writeBufferLowWaterMark cannot be greater than " +
|
||||
"writeBufferHighWaterMark (" + getWriteBufferHighWaterMark() + "): " +
|
||||
writeBufferLowWaterMark);
|
||||
}
|
||||
if (writeBufferLowWaterMark < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"writeBufferLowWaterMark must be >= 0");
|
||||
}
|
||||
this.writeBufferLowWaterMark = writeBufferLowWaterMark;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageSizeEstimator getMessageSizeEstimator() {
|
||||
return msgSizeEstimator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
|
||||
if (estimator == null) {
|
||||
throw new NullPointerException("estimator");
|
||||
}
|
||||
msgSizeEstimator = estimator;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.EventExecutorGroup;
|
||||
|
||||
final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
|
||||
|
||||
private final ChannelHandler handler;
|
||||
|
||||
DefaultChannelHandlerContext(
|
||||
DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) {
|
||||
super(pipeline, group, name, isInbound(handler), isOutbound(handler));
|
||||
if (handler == null) {
|
||||
throw new NullPointerException("handler");
|
||||
}
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler handler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static boolean isInbound(ChannelHandler handler) {
|
||||
return handler instanceof ChannelInboundHandler;
|
||||
}
|
||||
|
||||
private static boolean isOutbound(ChannelHandler handler) {
|
||||
return handler instanceof ChannelOutboundHandler;
|
||||
}
|
||||
}
|
1067
common/src/common/net/channel/DefaultChannelPipeline.java
Normal file
1067
common/src/common/net/channel/DefaultChannelPipeline.java
Normal file
File diff suppressed because it is too large
Load diff
159
common/src/common/net/channel/DefaultChannelPromise.java
Normal file
159
common/src/common/net/channel/DefaultChannelPromise.java
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.DefaultPromise;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
|
||||
/**
|
||||
* The default {@link ChannelPromise} implementation. It is recommended to use {@link Channel#newPromise()} to create
|
||||
* a new {@link ChannelPromise} rather than calling the constructor explicitly.
|
||||
*/
|
||||
public class DefaultChannelPromise extends DefaultPromise<Void> implements ChannelPromise {
|
||||
|
||||
private final Channel channel;
|
||||
// private long checkpoint;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel
|
||||
* the {@link Channel} associated with this future
|
||||
*/
|
||||
public DefaultChannelPromise(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel
|
||||
* the {@link Channel} associated with this future
|
||||
*/
|
||||
public DefaultChannelPromise(Channel channel, EventExecutor executor) {
|
||||
super(executor);
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventExecutor executor() {
|
||||
EventExecutor e = super.executor();
|
||||
if (e == null) {
|
||||
return channel().eventLoop();
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise setSuccess() {
|
||||
return setSuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise setSuccess(Void result) {
|
||||
super.setSuccess(result);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trySuccess() {
|
||||
return trySuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise setFailure(Throwable cause) {
|
||||
super.setFailure(cause);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
super.addListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
super.addListeners(listeners);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
super.removeListener(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
super.removeListeners(listeners);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise sync() throws InterruptedException {
|
||||
super.sync();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise syncUninterruptibly() {
|
||||
super.syncUninterruptibly();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise await() throws InterruptedException {
|
||||
super.await();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise awaitUninterruptibly() {
|
||||
super.awaitUninterruptibly();
|
||||
return this;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public long flushCheckpoint() {
|
||||
// return checkpoint;
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public void flushCheckpoint(long checkpoint) {
|
||||
// this.checkpoint = checkpoint;
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public ChannelPromise promise() {
|
||||
// return this;
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void checkDeadLock() {
|
||||
if (channel().isRegistered()) {
|
||||
super.checkDeadLock();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Default {@link MessageSizeEstimator} implementation which supports the estimation of the size of
|
||||
* {@link ByteBuf}, {@link ByteBufHolder} and {@link FileRegion}.
|
||||
*/
|
||||
public final class DefaultMessageSizeEstimator implements MessageSizeEstimator {
|
||||
|
||||
private static final class HandleImpl implements Handle {
|
||||
private final int unknownSize;
|
||||
|
||||
private HandleImpl(int unknownSize) {
|
||||
this.unknownSize = unknownSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size(Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
return ((ByteBuf) msg).readableBytes();
|
||||
}
|
||||
// if (msg instanceof ByteBufHolder) {
|
||||
// return ((ByteBufHolder) msg).content().readableBytes();
|
||||
// }
|
||||
// if (msg instanceof FileRegion) {
|
||||
// return 0;
|
||||
// }
|
||||
return unknownSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default implementation which returns {@code -1} for unknown messages.
|
||||
*/
|
||||
public static final MessageSizeEstimator DEFAULT = new DefaultMessageSizeEstimator(0);
|
||||
|
||||
private final Handle handle;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param unknownSize The size which is returned for unknown messages.
|
||||
*/
|
||||
public DefaultMessageSizeEstimator(int unknownSize) {
|
||||
if (unknownSize < 0) {
|
||||
throw new IllegalArgumentException("unknownSize: " + unknownSize + " (expected: >= 0)");
|
||||
}
|
||||
handle = new HandleImpl(unknownSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Handle newHandle() {
|
||||
return handle;
|
||||
}
|
||||
}
|
30
common/src/common/net/channel/EventLoop.java
Normal file
30
common/src/common/net/channel/EventLoop.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
|
||||
/**
|
||||
* Will handle all the I/O-Operations for a {@link Channel} once it was registered.
|
||||
*
|
||||
* One {@link EventLoop} instance will usually handle more then one {@link Channel} but this may depend on
|
||||
* implementation details and internals.
|
||||
*
|
||||
*/
|
||||
public interface EventLoop extends EventExecutor, EventLoopGroup {
|
||||
@Override
|
||||
EventLoopGroup parent();
|
||||
}
|
41
common/src/common/net/channel/EventLoopException.java
Normal file
41
common/src/common/net/channel/EventLoopException.java
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* Special {@link ChannelException} which will be thrown by {@link EventLoop} and {@link EventLoopGroup}
|
||||
* implementations when an error occurs.
|
||||
*/
|
||||
public class EventLoopException extends ChannelException {
|
||||
|
||||
private static final long serialVersionUID = -8969100344583703616L;
|
||||
|
||||
public EventLoopException() {
|
||||
}
|
||||
|
||||
public EventLoopException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public EventLoopException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EventLoopException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
43
common/src/common/net/channel/EventLoopGroup.java
Normal file
43
common/src/common/net/channel/EventLoopGroup.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.EventExecutorGroup;
|
||||
|
||||
/**
|
||||
* Special {@link EventExecutorGroup} which allows to register {@link Channel}'s that get
|
||||
* processed for later selection during the event loop.
|
||||
*
|
||||
*/
|
||||
public interface EventLoopGroup extends EventExecutorGroup {
|
||||
/**
|
||||
* Return the next {@link EventLoop} to use
|
||||
*/
|
||||
@Override
|
||||
EventLoop next();
|
||||
|
||||
/**
|
||||
* Register a {@link Channel} with this {@link EventLoop}. The returned {@link ChannelFuture}
|
||||
* will get notified once the registration was complete.
|
||||
*/
|
||||
ChannelFuture register(Channel channel);
|
||||
|
||||
/**
|
||||
* Register a {@link Channel} with this {@link EventLoop}. The passed {@link ChannelFuture}
|
||||
* will get notified once the registration was complete and also will get returned.
|
||||
*/
|
||||
ChannelFuture register(Channel channel, ChannelPromise promise);
|
||||
}
|
65
common/src/common/net/channel/FailedChannelFuture.java
Normal file
65
common/src/common/net/channel/FailedChannelFuture.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
* The {@link CompleteChannelFuture} which is failed already. It is
|
||||
* recommended to use {@link Channel#newFailedFuture(Throwable)}
|
||||
* instead of calling the constructor of this future.
|
||||
*/
|
||||
final class FailedChannelFuture extends CompleteChannelFuture {
|
||||
|
||||
private final Throwable cause;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel the {@link Channel} associated with this future
|
||||
* @param cause the cause of failure
|
||||
*/
|
||||
FailedChannelFuture(Channel channel, EventExecutor executor, Throwable cause) {
|
||||
super(channel, executor);
|
||||
if (cause == null) {
|
||||
throw new NullPointerException("cause");
|
||||
}
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable cause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture sync() {
|
||||
PlatformDependent.throwException(cause);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture syncUninterruptibly() {
|
||||
PlatformDependent.throwException(cause);
|
||||
return this;
|
||||
}
|
||||
}
|
39
common/src/common/net/channel/MessageSizeEstimator.java
Normal file
39
common/src/common/net/channel/MessageSizeEstimator.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* Responsible to estimate size of a message. The size represent how much memory the message will ca. reserve in
|
||||
* memory.
|
||||
*/
|
||||
public interface MessageSizeEstimator {
|
||||
|
||||
/**
|
||||
* Creates a new handle. The handle provides the actual operations.
|
||||
*/
|
||||
Handle newHandle();
|
||||
|
||||
interface Handle {
|
||||
|
||||
/**
|
||||
* Calculate the size of the given message.
|
||||
*
|
||||
* @param msg The message for which the size should be calculated
|
||||
* @return size The size in bytes. The returned size must be >= 0
|
||||
*/
|
||||
int size(Object msg);
|
||||
}
|
||||
}
|
71
common/src/common/net/channel/MultithreadEventLoopGroup.java
Normal file
71
common/src/common/net/channel/MultithreadEventLoopGroup.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import common.net.util.concurrent.DefaultThreadFactory;
|
||||
import common.net.util.concurrent.MultithreadEventExecutorGroup;
|
||||
import common.net.util.internal.SystemPropertyUtil;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link EventLoopGroup} implementations that handles their tasks with multiple threads at
|
||||
* the same time.
|
||||
*/
|
||||
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(MultithreadEventLoopGroup.class);
|
||||
|
||||
private static final int DEFAULT_EVENT_LOOP_THREADS;
|
||||
|
||||
static {
|
||||
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
|
||||
"game.net.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("-Dgame.net.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, ThreadFactory, Object...)}
|
||||
*/
|
||||
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
|
||||
super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadFactory newDefaultThreadFactory() {
|
||||
return new DefaultThreadFactory(getClass(), Thread.MAX_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop next() {
|
||||
return (EventLoop) super.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(Channel channel) {
|
||||
return next().register(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(Channel channel, ChannelPromise promise) {
|
||||
return next().register(channel, promise);
|
||||
}
|
||||
}
|
54
common/src/common/net/channel/RecvByteBufAllocator.java
Normal file
54
common/src/common/net/channel/RecvByteBufAllocator.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
|
||||
/**
|
||||
* Allocates a new receive buffer whose capacity is probably large enough to read all inbound data and small enough
|
||||
* not to waste its space.
|
||||
*/
|
||||
public interface RecvByteBufAllocator {
|
||||
|
||||
/**
|
||||
* Creates a new handle. The handle provides the actual operations and keeps the internal information which is
|
||||
* required for predicting an optimal buffer capacity.
|
||||
*/
|
||||
Handle newHandle();
|
||||
|
||||
interface Handle {
|
||||
/**
|
||||
* Creates a new receive buffer whose capacity is probably large enough to read all inbound data and small
|
||||
* enough not to waste its space.
|
||||
*/
|
||||
ByteBuf allocate(ByteBufAllocator alloc);
|
||||
|
||||
/**
|
||||
* Similar to {@link #allocate(ByteBufAllocator)} except that it does not allocate anything but just tells the
|
||||
* capacity.
|
||||
*/
|
||||
int guess();
|
||||
|
||||
/**
|
||||
* Records the the actual number of read bytes in the previous read operation so that the allocator allocates
|
||||
* the buffer with potentially more correct capacity.
|
||||
*
|
||||
* @param actualReadBytes the actual number of read bytes in the previous read operation
|
||||
*/
|
||||
void record(int actualReadBytes);
|
||||
}
|
||||
}
|
25
common/src/common/net/channel/ServerChannel.java
Normal file
25
common/src/common/net/channel/ServerChannel.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
/**
|
||||
* A {@link Channel} that accepts an incoming connection attempt and creates
|
||||
* its child {@link Channel}s by accepting them. {@link ServerSocketChannel} is
|
||||
* a good example.
|
||||
*/
|
||||
public interface ServerChannel extends Channel {
|
||||
// This is a tag interface.
|
||||
}
|
129
common/src/common/net/channel/SimpleChannelInboundHandler.java
Normal file
129
common/src/common/net/channel/SimpleChannelInboundHandler.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.ReferenceCountUtil;
|
||||
import common.net.util.internal.TypeParameterMatcher;
|
||||
|
||||
/**
|
||||
* {@link ChannelInboundHandlerAdapter} which allows to explicit only handle a specific type of messages.
|
||||
*
|
||||
* For example here is an implementation which only handle {@link String} messages.
|
||||
*
|
||||
* <pre>
|
||||
* public class StringHandler extends
|
||||
* {@link SimpleChannelInboundHandler}<{@link String}> {
|
||||
*
|
||||
* {@code @Override}
|
||||
* protected void channelRead0({@link ChannelHandlerContext} ctx, {@link String} message)
|
||||
* throws {@link Exception} {
|
||||
* System.out.println(message);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Be aware that depending of the constructor parameters it will release all handled messages by pass them to
|
||||
* {@link ReferenceCountUtil#release(Object)}. In this case you may need to use
|
||||
* {@link ReferenceCountUtil#retain(Object)} if you pass the object to the next handler in the {@link ChannelPipeline}.
|
||||
*
|
||||
* <h3>Forward compatibility notice</h3>
|
||||
* <p>
|
||||
* Please keep in mind that {@link #channelRead0(ChannelHandlerContext, I)} will be renamed to
|
||||
* {@code messageReceived(ChannelHandlerContext, I)} in 5.0.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final TypeParameterMatcher matcher;
|
||||
private final boolean autoRelease;
|
||||
|
||||
/**
|
||||
* @see {@link #SimpleChannelInboundHandler(boolean)} with {@code true} as boolean parameter.
|
||||
*/
|
||||
protected SimpleChannelInboundHandler() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance which will try to detect the types to match out of the type parameter of the class.
|
||||
*
|
||||
* @param autoRelease {@code true} if handled messages should be released automatically by pass them to
|
||||
* {@link ReferenceCountUtil#release(Object)}.
|
||||
*/
|
||||
protected SimpleChannelInboundHandler(boolean autoRelease) {
|
||||
matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
|
||||
this.autoRelease = autoRelease;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #SimpleChannelInboundHandler(Class, boolean)} with {@code true} as boolean value.
|
||||
*/
|
||||
protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType) {
|
||||
this(inboundMessageType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param inboundMessageType The type of messages to match
|
||||
* @param autoRelease {@code true} if handled messages should be released automatically by pass them to
|
||||
* {@link ReferenceCountUtil#release(Object)}.
|
||||
*/
|
||||
protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {
|
||||
matcher = TypeParameterMatcher.get(inboundMessageType);
|
||||
this.autoRelease = autoRelease;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
|
||||
* {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
*/
|
||||
public boolean acceptInboundMessage(Object msg) throws Exception {
|
||||
return matcher.match(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
boolean release = true;
|
||||
try {
|
||||
if (acceptInboundMessage(msg)) {
|
||||
|
||||
I imsg = (I) msg;
|
||||
channelRead0(ctx, imsg);
|
||||
} else {
|
||||
release = false;
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
} finally {
|
||||
if (autoRelease && release) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <strong>Please keep in mind that this method will be renamed to
|
||||
* {@code messageReceived(ChannelHandlerContext, I)} in 5.0.</strong>
|
||||
*
|
||||
* Is called for each message of type {@link I}.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
|
||||
* belongs to
|
||||
* @param msg the message to handle
|
||||
* @throws Exception is thrown if an error occurred
|
||||
*/
|
||||
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
|
||||
}
|
72
common/src/common/net/channel/SingleThreadEventLoop.java
Normal file
72
common/src/common/net/channel/SingleThreadEventLoop.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import common.net.util.concurrent.SingleThreadEventExecutor;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link EventLoop}'s that execute all its submitted tasks in a single thread.
|
||||
*
|
||||
*/
|
||||
public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
|
||||
|
||||
/**
|
||||
* @see {@link SingleThreadEventExecutor#SingleThreadEventExecutor(EventExecutorGroup, ThreadFactory, boolean)}
|
||||
*/
|
||||
protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
|
||||
super(parent, threadFactory, addTaskWakesUp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoopGroup parent() {
|
||||
return (EventLoopGroup) super.parent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop next() {
|
||||
return (EventLoop) super.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(Channel channel) {
|
||||
return register(channel, new DefaultChannelPromise(channel, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
|
||||
if (channel == null) {
|
||||
throw new NullPointerException("channel");
|
||||
}
|
||||
if (promise == null) {
|
||||
throw new NullPointerException("promise");
|
||||
}
|
||||
|
||||
channel.unsafe().register(this, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean wakesUpForTask(Runnable task) {
|
||||
return !(task instanceof NonWakeupRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface for {@linkRunnable} that will not trigger an {@link #wakeup(boolean)} in all cases.
|
||||
*/
|
||||
interface NonWakeupRunnable extends Runnable { }
|
||||
}
|
45
common/src/common/net/channel/SucceededChannelFuture.java
Normal file
45
common/src/common/net/channel/SucceededChannelFuture.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
|
||||
/**
|
||||
* The {@link CompleteChannelFuture} which is succeeded already. It is
|
||||
* recommended to use {@link Channel#newSucceededFuture()} instead of
|
||||
* calling the constructor of this future.
|
||||
*/
|
||||
final class SucceededChannelFuture extends CompleteChannelFuture {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel the {@link Channel} associated with this future
|
||||
*/
|
||||
SucceededChannelFuture(Channel channel, EventExecutor executor) {
|
||||
super(channel, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable cause() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
205
common/src/common/net/channel/VoidChannelPromise.java
Normal file
205
common/src/common/net/channel/VoidChannelPromise.java
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import common.net.util.concurrent.AbstractFuture;
|
||||
import common.net.util.concurrent.Future;
|
||||
import common.net.util.concurrent.GenericFutureListener;
|
||||
|
||||
final class VoidChannelPromise extends AbstractFuture<Void> implements ChannelPromise {
|
||||
|
||||
private final Channel channel;
|
||||
private final boolean fireException;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param channel the {@link Channel} associated with this future
|
||||
*/
|
||||
VoidChannelPromise(Channel channel, boolean fireException) {
|
||||
if (channel == null) {
|
||||
throw new NullPointerException("channel");
|
||||
}
|
||||
this.channel = channel;
|
||||
this.fireException = fireException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
fail();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
fail();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener) {
|
||||
// NOOP
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
|
||||
// NOOP
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise await() throws InterruptedException {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean await(long timeout, TimeUnit unit) {
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean await(long timeoutMillis) {
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise awaitUninterruptibly() {
|
||||
fail();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitUninterruptibly(long timeoutMillis) {
|
||||
fail();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setUncancellable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancellable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable cause() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise sync() {
|
||||
fail();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise syncUninterruptibly() {
|
||||
fail();
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public VoidChannelPromise setFailure(Throwable cause) {
|
||||
fireException(cause);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise setSuccess() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryFailure(Throwable cause) {
|
||||
fireException(cause);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trySuccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void fail() {
|
||||
throw new IllegalStateException("void future");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoidChannelPromise setSuccess(Void result) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trySuccess(Void result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void getNow() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fireException(Throwable cause) {
|
||||
// Only fire the exception if the channel is open and registered
|
||||
// if not the pipeline is not setup and so it would hit the tail
|
||||
// of the pipeline.
|
||||
// See https://github.com/netty/netty/issues/1517
|
||||
if (fireException && channel.isRegistered()) {
|
||||
channel.pipeline().fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
}
|
94
common/src/common/net/channel/local/LocalAddress.java
Normal file
94
common/src/common/net/channel/local/LocalAddress.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
|
||||
/**
|
||||
* An endpoint in the local transport. Each endpoint is identified by a unique
|
||||
* case-insensitive string.
|
||||
*/
|
||||
public final class LocalAddress extends SocketAddress implements Comparable<LocalAddress> {
|
||||
|
||||
private static final long serialVersionUID = 4644331421130916435L;
|
||||
|
||||
public static final LocalAddress ANY = new LocalAddress("ANY");
|
||||
|
||||
private final String id;
|
||||
private final String strVal;
|
||||
|
||||
/**
|
||||
* Creates a new ephemeral port based on the ID of the specified channel.
|
||||
* Note that we prepend an upper-case character so that it never conflicts with
|
||||
* the addresses created by a user, which are always lower-cased on construction time.
|
||||
*/
|
||||
LocalAddress(Channel channel) {
|
||||
StringBuilder buf = new StringBuilder(16);
|
||||
buf.append("local:E");
|
||||
buf.append(Long.toHexString(channel.hashCode() & 0xFFFFFFFFL | 0x100000000L));
|
||||
buf.setCharAt(7, ':');
|
||||
id = buf.substring(6);
|
||||
strVal = buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified ID.
|
||||
*/
|
||||
public LocalAddress(String id) {
|
||||
if (id == null) {
|
||||
throw new NullPointerException("id");
|
||||
}
|
||||
id = id.trim().toLowerCase();
|
||||
if (id.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty id");
|
||||
}
|
||||
this.id = id;
|
||||
strVal = "local:" + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of this address.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof LocalAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return id.equals(((LocalAddress) o).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LocalAddress o) {
|
||||
return id.compareTo(o.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strVal;
|
||||
}
|
||||
}
|
383
common/src/common/net/channel/local/LocalChannel.java
Normal file
383
common/src/common/net/channel/local/LocalChannel.java
Normal file
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.AlreadyConnectedException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.ConnectionPendingException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.Queue;
|
||||
|
||||
import common.net.channel.AbstractChannel;
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelConfig;
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.channel.ChannelMetadata;
|
||||
import common.net.channel.ChannelOutboundBuffer;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.ChannelPromise;
|
||||
import common.net.channel.DefaultChannelConfig;
|
||||
import common.net.channel.EventLoop;
|
||||
import common.net.channel.SingleThreadEventLoop;
|
||||
import common.net.util.ReferenceCountUtil;
|
||||
import common.net.util.concurrent.SingleThreadEventExecutor;
|
||||
import common.net.util.internal.InternalThreadLocalMap;
|
||||
|
||||
/**
|
||||
* A {@link Channel} for the local transport.
|
||||
*/
|
||||
public class LocalChannel extends AbstractChannel {
|
||||
|
||||
private static final ChannelMetadata METADATA = new ChannelMetadata(false);
|
||||
|
||||
private static final int MAX_READER_STACK_DEPTH = 8;
|
||||
|
||||
private final ChannelConfig config = new DefaultChannelConfig(this);
|
||||
private final Queue<Object> inboundBuffer = new ArrayDeque<Object>();
|
||||
private final Runnable readTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ChannelPipeline pipeline = pipeline();
|
||||
for (;;) {
|
||||
Object m = inboundBuffer.poll();
|
||||
if (m == null) {
|
||||
break;
|
||||
}
|
||||
pipeline.fireChannelRead(m);
|
||||
}
|
||||
pipeline.fireChannelReadComplete();
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable shutdownHook = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
unsafe().close(unsafe().voidPromise());
|
||||
}
|
||||
};
|
||||
|
||||
private volatile int state; // 0 - open, 1 - bound, 2 - connected, 3 - closed
|
||||
private volatile LocalChannel peer;
|
||||
private volatile LocalAddress localAddress;
|
||||
private volatile LocalAddress remoteAddress;
|
||||
private volatile ChannelPromise connectPromise;
|
||||
private volatile boolean readInProgress;
|
||||
private volatile boolean registerInProgress;
|
||||
|
||||
public LocalChannel() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
LocalChannel(LocalServerChannel parent, LocalChannel peer) {
|
||||
super(parent);
|
||||
this.peer = peer;
|
||||
localAddress = parent.localAddress();
|
||||
remoteAddress = peer.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelMetadata metadata() {
|
||||
return METADATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalServerChannel parent() {
|
||||
return (LocalServerChannel) super.parent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAddress localAddress() {
|
||||
return (LocalAddress) super.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAddress remoteAddress() {
|
||||
return (LocalAddress) super.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return state < 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return state == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractUnsafe newUnsafe() {
|
||||
return new LocalUnsafe();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCompatible(EventLoop loop) {
|
||||
return loop instanceof SingleThreadEventLoop;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SocketAddress localAddress0() {
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SocketAddress remoteAddress0() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRegister() throws Exception {
|
||||
// Check if both peer and parent are non-null because this channel was created by a LocalServerChannel.
|
||||
// This is needed as a peer may not be null also if a LocalChannel was connected before and
|
||||
// deregistered / registered later again.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2400
|
||||
if (peer != null && parent() != null) {
|
||||
// Store the peer in a local variable as it may be set to null if doClose() is called.
|
||||
// Because of this we also set registerInProgress to true as we check for this in doClose() and make sure
|
||||
// we delay the fireChannelInactive() to be fired after the fireChannelActive() and so keep the correct
|
||||
// order of events.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2144
|
||||
final LocalChannel peer = this.peer;
|
||||
registerInProgress = true;
|
||||
state = 2;
|
||||
|
||||
peer.remoteAddress = parent().localAddress();
|
||||
peer.state = 2;
|
||||
|
||||
// Always call peer.eventLoop().execute() even if peer.eventLoop().inEventLoop() is true.
|
||||
// This ensures that if both channels are on the same event loop, the peer's channelActive
|
||||
// event is triggered *after* this channel's channelRegistered event, so that this channel's
|
||||
// pipeline is fully initialized by ChannelInitializer before any channelRead events.
|
||||
peer.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
registerInProgress = false;
|
||||
peer.pipeline().fireChannelActive();
|
||||
peer.connectPromise.setSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBind(SocketAddress localAddress) throws Exception {
|
||||
this.localAddress =
|
||||
LocalChannelRegistry.register(this, this.localAddress,
|
||||
localAddress);
|
||||
state = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDisconnect() throws Exception {
|
||||
doClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws Exception {
|
||||
if (state <= 2) {
|
||||
// Update all internal state before the closeFuture is notified.
|
||||
if (localAddress != null) {
|
||||
if (parent() == null) {
|
||||
LocalChannelRegistry.unregister(localAddress);
|
||||
}
|
||||
localAddress = null;
|
||||
}
|
||||
state = 3;
|
||||
}
|
||||
|
||||
final LocalChannel peer = this.peer;
|
||||
if (peer != null && peer.isActive()) {
|
||||
// Need to execute the close in the correct EventLoop
|
||||
// See https://github.com/netty/netty/issues/1777
|
||||
EventLoop eventLoop = peer.eventLoop();
|
||||
|
||||
// Also check if the registration was not done yet. In this case we submit the close to the EventLoop
|
||||
// to make sure it is run after the registration completes.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2144
|
||||
if (eventLoop.inEventLoop() && !registerInProgress) {
|
||||
peer.unsafe().close(unsafe().voidPromise());
|
||||
} else {
|
||||
peer.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
peer.unsafe().close(unsafe().voidPromise());
|
||||
}
|
||||
});
|
||||
}
|
||||
this.peer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDeregister() throws Exception {
|
||||
// Just remove the shutdownHook as this Channel may be closed later or registered to another EventLoop
|
||||
((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBeginRead() throws Exception {
|
||||
if (readInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelPipeline pipeline = pipeline();
|
||||
Queue<Object> inboundBuffer = this.inboundBuffer;
|
||||
if (inboundBuffer.isEmpty()) {
|
||||
readInProgress = true;
|
||||
return;
|
||||
}
|
||||
|
||||
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
|
||||
final Integer stackDepth = threadLocals.localChannelReaderStackDepth();
|
||||
if (stackDepth < MAX_READER_STACK_DEPTH) {
|
||||
threadLocals.setLocalChannelReaderStackDepth(stackDepth + 1);
|
||||
try {
|
||||
for (;;) {
|
||||
Object received = inboundBuffer.poll();
|
||||
if (received == null) {
|
||||
break;
|
||||
}
|
||||
pipeline.fireChannelRead(received);
|
||||
}
|
||||
pipeline.fireChannelReadComplete();
|
||||
} finally {
|
||||
threadLocals.setLocalChannelReaderStackDepth(stackDepth);
|
||||
}
|
||||
} else {
|
||||
eventLoop().execute(readTask);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
||||
if (state < 2) {
|
||||
throw new NotYetConnectedException();
|
||||
}
|
||||
if (state > 2) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
|
||||
final LocalChannel peer = this.peer;
|
||||
final ChannelPipeline peerPipeline = peer.pipeline();
|
||||
final EventLoop peerLoop = peer.eventLoop();
|
||||
|
||||
if (peerLoop == eventLoop()) {
|
||||
for (;;) {
|
||||
Object msg = in.current();
|
||||
if (msg == null) {
|
||||
break;
|
||||
}
|
||||
peer.inboundBuffer.add(msg);
|
||||
ReferenceCountUtil.retain(msg);
|
||||
in.remove();
|
||||
}
|
||||
finishPeerRead(peer, peerPipeline);
|
||||
} else {
|
||||
// Use a copy because the original msgs will be recycled by AbstractChannel.
|
||||
final Object[] msgsCopy = new Object[in.size()];
|
||||
for (int i = 0; i < msgsCopy.length; i ++) {
|
||||
msgsCopy[i] = ReferenceCountUtil.retain(in.current());
|
||||
in.remove();
|
||||
}
|
||||
|
||||
peerLoop.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Collections.addAll(peer.inboundBuffer, msgsCopy);
|
||||
finishPeerRead(peer, peerPipeline);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void finishPeerRead(LocalChannel peer, ChannelPipeline peerPipeline) {
|
||||
if (peer.readInProgress) {
|
||||
peer.readInProgress = false;
|
||||
for (;;) {
|
||||
Object received = peer.inboundBuffer.poll();
|
||||
if (received == null) {
|
||||
break;
|
||||
}
|
||||
peerPipeline.fireChannelRead(received);
|
||||
}
|
||||
peerPipeline.fireChannelReadComplete();
|
||||
}
|
||||
}
|
||||
|
||||
private class LocalUnsafe extends AbstractUnsafe {
|
||||
|
||||
@Override
|
||||
public void connect(final SocketAddress remoteAddress,
|
||||
SocketAddress localAddress, final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable() || !ensureOpen(promise)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == 2) {
|
||||
Exception cause = new AlreadyConnectedException();
|
||||
safeSetFailure(promise, cause);
|
||||
pipeline().fireExceptionCaught(cause);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectPromise != null) {
|
||||
throw new ConnectionPendingException();
|
||||
}
|
||||
|
||||
connectPromise = promise;
|
||||
|
||||
if (state != 1) {
|
||||
// Not bound yet and no localAddress specified - get one.
|
||||
if (localAddress == null) {
|
||||
localAddress = new LocalAddress(LocalChannel.this);
|
||||
}
|
||||
}
|
||||
|
||||
if (localAddress != null) {
|
||||
try {
|
||||
doBind(localAddress);
|
||||
} catch (Throwable t) {
|
||||
safeSetFailure(promise, t);
|
||||
close(voidPromise());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
|
||||
if (!(boundChannel instanceof LocalServerChannel)) {
|
||||
Exception cause = new ChannelException("connection refused");
|
||||
safeSetFailure(promise, cause);
|
||||
close(voidPromise());
|
||||
return;
|
||||
}
|
||||
|
||||
LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
|
||||
peer = serverChannel.serve(LocalChannel.this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
final class LocalChannelRegistry {
|
||||
|
||||
private static final ConcurrentMap<LocalAddress, Channel> boundChannels = PlatformDependent.newConcurrentHashMap();
|
||||
|
||||
static LocalAddress register(
|
||||
Channel channel, LocalAddress oldLocalAddress, SocketAddress localAddress) {
|
||||
if (oldLocalAddress != null) {
|
||||
throw new ChannelException("already bound");
|
||||
}
|
||||
if (!(localAddress instanceof LocalAddress)) {
|
||||
throw new ChannelException("unsupported address type: " + StringUtil.simpleClassName(localAddress));
|
||||
}
|
||||
|
||||
LocalAddress addr = (LocalAddress) localAddress;
|
||||
if (LocalAddress.ANY.equals(addr)) {
|
||||
addr = new LocalAddress(channel);
|
||||
}
|
||||
|
||||
Channel boundChannel = boundChannels.putIfAbsent(addr, channel);
|
||||
if (boundChannel != null) {
|
||||
throw new ChannelException("address already in use by: " + boundChannel);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
static Channel get(SocketAddress localAddress) {
|
||||
return boundChannels.get(localAddress);
|
||||
}
|
||||
|
||||
static void unregister(LocalAddress localAddress) {
|
||||
boundChannels.remove(localAddress);
|
||||
}
|
||||
|
||||
private LocalChannelRegistry() {
|
||||
// Unused
|
||||
}
|
||||
}
|
42
common/src/common/net/channel/local/LocalEventLoop.java
Normal file
42
common/src/common/net/channel/local/LocalEventLoop.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import common.net.channel.SingleThreadEventLoop;
|
||||
|
||||
final class LocalEventLoop extends SingleThreadEventLoop {
|
||||
|
||||
LocalEventLoop(LocalEventLoopGroup parent, ThreadFactory threadFactory) {
|
||||
super(parent, threadFactory, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
for (;;) {
|
||||
Runnable task = takeTask();
|
||||
if (task != null) {
|
||||
task.run();
|
||||
updateLastExecutionTime();
|
||||
}
|
||||
|
||||
if (confirmShutdown()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
common/src/common/net/channel/local/LocalEventLoopGroup.java
Normal file
59
common/src/common/net/channel/local/LocalEventLoopGroup.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import common.net.channel.MultithreadEventLoopGroup;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
|
||||
/**
|
||||
* {@link MultithreadEventLoopGroup} which must be used for the local transport.
|
||||
*/
|
||||
public class LocalEventLoopGroup extends MultithreadEventLoopGroup {
|
||||
|
||||
/**
|
||||
* Create a new instance with the default number of threads.
|
||||
*/
|
||||
public LocalEventLoopGroup() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param nThreads the number of threads to use
|
||||
*/
|
||||
public LocalEventLoopGroup(int nThreads) {
|
||||
this(nThreads, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param nThreads the number of threads to use
|
||||
* @param threadFactory the {@link ThreadFactory} or {@code null} to use the default
|
||||
*/
|
||||
public LocalEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
|
||||
super(nThreads, threadFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventExecutor newChild(
|
||||
ThreadFactory threadFactory, Object... args) throws Exception {
|
||||
return new LocalEventLoop(this, threadFactory);
|
||||
}
|
||||
}
|
164
common/src/common/net/channel/local/LocalServerChannel.java
Normal file
164
common/src/common/net/channel/local/LocalServerChannel.java
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.local;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
import common.net.channel.AbstractServerChannel;
|
||||
import common.net.channel.ChannelConfig;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.DefaultChannelConfig;
|
||||
import common.net.channel.EventLoop;
|
||||
import common.net.channel.SingleThreadEventLoop;
|
||||
import common.net.util.concurrent.SingleThreadEventExecutor;
|
||||
|
||||
/**
|
||||
* A {@link ServerChannel} for the local transport which allows in VM communication.
|
||||
*/
|
||||
public class LocalServerChannel extends AbstractServerChannel {
|
||||
|
||||
private final ChannelConfig config = new DefaultChannelConfig(this);
|
||||
private final Queue<Object> inboundBuffer = new ArrayDeque<Object>();
|
||||
private final Runnable shutdownHook = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
unsafe().close(unsafe().voidPromise());
|
||||
}
|
||||
};
|
||||
|
||||
private volatile int state; // 0 - open, 1 - active, 2 - closed
|
||||
private volatile LocalAddress localAddress;
|
||||
private volatile boolean acceptInProgress;
|
||||
|
||||
@Override
|
||||
public ChannelConfig config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAddress localAddress() {
|
||||
return (LocalAddress) super.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAddress remoteAddress() {
|
||||
return (LocalAddress) super.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return state < 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return state == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCompatible(EventLoop loop) {
|
||||
return loop instanceof SingleThreadEventLoop;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SocketAddress localAddress0() {
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRegister() throws Exception {
|
||||
((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBind(SocketAddress localAddress) throws Exception {
|
||||
this.localAddress = LocalChannelRegistry.register(this, this.localAddress, localAddress);
|
||||
state = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws Exception {
|
||||
if (state <= 1) {
|
||||
// Update all internal state before the closeFuture is notified.
|
||||
if (localAddress != null) {
|
||||
LocalChannelRegistry.unregister(localAddress);
|
||||
localAddress = null;
|
||||
}
|
||||
state = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDeregister() throws Exception {
|
||||
((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBeginRead() throws Exception {
|
||||
if (acceptInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
Queue<Object> inboundBuffer = this.inboundBuffer;
|
||||
if (inboundBuffer.isEmpty()) {
|
||||
acceptInProgress = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelPipeline pipeline = pipeline();
|
||||
for (;;) {
|
||||
Object m = inboundBuffer.poll();
|
||||
if (m == null) {
|
||||
break;
|
||||
}
|
||||
pipeline.fireChannelRead(m);
|
||||
}
|
||||
pipeline.fireChannelReadComplete();
|
||||
}
|
||||
|
||||
LocalChannel serve(final LocalChannel peer) {
|
||||
final LocalChannel child = new LocalChannel(this, peer);
|
||||
if (eventLoop().inEventLoop()) {
|
||||
serve0(child);
|
||||
} else {
|
||||
eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serve0(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
private void serve0(final LocalChannel child) {
|
||||
inboundBuffer.add(child);
|
||||
if (acceptInProgress) {
|
||||
acceptInProgress = false;
|
||||
ChannelPipeline pipeline = pipeline();
|
||||
for (;;) {
|
||||
Object m = inboundBuffer.poll();
|
||||
if (m == null) {
|
||||
break;
|
||||
}
|
||||
pipeline.fireChannelRead(m);
|
||||
}
|
||||
pipeline.fireChannelReadComplete();
|
||||
}
|
||||
}
|
||||
}
|
347
common/src/common/net/channel/nio/AbstractNioByteChannel.java
Normal file
347
common/src/common/net/channel/nio/AbstractNioByteChannel.java
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelConfig;
|
||||
import common.net.channel.ChannelOption;
|
||||
import common.net.channel.ChannelOutboundBuffer;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.RecvByteBufAllocator;
|
||||
import common.net.channel.socket.ChannelInputShutdownEvent;
|
||||
import common.net.util.internal.StringUtil;
|
||||
|
||||
/**
|
||||
* {@link AbstractNioChannel} base class for {@link Channel}s that operate on bytes.
|
||||
*/
|
||||
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
|
||||
|
||||
private static final String EXPECTED_TYPES =
|
||||
" (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ')'; // ", " +
|
||||
// StringUtil.simpleClassName(FileRegion.class) + ')';
|
||||
|
||||
private Runnable flushTask;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
|
||||
* @param ch the underlying {@link SelectableChannel} on which it operates
|
||||
*/
|
||||
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
|
||||
super(parent, ch, SelectionKey.OP_READ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractNioUnsafe newUnsafe() {
|
||||
return new NioByteUnsafe();
|
||||
}
|
||||
|
||||
private final class NioByteUnsafe extends AbstractNioUnsafe {
|
||||
private RecvByteBufAllocator.Handle allocHandle;
|
||||
|
||||
private void closeOnRead(ChannelPipeline pipeline) {
|
||||
SelectionKey key = selectionKey();
|
||||
setInputShutdown();
|
||||
if (isOpen()) {
|
||||
if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
|
||||
key.interestOps(key.interestOps() & ~readInterestOp);
|
||||
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||
} else {
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReadException(ChannelPipeline pipeline,
|
||||
ByteBuf byteBuf, Throwable cause, boolean close) {
|
||||
if (byteBuf != null) {
|
||||
if (byteBuf.isReadable()) {
|
||||
setReadPending(false);
|
||||
pipeline.fireChannelRead(byteBuf);
|
||||
} else {
|
||||
byteBuf.release();
|
||||
}
|
||||
}
|
||||
pipeline.fireChannelReadComplete();
|
||||
pipeline.fireExceptionCaught(cause);
|
||||
if (close || cause instanceof IOException) {
|
||||
closeOnRead(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read() {
|
||||
final ChannelConfig config = config();
|
||||
if (!config.isAutoRead() && !isReadPending()) {
|
||||
// ChannelConfig.setAutoRead(false) was called in the meantime
|
||||
removeReadOp();
|
||||
return;
|
||||
}
|
||||
|
||||
final ChannelPipeline pipeline = pipeline();
|
||||
final ByteBufAllocator allocator = config.getAllocator();
|
||||
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
|
||||
RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
|
||||
if (allocHandle == null) {
|
||||
this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
|
||||
}
|
||||
|
||||
ByteBuf byteBuf = null;
|
||||
int messages = 0;
|
||||
boolean close = false;
|
||||
try {
|
||||
int totalReadAmount = 0;
|
||||
boolean readPendingReset = false;
|
||||
do {
|
||||
byteBuf = allocHandle.allocate(allocator);
|
||||
int writable = byteBuf.writableBytes();
|
||||
int localReadAmount = doReadBytes(byteBuf);
|
||||
if (localReadAmount <= 0) {
|
||||
// not was read release the buffer
|
||||
byteBuf.release();
|
||||
close = localReadAmount < 0;
|
||||
break;
|
||||
}
|
||||
if (!readPendingReset) {
|
||||
readPendingReset = true;
|
||||
setReadPending(false);
|
||||
}
|
||||
pipeline.fireChannelRead(byteBuf);
|
||||
byteBuf = null;
|
||||
|
||||
if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
|
||||
// Avoid overflow.
|
||||
totalReadAmount = Integer.MAX_VALUE;
|
||||
break;
|
||||
}
|
||||
|
||||
totalReadAmount += localReadAmount;
|
||||
|
||||
// stop reading
|
||||
if (!config.isAutoRead()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (localReadAmount < writable) {
|
||||
// Read less than what the buffer can hold,
|
||||
// which might mean we drained the recv buffer completely.
|
||||
break;
|
||||
}
|
||||
} while (++ messages < maxMessagesPerRead);
|
||||
|
||||
pipeline.fireChannelReadComplete();
|
||||
allocHandle.record(totalReadAmount);
|
||||
|
||||
if (close) {
|
||||
closeOnRead(pipeline);
|
||||
close = false;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
handleReadException(pipeline, byteBuf, t, close);
|
||||
} finally {
|
||||
// Check if there is a readPending which was not processed yet.
|
||||
// This could be for two reasons:
|
||||
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
|
||||
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2254
|
||||
if (!config.isAutoRead() && !isReadPending()) {
|
||||
removeReadOp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
||||
int writeSpinCount = -1;
|
||||
|
||||
for (;;) {
|
||||
Object msg = in.current();
|
||||
if (msg == null) {
|
||||
// Wrote all messages.
|
||||
clearOpWrite();
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg instanceof ByteBuf) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
int readableBytes = buf.readableBytes();
|
||||
if (readableBytes == 0) {
|
||||
in.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean setOpWrite = false;
|
||||
boolean done = false;
|
||||
long flushedAmount = 0;
|
||||
if (writeSpinCount == -1) {
|
||||
writeSpinCount = config().getWriteSpinCount();
|
||||
}
|
||||
for (int i = writeSpinCount - 1; i >= 0; i --) {
|
||||
int localFlushedAmount = doWriteBytes(buf);
|
||||
if (localFlushedAmount == 0) {
|
||||
setOpWrite = true;
|
||||
break;
|
||||
}
|
||||
|
||||
flushedAmount += localFlushedAmount;
|
||||
if (!buf.isReadable()) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
in.progress(flushedAmount);
|
||||
|
||||
if (done) {
|
||||
in.remove();
|
||||
} else {
|
||||
incompleteWrite(setOpWrite);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// else if (msg instanceof FileRegion) {
|
||||
// FileRegion region = (FileRegion) msg;
|
||||
// boolean setOpWrite = false;
|
||||
// boolean done = false;
|
||||
// long flushedAmount = 0;
|
||||
// if (writeSpinCount == -1) {
|
||||
// writeSpinCount = config().getWriteSpinCount();
|
||||
// }
|
||||
// for (int i = writeSpinCount - 1; i >= 0; i --) {
|
||||
// long localFlushedAmount = doWriteFileRegion(region);
|
||||
// if (localFlushedAmount == 0) {
|
||||
// setOpWrite = true;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// flushedAmount += localFlushedAmount;
|
||||
// if (region.transfered() >= region.count()) {
|
||||
// done = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// in.progress(flushedAmount);
|
||||
//
|
||||
// if (done) {
|
||||
// in.remove();
|
||||
// } else {
|
||||
// incompleteWrite(setOpWrite);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
else {
|
||||
// Should not reach here.
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Object filterOutboundMessage(Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
if (buf.isDirect()) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
return newDirectBuffer(buf);
|
||||
}
|
||||
|
||||
// if (msg instanceof FileRegion) {
|
||||
// return msg;
|
||||
// }
|
||||
|
||||
throw new UnsupportedOperationException(
|
||||
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
|
||||
}
|
||||
|
||||
protected final void incompleteWrite(boolean setOpWrite) {
|
||||
// Did not write completely.
|
||||
if (setOpWrite) {
|
||||
setOpWrite();
|
||||
} else {
|
||||
// Schedule flush again later so other tasks can be picked up in the meantime
|
||||
Runnable flushTask = this.flushTask;
|
||||
if (flushTask == null) {
|
||||
flushTask = this.flushTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
flush();
|
||||
}
|
||||
};
|
||||
}
|
||||
eventLoop().execute(flushTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a {@link FileRegion}
|
||||
*
|
||||
* @param region the {@link FileRegion} from which the bytes should be written
|
||||
* @return amount the amount of written bytes
|
||||
*/
|
||||
// protected abstract long doWriteFileRegion(FileRegion region) throws Exception;
|
||||
|
||||
/**
|
||||
* Read bytes into the given {@link ByteBuf} and return the amount.
|
||||
*/
|
||||
protected abstract int doReadBytes(ByteBuf buf) throws Exception;
|
||||
|
||||
/**
|
||||
* Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}.
|
||||
* @param buf the {@link ByteBuf} from which the bytes should be written
|
||||
* @return amount the amount of written bytes
|
||||
*/
|
||||
protected abstract int doWriteBytes(ByteBuf buf) throws Exception;
|
||||
|
||||
protected final void setOpWrite() {
|
||||
final SelectionKey key = selectionKey();
|
||||
// Check first if the key is still valid as it may be canceled as part of the deregistration
|
||||
// from the EventLoop
|
||||
// See https://github.com/netty/netty/issues/2104
|
||||
if (!key.isValid()) {
|
||||
return;
|
||||
}
|
||||
final int interestOps = key.interestOps();
|
||||
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
||||
key.interestOps(interestOps | SelectionKey.OP_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void clearOpWrite() {
|
||||
final SelectionKey key = selectionKey();
|
||||
// Check first if the key is still valid as it may be canceled as part of the deregistration
|
||||
// from the EventLoop
|
||||
// See https://github.com/netty/netty/issues/2104
|
||||
if (!key.isValid()) {
|
||||
return;
|
||||
}
|
||||
final int interestOps = key.interestOps();
|
||||
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
||||
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
|
||||
}
|
||||
}
|
||||
}
|
460
common/src/common/net/channel/nio/AbstractNioChannel.java
Normal file
460
common/src/common/net/channel/nio/AbstractNioChannel.java
Normal file
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import common.net.buffer.ByteBuf;
|
||||
import common.net.buffer.ByteBufAllocator;
|
||||
import common.net.buffer.ByteBufUtil;
|
||||
import common.net.buffer.Unpooled;
|
||||
import common.net.channel.AbstractChannel;
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.channel.ChannelFuture;
|
||||
import common.net.channel.ChannelFutureListener;
|
||||
import common.net.channel.ChannelPromise;
|
||||
import common.net.channel.ConnectTimeoutException;
|
||||
import common.net.channel.EventLoop;
|
||||
import common.net.util.ReferenceCountUtil;
|
||||
import common.net.util.ReferenceCounted;
|
||||
import common.net.util.internal.OneTimeTask;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link Channel} implementations which use a Selector based approach.
|
||||
*/
|
||||
public abstract class AbstractNioChannel extends AbstractChannel {
|
||||
|
||||
private static final InternalLogger logger =
|
||||
InternalLoggerFactory.getInstance(AbstractNioChannel.class);
|
||||
|
||||
private final SelectableChannel ch;
|
||||
protected final int readInterestOp;
|
||||
volatile SelectionKey selectionKey;
|
||||
private volatile boolean inputShutdown;
|
||||
private volatile boolean readPending;
|
||||
|
||||
/**
|
||||
* The future of the current connection attempt. If not null, subsequent
|
||||
* connection attempts will fail.
|
||||
*/
|
||||
private ChannelPromise connectPromise;
|
||||
private ScheduledFuture<?> connectTimeoutFuture;
|
||||
private SocketAddress requestedRemoteAddress;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
|
||||
* @param ch the underlying {@link SelectableChannel} on which it operates
|
||||
* @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
|
||||
*/
|
||||
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
|
||||
super(parent);
|
||||
this.ch = ch;
|
||||
this.readInterestOp = readInterestOp;
|
||||
try {
|
||||
ch.configureBlocking(false);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
ch.close();
|
||||
} catch (IOException e2) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(
|
||||
"Failed to close a partially initialized socket.", e2);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ChannelException("Failed to enter non-blocking mode.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return ch.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NioUnsafe unsafe() {
|
||||
return (NioUnsafe) super.unsafe();
|
||||
}
|
||||
|
||||
protected SelectableChannel javaChannel() {
|
||||
return ch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NioEventLoop eventLoop() {
|
||||
return (NioEventLoop) super.eventLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current {@link SelectionKey}
|
||||
*/
|
||||
protected SelectionKey selectionKey() {
|
||||
assert selectionKey != null;
|
||||
return selectionKey;
|
||||
}
|
||||
|
||||
protected boolean isReadPending() {
|
||||
return readPending;
|
||||
}
|
||||
|
||||
protected void setReadPending(boolean readPending) {
|
||||
this.readPending = readPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the input of this {@link Channel} is shutdown
|
||||
*/
|
||||
protected boolean isInputShutdown() {
|
||||
return inputShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the input of this {@link Channel}.
|
||||
*/
|
||||
void setInputShutdown() {
|
||||
inputShutdown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
|
||||
*/
|
||||
public interface NioUnsafe extends Unsafe {
|
||||
/**
|
||||
* Return underlying {@link SelectableChannel}
|
||||
*/
|
||||
SelectableChannel ch();
|
||||
|
||||
/**
|
||||
* Finish connect
|
||||
*/
|
||||
void finishConnect();
|
||||
|
||||
/**
|
||||
* Read from underlying {@link SelectableChannel}
|
||||
*/
|
||||
void read();
|
||||
|
||||
void forceFlush();
|
||||
}
|
||||
|
||||
protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe {
|
||||
|
||||
protected final void removeReadOp() {
|
||||
SelectionKey key = selectionKey();
|
||||
// Check first if the key is still valid as it may be canceled as part of the deregistration
|
||||
// from the EventLoop
|
||||
// See https://github.com/netty/netty/issues/2104
|
||||
if (!key.isValid()) {
|
||||
return;
|
||||
}
|
||||
int interestOps = key.interestOps();
|
||||
if ((interestOps & readInterestOp) != 0) {
|
||||
// only remove readInterestOp if needed
|
||||
key.interestOps(interestOps & ~readInterestOp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SelectableChannel ch() {
|
||||
return javaChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void connect(
|
||||
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
|
||||
if (!promise.setUncancellable() || !ensureOpen(promise)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (connectPromise != null) {
|
||||
throw new IllegalStateException("connection attempt already made");
|
||||
}
|
||||
|
||||
boolean wasActive = isActive();
|
||||
if (doConnect(remoteAddress, localAddress)) {
|
||||
fulfillConnectPromise(promise, wasActive);
|
||||
} else {
|
||||
connectPromise = promise;
|
||||
requestedRemoteAddress = remoteAddress;
|
||||
|
||||
// Schedule connect timeout.
|
||||
int connectTimeoutMillis = config().getConnectTimeoutMillis();
|
||||
if (connectTimeoutMillis > 0) {
|
||||
connectTimeoutFuture = eventLoop().schedule(new OneTimeTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
|
||||
ConnectTimeoutException cause =
|
||||
new ConnectTimeoutException("connection timed out: " + remoteAddress);
|
||||
if (connectPromise != null && connectPromise.tryFailure(cause)) {
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isCancelled()) {
|
||||
if (connectTimeoutFuture != null) {
|
||||
connectTimeoutFuture.cancel(false);
|
||||
}
|
||||
connectPromise = null;
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof ConnectException) {
|
||||
Throwable newT = new ConnectException(t.getMessage() + ": " + remoteAddress);
|
||||
newT.setStackTrace(t.getStackTrace());
|
||||
t = newT;
|
||||
}
|
||||
promise.tryFailure(t);
|
||||
closeIfClosed();
|
||||
}
|
||||
}
|
||||
|
||||
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
|
||||
if (promise == null) {
|
||||
// Closed via cancellation and the promise has been notified already.
|
||||
return;
|
||||
}
|
||||
|
||||
// trySuccess() will return false if a user cancelled the connection attempt.
|
||||
boolean promiseSet = promise.trySuccess();
|
||||
|
||||
// Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
|
||||
// because what happened is what happened.
|
||||
if (!wasActive && isActive()) {
|
||||
pipeline().fireChannelActive();
|
||||
}
|
||||
|
||||
// If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
|
||||
if (!promiseSet) {
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) {
|
||||
if (promise == null) {
|
||||
// Closed via cancellation and the promise has been notified already.
|
||||
return;
|
||||
}
|
||||
|
||||
// Use tryFailure() instead of setFailure() to avoid the race against cancel().
|
||||
promise.tryFailure(cause);
|
||||
closeIfClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void finishConnect() {
|
||||
// Note this method is invoked by the event loop only if the connection attempt was
|
||||
// neither cancelled nor timed out.
|
||||
|
||||
assert eventLoop().inEventLoop();
|
||||
|
||||
try {
|
||||
boolean wasActive = isActive();
|
||||
doFinishConnect();
|
||||
fulfillConnectPromise(connectPromise, wasActive);
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof ConnectException) {
|
||||
Throwable newT = new ConnectException(t.getMessage() + ": " + requestedRemoteAddress);
|
||||
newT.setStackTrace(t.getStackTrace());
|
||||
t = newT;
|
||||
}
|
||||
|
||||
fulfillConnectPromise(connectPromise, t);
|
||||
} finally {
|
||||
// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
|
||||
// See https://github.com/netty/netty/issues/1770
|
||||
if (connectTimeoutFuture != null) {
|
||||
connectTimeoutFuture.cancel(false);
|
||||
}
|
||||
connectPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void flush0() {
|
||||
// Flush immediately only when there's no pending flush.
|
||||
// If there's a pending flush operation, event loop will call forceFlush() later,
|
||||
// and thus there's no need to call it now.
|
||||
if (isFlushPending()) {
|
||||
return;
|
||||
}
|
||||
super.flush0();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void forceFlush() {
|
||||
// directly call super.flush0() to force a flush now
|
||||
super.flush0();
|
||||
}
|
||||
|
||||
private boolean isFlushPending() {
|
||||
SelectionKey selectionKey = selectionKey();
|
||||
return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCompatible(EventLoop loop) {
|
||||
return loop instanceof NioEventLoop;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRegister() throws Exception {
|
||||
boolean selected = false;
|
||||
for (;;) {
|
||||
try {
|
||||
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
|
||||
return;
|
||||
} catch (CancelledKeyException e) {
|
||||
if (!selected) {
|
||||
// Force the Selector to select now as the "canceled" SelectionKey may still be
|
||||
// cached and not removed because no Select.select(..) operation was called yet.
|
||||
eventLoop().selectNow();
|
||||
selected = true;
|
||||
} else {
|
||||
// We forced a select operation on the selector before but the SelectionKey is still cached
|
||||
// for whatever reason. JDK bug ?
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDeregister() throws Exception {
|
||||
eventLoop().cancel(selectionKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBeginRead() throws Exception {
|
||||
// Channel.read() or ChannelHandlerContext.read() was called
|
||||
if (inputShutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SelectionKey selectionKey = this.selectionKey;
|
||||
if (!selectionKey.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
readPending = true;
|
||||
|
||||
final int interestOps = selectionKey.interestOps();
|
||||
if ((interestOps & readInterestOp) == 0) {
|
||||
selectionKey.interestOps(interestOps | readInterestOp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the remote peer
|
||||
*/
|
||||
protected abstract boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception;
|
||||
|
||||
/**
|
||||
* Finish the connect
|
||||
*/
|
||||
protected abstract void doFinishConnect() throws Exception;
|
||||
|
||||
/**
|
||||
* Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one.
|
||||
* Note that this method does not create an off-heap copy if the allocation / deallocation cost is too high,
|
||||
* but just returns the original {@link ByteBuf}..
|
||||
*/
|
||||
protected final ByteBuf newDirectBuffer(ByteBuf buf) {
|
||||
final int readableBytes = buf.readableBytes();
|
||||
if (readableBytes == 0) {
|
||||
ReferenceCountUtil.safeRelease(buf);
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
final ByteBufAllocator alloc = alloc();
|
||||
if (alloc.isDirectBufferPooled()) {
|
||||
ByteBuf directBuf = alloc.directBuffer(readableBytes);
|
||||
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
|
||||
ReferenceCountUtil.safeRelease(buf);
|
||||
return directBuf;
|
||||
}
|
||||
|
||||
final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
|
||||
if (directBuf != null) {
|
||||
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
|
||||
ReferenceCountUtil.safeRelease(buf);
|
||||
return directBuf;
|
||||
}
|
||||
|
||||
// Allocating and deallocating an unpooled direct buffer is very expensive; give up.
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder.
|
||||
* The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by
|
||||
* this method. Note that this method does not create an off-heap copy if the allocation / deallocation cost is
|
||||
* too high, but just returns the original {@link ByteBuf}..
|
||||
*/
|
||||
protected final ByteBuf newDirectBuffer(ReferenceCounted holder, ByteBuf buf) {
|
||||
final int readableBytes = buf.readableBytes();
|
||||
if (readableBytes == 0) {
|
||||
ReferenceCountUtil.safeRelease(holder);
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
final ByteBufAllocator alloc = alloc();
|
||||
if (alloc.isDirectBufferPooled()) {
|
||||
ByteBuf directBuf = alloc.directBuffer(readableBytes);
|
||||
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
|
||||
ReferenceCountUtil.safeRelease(holder);
|
||||
return directBuf;
|
||||
}
|
||||
|
||||
final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
|
||||
if (directBuf != null) {
|
||||
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
|
||||
ReferenceCountUtil.safeRelease(holder);
|
||||
return directBuf;
|
||||
}
|
||||
|
||||
// Allocating and deallocating an unpooled direct buffer is very expensive; give up.
|
||||
if (holder != buf) {
|
||||
// Ensure to call holder.release() to give the holder a chance to release other resources than its content.
|
||||
buf.retain();
|
||||
ReferenceCountUtil.safeRelease(holder);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
187
common/src/common/net/channel/nio/AbstractNioMessageChannel.java
Normal file
187
common/src/common/net/channel/nio/AbstractNioMessageChannel.java
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import common.net.channel.Channel;
|
||||
import common.net.channel.ChannelConfig;
|
||||
import common.net.channel.ChannelOutboundBuffer;
|
||||
import common.net.channel.ChannelPipeline;
|
||||
import common.net.channel.ServerChannel;
|
||||
|
||||
/**
|
||||
* {@link AbstractNioChannel} base class for {@link Channel}s that operate on messages.
|
||||
*/
|
||||
public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* @see {@link AbstractNioChannel#AbstractNioChannel(Channel, SelectableChannel, int)}
|
||||
*/
|
||||
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
|
||||
super(parent, ch, readInterestOp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractNioUnsafe newUnsafe() {
|
||||
return new NioMessageUnsafe();
|
||||
}
|
||||
|
||||
private final class NioMessageUnsafe extends AbstractNioUnsafe {
|
||||
|
||||
private final List<Object> readBuf = new ArrayList<Object>();
|
||||
|
||||
@Override
|
||||
public void read() {
|
||||
assert eventLoop().inEventLoop();
|
||||
final ChannelConfig config = config();
|
||||
if (!config.isAutoRead() && !isReadPending()) {
|
||||
// ChannelConfig.setAutoRead(false) was called in the meantime
|
||||
removeReadOp();
|
||||
return;
|
||||
}
|
||||
|
||||
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
|
||||
final ChannelPipeline pipeline = pipeline();
|
||||
boolean closed = false;
|
||||
Throwable exception = null;
|
||||
try {
|
||||
try {
|
||||
for (;;) {
|
||||
int localRead = doReadMessages(readBuf);
|
||||
if (localRead == 0) {
|
||||
break;
|
||||
}
|
||||
if (localRead < 0) {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// stop reading and remove op
|
||||
if (!config.isAutoRead()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (readBuf.size() >= maxMessagesPerRead) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
exception = t;
|
||||
}
|
||||
setReadPending(false);
|
||||
int size = readBuf.size();
|
||||
for (int i = 0; i < size; i ++) {
|
||||
pipeline.fireChannelRead(readBuf.get(i));
|
||||
}
|
||||
|
||||
readBuf.clear();
|
||||
pipeline.fireChannelReadComplete();
|
||||
|
||||
if (exception != null) {
|
||||
if (exception instanceof IOException) {
|
||||
// ServerChannel should not be closed even on IOException because it can often continue
|
||||
// accepting incoming connections. (e.g. too many open files)
|
||||
closed = !(AbstractNioMessageChannel.this instanceof ServerChannel);
|
||||
}
|
||||
|
||||
pipeline.fireExceptionCaught(exception);
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
if (isOpen()) {
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Check if there is a readPending which was not processed yet.
|
||||
// This could be for two reasons:
|
||||
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
|
||||
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2254
|
||||
if (!config.isAutoRead() && !isReadPending()) {
|
||||
removeReadOp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
|
||||
final SelectionKey key = selectionKey();
|
||||
final int interestOps = key.interestOps();
|
||||
|
||||
for (;;) {
|
||||
Object msg = in.current();
|
||||
if (msg == null) {
|
||||
// Wrote all messages.
|
||||
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
||||
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
try {
|
||||
boolean done = false;
|
||||
for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
|
||||
if (doWriteMessage(msg, in)) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
in.remove();
|
||||
} else {
|
||||
// Did not write all messages.
|
||||
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
||||
key.interestOps(interestOps | SelectionKey.OP_WRITE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (continueOnWriteError()) {
|
||||
in.remove(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if we should continue the write loop on a write error.
|
||||
*/
|
||||
protected boolean continueOnWriteError() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read messages into the given array and return the amount which was read.
|
||||
*/
|
||||
protected abstract int doReadMessages(List<Object> buf) throws Exception;
|
||||
|
||||
/**
|
||||
* Write a message to the underlying {@link java.nio.channels.Channel}.
|
||||
*
|
||||
* @return {@code true} if and only if the message has been written
|
||||
*/
|
||||
protected abstract boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception;
|
||||
}
|
691
common/src/common/net/channel/nio/NioEventLoop.java
Normal file
691
common/src/common/net/channel/nio/NioEventLoop.java
Normal file
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.spi.SelectorProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import common.net.channel.ChannelException;
|
||||
import common.net.channel.EventLoopException;
|
||||
import common.net.channel.SingleThreadEventLoop;
|
||||
import common.net.channel.nio.AbstractNioChannel.NioUnsafe;
|
||||
import common.net.util.internal.PlatformDependent;
|
||||
import common.net.util.internal.SystemPropertyUtil;
|
||||
import common.net.util.internal.logging.InternalLogger;
|
||||
import common.net.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link SingleThreadEventLoop} implementation which register the {@link Channel}'s to a
|
||||
* {@link Selector} and so does the multi-plexing of these in the event loop.
|
||||
*
|
||||
*/
|
||||
public final class NioEventLoop extends SingleThreadEventLoop {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioEventLoop.class);
|
||||
|
||||
private static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
|
||||
|
||||
private static final boolean DISABLE_KEYSET_OPTIMIZATION =
|
||||
SystemPropertyUtil.getBoolean("game.net.noKeySetOptimization", false);
|
||||
|
||||
private static final int MIN_PREMATURE_SELECTOR_RETURNS = 3;
|
||||
private static final int SELECTOR_AUTO_REBUILD_THRESHOLD;
|
||||
|
||||
// Workaround for JDK NIO bug.
|
||||
//
|
||||
// See:
|
||||
// - http://bugs.sun.com/view_bug.do?bug_id=6427854
|
||||
// - https://github.com/netty/netty/issues/203
|
||||
static {
|
||||
String key = "sun.nio.ch.bugLevel";
|
||||
try {
|
||||
String buglevel = SystemPropertyUtil.get(key);
|
||||
if (buglevel == null) {
|
||||
System.setProperty(key, "");
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Unable to get/set System Property: {}", key, e);
|
||||
}
|
||||
}
|
||||
|
||||
int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("game.net.selectorAutoRebuildThreshold", 512);
|
||||
if (selectorAutoRebuildThreshold < MIN_PREMATURE_SELECTOR_RETURNS) {
|
||||
selectorAutoRebuildThreshold = 0;
|
||||
}
|
||||
|
||||
SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("-Dgame.net.noKeySetOptimization: {}", DISABLE_KEYSET_OPTIMIZATION);
|
||||
logger.debug("-Dgame.net.selectorAutoRebuildThreshold: {}", SELECTOR_AUTO_REBUILD_THRESHOLD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The NIO {@link Selector}.
|
||||
*/
|
||||
Selector selector;
|
||||
private SelectedSelectionKeySet selectedKeys;
|
||||
|
||||
private final SelectorProvider provider;
|
||||
|
||||
/**
|
||||
* Boolean that controls determines if a blocked Selector.select should
|
||||
* break out of its selection process. In our case we use a timeout for
|
||||
* the select method and the select method will block for that time unless
|
||||
* waken up.
|
||||
*/
|
||||
private final AtomicBoolean wakenUp = new AtomicBoolean();
|
||||
|
||||
private volatile int ioRatio = 50;
|
||||
private int cancelledKeys;
|
||||
private boolean needsToSelectAgain;
|
||||
|
||||
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
|
||||
super(parent, threadFactory, false);
|
||||
if (selectorProvider == null) {
|
||||
throw new NullPointerException("selectorProvider");
|
||||
}
|
||||
provider = selectorProvider;
|
||||
selector = openSelector();
|
||||
}
|
||||
|
||||
private Selector openSelector() {
|
||||
final Selector selector;
|
||||
try {
|
||||
selector = provider.openSelector();
|
||||
} catch (IOException e) {
|
||||
throw new ChannelException("failed to open a new selector", e);
|
||||
}
|
||||
|
||||
if (DISABLE_KEYSET_OPTIMIZATION) {
|
||||
return selector;
|
||||
}
|
||||
|
||||
try {
|
||||
SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
|
||||
|
||||
Class<?> selectorImplClass =
|
||||
Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader());
|
||||
|
||||
// Ensure the current selector implementation is what we can instrument.
|
||||
if (!selectorImplClass.isAssignableFrom(selector.getClass())) {
|
||||
return selector;
|
||||
}
|
||||
|
||||
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
|
||||
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
|
||||
|
||||
selectedKeysField.setAccessible(true);
|
||||
publicSelectedKeysField.setAccessible(true);
|
||||
|
||||
selectedKeysField.set(selector, selectedKeySet);
|
||||
publicSelectedKeysField.set(selector, selectedKeySet);
|
||||
|
||||
selectedKeys = selectedKeySet;
|
||||
logger.trace("Instrumented an optimized java.util.Set into: {}", selector);
|
||||
} catch (Throwable t) {
|
||||
selectedKeys = null;
|
||||
logger.trace("Failed to instrument an optimized java.util.Set into: {}", selector, t);
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Queue<Runnable> newTaskQueue() {
|
||||
// This event loop never calls takeTask()
|
||||
return PlatformDependent.newMpscQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an arbitrary {@link SelectableChannel}, not necessarily created by Netty, to the {@link Selector}
|
||||
* of this event loop. Once the specified {@link SelectableChannel} is registered, the specified {@code task} will
|
||||
* be executed by this event loop when the {@link SelectableChannel} is ready.
|
||||
*/
|
||||
public void register(final SelectableChannel ch, final int interestOps, final NioTask<?> task) {
|
||||
if (ch == null) {
|
||||
throw new NullPointerException("ch");
|
||||
}
|
||||
if (interestOps == 0) {
|
||||
throw new IllegalArgumentException("interestOps must be non-zero.");
|
||||
}
|
||||
if ((interestOps & ~ch.validOps()) != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid interestOps: " + interestOps + "(validOps: " + ch.validOps() + ')');
|
||||
}
|
||||
if (task == null) {
|
||||
throw new NullPointerException("task");
|
||||
}
|
||||
|
||||
if (isShutdown()) {
|
||||
throw new IllegalStateException("event loop shut down");
|
||||
}
|
||||
|
||||
try {
|
||||
ch.register(selector, interestOps, task);
|
||||
} catch (Exception e) {
|
||||
throw new EventLoopException("failed to register a channel", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage of the desired amount of time spent for I/O in the event loop.
|
||||
*/
|
||||
public int getIoRatio() {
|
||||
return ioRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the percentage of the desired amount of time spent for I/O in the event loop. The default value is
|
||||
* {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks.
|
||||
*/
|
||||
public void setIoRatio(int ioRatio) {
|
||||
if (ioRatio <= 0 || ioRatio > 100) {
|
||||
throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)");
|
||||
}
|
||||
this.ioRatio = ioRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
|
||||
* around the infamous epoll 100% CPU bug.
|
||||
*/
|
||||
public void rebuildSelector() {
|
||||
if (!inEventLoop()) {
|
||||
execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
rebuildSelector();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final Selector oldSelector = selector;
|
||||
final Selector newSelector;
|
||||
|
||||
if (oldSelector == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
newSelector = openSelector();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to create a new Selector.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register all channels to the new Selector.
|
||||
int nChannels = 0;
|
||||
for (;;) {
|
||||
try {
|
||||
for (SelectionKey key: oldSelector.keys()) {
|
||||
Object a = key.attachment();
|
||||
try {
|
||||
if (!key.isValid() || key.channel().keyFor(newSelector) != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int interestOps = key.interestOps();
|
||||
key.cancel();
|
||||
SelectionKey newKey = key.channel().register(newSelector, interestOps, a);
|
||||
if (a instanceof AbstractNioChannel) {
|
||||
// Update SelectionKey
|
||||
((AbstractNioChannel) a).selectionKey = newKey;
|
||||
}
|
||||
nChannels ++;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to re-register a Channel to the new Selector.", e);
|
||||
if (a instanceof AbstractNioChannel) {
|
||||
AbstractNioChannel ch = (AbstractNioChannel) a;
|
||||
ch.unsafe().close(ch.unsafe().voidPromise());
|
||||
} else {
|
||||
|
||||
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
|
||||
invokeChannelUnregistered(task, key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
// Probably due to concurrent modification of the key set.
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
selector = newSelector;
|
||||
|
||||
try {
|
||||
// time to close the old selector as everything else is registered to the new one
|
||||
oldSelector.close();
|
||||
} catch (Throwable t) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to close the old Selector.", t);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
for (;;) {
|
||||
boolean oldWakenUp = wakenUp.getAndSet(false);
|
||||
try {
|
||||
if (hasTasks()) {
|
||||
selectNow();
|
||||
} else {
|
||||
select(oldWakenUp);
|
||||
|
||||
// 'wakenUp.compareAndSet(false, true)' is always evaluated
|
||||
// before calling 'selector.wakeup()' to reduce the wake-up
|
||||
// overhead. (Selector.wakeup() is an expensive operation.)
|
||||
//
|
||||
// However, there is a race condition in this approach.
|
||||
// The race condition is triggered when 'wakenUp' is set to
|
||||
// true too early.
|
||||
//
|
||||
// 'wakenUp' is set to true too early if:
|
||||
// 1) Selector is waken up between 'wakenUp.set(false)' and
|
||||
// 'selector.select(...)'. (BAD)
|
||||
// 2) Selector is waken up between 'selector.select(...)' and
|
||||
// 'if (wakenUp.get()) { ... }'. (OK)
|
||||
//
|
||||
// In the first case, 'wakenUp' is set to true and the
|
||||
// following 'selector.select(...)' will wake up immediately.
|
||||
// Until 'wakenUp' is set to false again in the next round,
|
||||
// 'wakenUp.compareAndSet(false, true)' will fail, and therefore
|
||||
// any attempt to wake up the Selector will fail, too, causing
|
||||
// the following 'selector.select(...)' call to block
|
||||
// unnecessarily.
|
||||
//
|
||||
// To fix this problem, we wake up the selector again if wakenUp
|
||||
// is true immediately after selector.select(...).
|
||||
// It is inefficient in that it wakes up the selector for both
|
||||
// the first case (BAD - wake-up required) and the second case
|
||||
// (OK - no wake-up required).
|
||||
|
||||
if (wakenUp.get()) {
|
||||
selector.wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
cancelledKeys = 0;
|
||||
needsToSelectAgain = false;
|
||||
final int ioRatio = this.ioRatio;
|
||||
if (ioRatio == 100) {
|
||||
processSelectedKeys();
|
||||
runAllTasks();
|
||||
} else {
|
||||
final long ioStartTime = System.nanoTime();
|
||||
|
||||
processSelectedKeys();
|
||||
|
||||
final long ioTime = System.nanoTime() - ioStartTime;
|
||||
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
|
||||
}
|
||||
|
||||
if (isShuttingDown()) {
|
||||
closeAll();
|
||||
if (confirmShutdown()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unexpected exception in the selector loop.", t);
|
||||
|
||||
// Prevent possible consecutive immediate failures that lead to
|
||||
// excessive CPU consumption.
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processSelectedKeys() {
|
||||
if (selectedKeys != null) {
|
||||
processSelectedKeysOptimized(selectedKeys.flip());
|
||||
} else {
|
||||
processSelectedKeysPlain(selector.selectedKeys());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup() {
|
||||
try {
|
||||
selector.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to close a selector.", e);
|
||||
}
|
||||
}
|
||||
|
||||
void cancel(SelectionKey key) {
|
||||
key.cancel();
|
||||
cancelledKeys ++;
|
||||
if (cancelledKeys >= CLEANUP_INTERVAL) {
|
||||
cancelledKeys = 0;
|
||||
needsToSelectAgain = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Runnable pollTask() {
|
||||
Runnable task = super.pollTask();
|
||||
if (needsToSelectAgain) {
|
||||
selectAgain();
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
|
||||
// check if the set is empty and if so just return to not create garbage by
|
||||
// creating a new Iterator every time even if there is nothing to process.
|
||||
// See https://github.com/netty/netty/issues/597
|
||||
if (selectedKeys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<SelectionKey> i = selectedKeys.iterator();
|
||||
for (;;) {
|
||||
final SelectionKey k = i.next();
|
||||
final Object a = k.attachment();
|
||||
i.remove();
|
||||
|
||||
if (a instanceof AbstractNioChannel) {
|
||||
processSelectedKey(k, (AbstractNioChannel) a);
|
||||
} else {
|
||||
|
||||
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
|
||||
processSelectedKey(k, task);
|
||||
}
|
||||
|
||||
if (!i.hasNext()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (needsToSelectAgain) {
|
||||
selectAgain();
|
||||
selectedKeys = selector.selectedKeys();
|
||||
|
||||
// Create the iterator again to avoid ConcurrentModificationException
|
||||
if (selectedKeys.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
i = selectedKeys.iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
|
||||
for (int i = 0;; i ++) {
|
||||
final SelectionKey k = selectedKeys[i];
|
||||
if (k == null) {
|
||||
break;
|
||||
}
|
||||
// null out entry in the array to allow to have it GC'ed once the Channel close
|
||||
// See https://github.com/netty/netty/issues/2363
|
||||
selectedKeys[i] = null;
|
||||
|
||||
final Object a = k.attachment();
|
||||
|
||||
if (a instanceof AbstractNioChannel) {
|
||||
processSelectedKey(k, (AbstractNioChannel) a);
|
||||
} else {
|
||||
|
||||
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
|
||||
processSelectedKey(k, task);
|
||||
}
|
||||
|
||||
if (needsToSelectAgain) {
|
||||
// null out entries in the array to allow to have it GC'ed once the Channel close
|
||||
// See https://github.com/netty/netty/issues/2363
|
||||
for (;;) {
|
||||
if (selectedKeys[i] == null) {
|
||||
break;
|
||||
}
|
||||
selectedKeys[i] = null;
|
||||
i++;
|
||||
}
|
||||
|
||||
selectAgain();
|
||||
// Need to flip the optimized selectedKeys to get the right reference to the array
|
||||
// and reset the index to -1 which will then set to 0 on the for loop
|
||||
// to start over again.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1523
|
||||
selectedKeys = this.selectedKeys.flip();
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
|
||||
final NioUnsafe unsafe = ch.unsafe();
|
||||
if (!k.isValid()) {
|
||||
// close the channel if the key is not valid anymore
|
||||
unsafe.close(unsafe.voidPromise());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int readyOps = k.readyOps();
|
||||
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
|
||||
// to a spin loop
|
||||
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
|
||||
unsafe.read();
|
||||
if (!ch.isOpen()) {
|
||||
// Connection already closed - no need to handle write.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
|
||||
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
|
||||
ch.unsafe().forceFlush();
|
||||
}
|
||||
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();
|
||||
}
|
||||
} catch (CancelledKeyException ignored) {
|
||||
unsafe.close(unsafe.voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
private static void processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) {
|
||||
int state = 0;
|
||||
try {
|
||||
task.channelReady(k.channel(), k);
|
||||
state = 1;
|
||||
} catch (Exception e) {
|
||||
k.cancel();
|
||||
invokeChannelUnregistered(task, k, e);
|
||||
state = 2;
|
||||
} finally {
|
||||
switch (state) {
|
||||
case 0:
|
||||
k.cancel();
|
||||
invokeChannelUnregistered(task, k, null);
|
||||
break;
|
||||
case 1:
|
||||
if (!k.isValid()) { // Cancelled by channelReady()
|
||||
invokeChannelUnregistered(task, k, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
selectAgain();
|
||||
Set<SelectionKey> keys = selector.keys();
|
||||
Collection<AbstractNioChannel> channels = new ArrayList<AbstractNioChannel>(keys.size());
|
||||
for (SelectionKey k: keys) {
|
||||
Object a = k.attachment();
|
||||
if (a instanceof AbstractNioChannel) {
|
||||
channels.add((AbstractNioChannel) a);
|
||||
} else {
|
||||
k.cancel();
|
||||
|
||||
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
|
||||
invokeChannelUnregistered(task, k, null);
|
||||
}
|
||||
}
|
||||
|
||||
for (AbstractNioChannel ch: channels) {
|
||||
ch.unsafe().close(ch.unsafe().voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
private static void invokeChannelUnregistered(NioTask<SelectableChannel> task, SelectionKey k, Throwable cause) {
|
||||
try {
|
||||
task.channelUnregistered(k.channel(), cause);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unexpected exception while running NioTask.channelUnregistered()", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void wakeup(boolean inEventLoop) {
|
||||
if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
|
||||
selector.wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
void selectNow() throws IOException {
|
||||
try {
|
||||
selector.selectNow();
|
||||
} finally {
|
||||
// restore wakup state if needed
|
||||
if (wakenUp.get()) {
|
||||
selector.wakeup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void select(boolean oldWakenUp) throws IOException {
|
||||
Selector selector = this.selector;
|
||||
try {
|
||||
int selectCnt = 0;
|
||||
long currentTimeNanos = System.nanoTime();
|
||||
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
|
||||
for (;;) {
|
||||
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
|
||||
if (timeoutMillis <= 0) {
|
||||
if (selectCnt == 0) {
|
||||
selector.selectNow();
|
||||
selectCnt = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int selectedKeys = selector.select(timeoutMillis);
|
||||
selectCnt ++;
|
||||
|
||||
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
|
||||
// - Selected something,
|
||||
// - waken up by user, or
|
||||
// - the task queue has a pending task.
|
||||
// - a scheduled task is ready for processing
|
||||
break;
|
||||
}
|
||||
if (Thread.interrupted()) {
|
||||
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
|
||||
// As this is most likely a bug in the handler of the user or it's client library we will
|
||||
// also log it.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/2426
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Selector.select() returned prematurely because " +
|
||||
"Thread.currentThread().interrupt() was called. Use " +
|
||||
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
|
||||
}
|
||||
selectCnt = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
long time = System.nanoTime();
|
||||
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
|
||||
// timeoutMillis elapsed without anything selected.
|
||||
selectCnt = 1;
|
||||
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
|
||||
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
|
||||
// The selector returned prematurely many times in a row.
|
||||
// Rebuild the selector to work around the problem.
|
||||
logger.warn(
|
||||
"Selector.select() returned prematurely {} times in a row; rebuilding selector.",
|
||||
selectCnt);
|
||||
|
||||
rebuildSelector();
|
||||
selector = this.selector;
|
||||
|
||||
// Select again to populate selectedKeys.
|
||||
selector.selectNow();
|
||||
selectCnt = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
currentTimeNanos = time;
|
||||
}
|
||||
|
||||
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Selector.select() returned prematurely {} times in a row.", selectCnt - 1);
|
||||
}
|
||||
}
|
||||
} catch (CancelledKeyException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector - JDK bug?", e);
|
||||
}
|
||||
// Harmless exception - log anyway
|
||||
}
|
||||
}
|
||||
|
||||
private void selectAgain() {
|
||||
needsToSelectAgain = false;
|
||||
try {
|
||||
selector.selectNow();
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Failed to update SelectionKeys.", t);
|
||||
}
|
||||
}
|
||||
}
|
87
common/src/common/net/channel/nio/NioEventLoopGroup.java
Normal file
87
common/src/common/net/channel/nio/NioEventLoopGroup.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
import java.nio.channels.spi.SelectorProvider;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import common.net.channel.MultithreadEventLoopGroup;
|
||||
import common.net.util.concurrent.EventExecutor;
|
||||
|
||||
/**
|
||||
* {@link MultithreadEventLoopGroup} implementations which is used for NIO {@link Selector} based {@link Channel}s.
|
||||
*/
|
||||
public class NioEventLoopGroup extends MultithreadEventLoopGroup {
|
||||
|
||||
/**
|
||||
* Create a new instance using the default number of threads, the default {@link ThreadFactory} and
|
||||
* the {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}.
|
||||
*/
|
||||
public NioEventLoopGroup() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified number of threads, {@link ThreadFactory} and the
|
||||
* {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}.
|
||||
*/
|
||||
public NioEventLoopGroup(int nThreads) {
|
||||
this(nThreads, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the
|
||||
* {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}.
|
||||
*/
|
||||
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
|
||||
this(nThreads, threadFactory, SelectorProvider.provider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the given
|
||||
* {@link SelectorProvider}.
|
||||
*/
|
||||
public NioEventLoopGroup(
|
||||
int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
|
||||
super(nThreads, threadFactory, selectorProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the percentage of the desired amount of time spent for I/O in the child event loops. The default value is
|
||||
* {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks.
|
||||
*/
|
||||
public void setIoRatio(int ioRatio) {
|
||||
for (EventExecutor e: children()) {
|
||||
((NioEventLoop) e).setIoRatio(ioRatio);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current {@link Selector}s of the child event loops with newly created {@link Selector}s to work
|
||||
* around the infamous epoll 100% CPU bug.
|
||||
*/
|
||||
public void rebuildSelectors() {
|
||||
for (EventExecutor e: children()) {
|
||||
((NioEventLoop) e).rebuildSelector();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventExecutor newChild(
|
||||
ThreadFactory threadFactory, Object... args) throws Exception {
|
||||
return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
|
||||
}
|
||||
}
|
40
common/src/common/net/channel/nio/NioTask.java
Normal file
40
common/src/common/net/channel/nio/NioTask.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package common.net.channel.nio;
|
||||
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
||||
/**
|
||||
* An arbitrary task that can be executed by {@link NioEventLoop} when a {@link SelectableChannel} becomes ready.
|
||||
*
|
||||
* @see NioEventLoop#register(SelectableChannel, int, NioTask)
|
||||
*/
|
||||
public interface NioTask<C extends SelectableChannel> {
|
||||
/**
|
||||
* Invoked when the {@link SelectableChannel} has been selected by the {@link Selector}.
|
||||
*/
|
||||
void channelReady(C ch, SelectionKey key) throws Exception;
|
||||
|
||||
/**
|
||||
* Invoked when the {@link SelectionKey} of the specified {@link SelectableChannel} has been cancelled and thus
|
||||
* this {@link NioTask} will not be notified anymore.
|
||||
*
|
||||
* @param cause the cause of the unregistration. {@code null} if a user called {@link SelectionKey#cancel()} or
|
||||
* the event loop has been shut down.
|
||||
*/
|
||||
void channelUnregistered(C ch, Throwable cause) throws Exception;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue