JavaDoc for the bootstrap package
This commit is contained in:
parent
ad805a1f70
commit
24b4db3f57
@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFactory;
|
||||
import org.jboss.netty.channel.ChannelHandler;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
@ -42,6 +43,13 @@ import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
|
||||
/**
|
||||
* Helper class which helps a user initialize a {@link Channel}. This class
|
||||
* provides the common data structure for its subclasses which implements an
|
||||
* actual channel initialization from the common data structure. Please refer
|
||||
* to {@link ClientBootstrap} and {@link ServerBootstrap} for client side and
|
||||
* server-side channel initialization respectively.
|
||||
*
|
||||
*
|
||||
* @author The Netty Project (netty-dev@lists.jboss.org)
|
||||
* @author Trustin Lee (tlee@redhat.com)
|
||||
*
|
||||
@ -58,14 +66,31 @@ public class Bootstrap {
|
||||
private volatile ChannelPipelineFactory pipelineFactory = pipelineFactory(pipeline);
|
||||
private volatile Map<String, Object> options = new HashMap<String, Object>();
|
||||
|
||||
public Bootstrap() {
|
||||
/**
|
||||
* Creates a new instance with no {@link ChannelFactory} set.
|
||||
* {@link #setFactory(ChannelFactory)} must be called at once before any
|
||||
* I/O operation is requested.
|
||||
*/
|
||||
protected Bootstrap() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Bootstrap(ChannelFactory channelFactory) {
|
||||
/**
|
||||
* Creates a new instance with the specified initial {@link ChannelFactory}.
|
||||
*/
|
||||
protected Bootstrap(ChannelFactory channelFactory) {
|
||||
setFactory(channelFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelFactory} that will be used to perform an
|
||||
* I/O operation.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if the factory is not set for this bootstrap yet.
|
||||
* The factory can be set in the constructor or
|
||||
* {@link #setFactory(ChannelFactory)}.
|
||||
*/
|
||||
public ChannelFactory getFactory() {
|
||||
ChannelFactory factory = this.factory;
|
||||
if (factory == null) {
|
||||
@ -75,6 +100,14 @@ public class Bootstrap {
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ChannelFactory} that will be used to perform an I/O
|
||||
* operation. This method can be called only once and can't be called at
|
||||
* all if the factory was specified in the constructor.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if the factory is already set
|
||||
*/
|
||||
public void setFactory(ChannelFactory factory) {
|
||||
if (this.factory != null) {
|
||||
throw new IllegalStateException(
|
||||
@ -86,10 +119,27 @@ public class Bootstrap {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default {@link ChannelPipeline} which is cloned when a new
|
||||
* {@link Channel} is created. Bootstrap creates a new pipeline which has
|
||||
* the same entries with the returned pipeline for a new {@link Channel}.
|
||||
*
|
||||
* @return the default {@link ChannelPipeline}. {@code null} if
|
||||
* {@link #setPipelineFactory(ChannelPipelineFactory)} was
|
||||
* called last time.
|
||||
*/
|
||||
public ChannelPipeline getPipeline() {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default {@link ChannelPipeline} which is cloned when a new
|
||||
* {@link Channel} is created. Bootstrap creates a new pipeline which has
|
||||
* the same entries with the specified pipeline for a new channel. Calling
|
||||
* this method also sets the {@code pipelineFactory} property to an
|
||||
* internal {@link ChannelPipelineFactory} implementation which returns
|
||||
* a copy of the specified pipeline.
|
||||
*/
|
||||
public void setPipeline(ChannelPipeline pipeline) {
|
||||
if (pipeline == null) {
|
||||
throw new NullPointerException("pipeline");
|
||||
@ -98,6 +148,14 @@ public class Bootstrap {
|
||||
pipelineFactory = pipelineFactory(pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for {@link #getPipeline()} which returns the default
|
||||
* pipeline of this bootstrap as an ordered map.
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if {@link #setPipelineFactory(ChannelPipelineFactory)} is in
|
||||
* use to create a new pipeline
|
||||
*/
|
||||
public Map<String, ChannelHandler> getPipelineAsMap() {
|
||||
ChannelPipeline pipeline = this.pipeline;
|
||||
if (pipeline == null) {
|
||||
@ -106,6 +164,13 @@ public class Bootstrap {
|
||||
return pipeline.toMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for {@link #setPipeline} which sets the default
|
||||
* pipeline of this bootstrap from an ordered map.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* if the specified map is not an ordered map
|
||||
*/
|
||||
public void setPipelineAsMap(Map<String, ChannelHandler> pipelineMap) {
|
||||
if (pipelineMap == null) {
|
||||
throw new NullPointerException("pipelineMap");
|
||||
@ -126,10 +191,26 @@ public class Bootstrap {
|
||||
setPipeline(pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ChannelPipelineFactory} which creates a new
|
||||
* {@link ChannelPipeline} for a new {@link Channel}.
|
||||
*
|
||||
* @see #getPipeline()
|
||||
*/
|
||||
public ChannelPipelineFactory getPipelineFactory() {
|
||||
return pipelineFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ChannelPipelineFactory} which creates a new
|
||||
* {@link ChannelPipeline} for a new {@link Channel}. Calling this method
|
||||
* invalidates the current {@code pipeline} property of this bootstrap.
|
||||
* Subsequent {@link #getPipeline()} and {@link #getPipelineAsMap()} calls
|
||||
* will raise {@link IllegalStateException}.
|
||||
*
|
||||
* @see #setPipeline(ChannelPipeline)
|
||||
* @see #setPipelineAsMap(Map)
|
||||
*/
|
||||
public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) {
|
||||
if (pipelineFactory == null) {
|
||||
throw new NullPointerException("pipelineFactory");
|
||||
@ -138,10 +219,16 @@ public class Bootstrap {
|
||||
this.pipelineFactory = pipelineFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options which configures a new {@link Channel}.
|
||||
*/
|
||||
public Map<String, Object> getOptions() {
|
||||
return new TreeMap<String, Object>(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options which configures a new {@link Channel}.
|
||||
*/
|
||||
public void setOptions(Map<String, Object> options) {
|
||||
if (options == null) {
|
||||
throw new NullPointerException("options");
|
||||
@ -149,6 +236,12 @@ public class Bootstrap {
|
||||
this.options = new HashMap<String, Object>(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the option with the specified key.
|
||||
*
|
||||
* @return the option value if the option is found.
|
||||
* {@code null} otherwise.
|
||||
*/
|
||||
public Object getOption(String key) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key");
|
||||
@ -156,6 +249,12 @@ public class Bootstrap {
|
||||
return options.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option with the specified key and value. If there's already
|
||||
* an option with the same key, it's replaced with the new value. If the
|
||||
* specified value is {@code null}, an existing option with the specified
|
||||
* key is removed.
|
||||
*/
|
||||
public void setOption(String key, Object value) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key");
|
||||
|
@ -29,17 +29,66 @@ import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFactory;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineCoverage;
|
||||
import org.jboss.netty.channel.ChannelPipelineException;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.ExceptionEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
|
||||
/**
|
||||
* Helper class which helps a user create a new client-side {@link Channel}
|
||||
* and make a connection attempt.
|
||||
*
|
||||
* <h3>Configuring a channel</h3>
|
||||
*
|
||||
* {@link #setOption(String, Object) Options} are used to configure a channel:
|
||||
*
|
||||
* <pre>
|
||||
* ClientBootstrap b = ...;
|
||||
*
|
||||
* // Options for a new channel
|
||||
* b.setOption("remoteAddress", new InetSocketAddress("example.com", 8080));
|
||||
* b.setOption("tcpNoDelay", true);
|
||||
* b.setOption("receiveBufferSize", 1048576);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Configuring a channel pipeline</h3>
|
||||
*
|
||||
* Every channel has its own {@link ChannelPipeline} and you can configure it
|
||||
* in two ways.
|
||||
*
|
||||
* {@linkplain #setPipeline(ChannelPipeline) The first approach} is to use
|
||||
* the default pipeline and let the bootstrap to clone it for each new channel:
|
||||
*
|
||||
* <pre>
|
||||
* ClientBootstrap b = ...;
|
||||
* ChannelPipeline p = b.getPipeline();
|
||||
*
|
||||
* // Add handlers to the pipeline.
|
||||
* p.addLast("encoder", new EncodingHandler());
|
||||
* p.addLast("decoder", new DecodingHandler());
|
||||
* p.addLast("logic", new LogicHandler());
|
||||
* </pre>
|
||||
*
|
||||
* {@linkplain #setPipelineFactory(ChannelPipelineFactory) The second approach}
|
||||
* is to specify a {@link ChannelPipelineFactory} by yourself and have full
|
||||
* control over how a new pipeline is created, at the cost of more complexity:
|
||||
*
|
||||
* <pre>
|
||||
* ClientBootstrap b = ...;
|
||||
* b.setPipelineFactory(new MyPipelineFactory());
|
||||
*
|
||||
* public class MyPipelineFactory implements ChannelPipelineFactory {
|
||||
* // Create a new pipeline for a new channel and configure it here ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author The Netty Project (netty-dev@lists.jboss.org)
|
||||
* @author Trustin Lee (tlee@redhat.com)
|
||||
*
|
||||
@ -49,30 +98,95 @@ import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
*/
|
||||
public class ClientBootstrap extends Bootstrap {
|
||||
|
||||
/**
|
||||
* Creates a new instance with no {@link ChannelFactory} set.
|
||||
* {@link #setFactory(ChannelFactory)} must be called at once before any
|
||||
* I/O operation is requested.
|
||||
*/
|
||||
public ClientBootstrap() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified initial {@link ChannelFactory}.
|
||||
*/
|
||||
public ClientBootstrap(ChannelFactory channelFactory) {
|
||||
super(channelFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts a new connection with the current {@code "remoteAddress"} and
|
||||
* {@code "localAddress"} option. If the {@code "localAddress"} option is
|
||||
* not set, the local address of a new channel is determined automatically.
|
||||
* This method is similar to the following code:
|
||||
*
|
||||
* <pre>
|
||||
* ClientBootstrap b = ...;
|
||||
* b.connect(b.getOption("remoteAddress"), b.getOption("localAddress"));
|
||||
* </pre>
|
||||
*
|
||||
* @return a future object which notifies when this connection attempt
|
||||
* succeeds or fails
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if {@code "remoteAddress"} option was not set
|
||||
* @throws ClassCastException
|
||||
* if {@code "remoteAddress"} or {@code "localAddress"} option's
|
||||
* value is neither a {@link SocketAddress} nor {@code null}
|
||||
* @throws ChannelPipelineException
|
||||
* if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
|
||||
* failed to create a new {@link ChannelPipeline}
|
||||
*/
|
||||
public ChannelFuture connect() {
|
||||
SocketAddress remoteAddress = (SocketAddress) getOption("remoteAddress");
|
||||
if (remoteAddress == null) {
|
||||
throw new IllegalStateException("remoteAddress option is not set.");
|
||||
}
|
||||
SocketAddress localAddress = (SocketAddress) getOption("localAddress");
|
||||
return connect(remoteAddress, localAddress);
|
||||
return connect(remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts a new connection with the specified {@code remoteAddress} and
|
||||
* the current {@code "localAddress"} option. If the {@code "localAddress"}
|
||||
* option is not set, the local address of a new channel is determined
|
||||
* automatically. This method is identical with the following code:
|
||||
*
|
||||
* <pre>
|
||||
* ClientBootstrap b = ...;
|
||||
* b.connect(remoteAddress, b.getOption("localAddress"));
|
||||
* </pre>
|
||||
*
|
||||
* @return a future object which notifies when this connection attempt
|
||||
* succeeds or fails
|
||||
*
|
||||
* @throws ClassCastException
|
||||
* if {@code "localAddress"} option's value is
|
||||
* neither a {@link SocketAddress} nor {@code null}
|
||||
* @throws ChannelPipelineException
|
||||
* if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
|
||||
* failed to create a new {@link ChannelPipeline}
|
||||
*/
|
||||
public ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
if (remoteAddress == null) {
|
||||
throw new NullPointerException("remotedAddress");
|
||||
}
|
||||
return connect(remoteAddress, null);
|
||||
SocketAddress localAddress = (SocketAddress) getOption("localAddress");
|
||||
return connect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts a new connection with the specified {@code remoteAddress} and
|
||||
* the specified {@code localAddress}. If the specified local address is
|
||||
* {@code null}, the local address of a new channel is determined
|
||||
* automatically.
|
||||
*
|
||||
* @return a future object which notifies when this connection attempt
|
||||
* succeeds or fails
|
||||
*
|
||||
* @throws ChannelPipelineException
|
||||
* if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
|
||||
* failed to create a new {@link ChannelPipeline}
|
||||
*/
|
||||
public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
|
||||
|
||||
final BlockingQueue<ChannelFuture> futureQueue =
|
||||
|
@ -40,12 +40,91 @@ import org.jboss.netty.channel.ChannelHandler;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineCoverage;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.ChildChannelStateEvent;
|
||||
import org.jboss.netty.channel.ExceptionEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
|
||||
/**
|
||||
* Helper class which helps a user create a new server-side {@link Channel}
|
||||
* and accept incoming connections.
|
||||
*
|
||||
* <h3>Parent channel and its children</h3>
|
||||
*
|
||||
* A parent channel is a channel which is supposed to accept incoming
|
||||
* connections. It is created by this bootstrap's
|
||||
* {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory} or
|
||||
* {@link #setPipeline(ChannelPipeline) pipeline} property via {@link #bind()}
|
||||
* and {@link #bind(SocketAddress)}.
|
||||
* <p>
|
||||
* Once successfully bound, the parent channel starts to accept incoming
|
||||
* connections, and the accepted connections becomes the children of the
|
||||
* parent channel.
|
||||
*
|
||||
* <h3>Configuring channels</h3>
|
||||
*
|
||||
* {@link #setOption(String, Object) Options} are used to configure both a
|
||||
* parent channel and its child channels. To configure the child channels,
|
||||
* prepend {@code "child."} prefix to the actual option names of a child
|
||||
* channel:
|
||||
*
|
||||
* <pre>
|
||||
* ServerBootstrap b = ...;
|
||||
*
|
||||
* // Options for a parent channel
|
||||
* b.setOption("localAddress", new InetSocketAddress(8080));
|
||||
* b.setOption("reuseAddress", true);
|
||||
*
|
||||
* // Options for its children
|
||||
* b.setOption("child.tcpNoDelay", true);
|
||||
* b.setOption("child.receiveBufferSize", 1048576);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Configuring a parent channel pipeline</h3>
|
||||
*
|
||||
* It is rare to configure the pipeline of a parent channel because what it's
|
||||
* supposed to do is very typical. However, you might want to add a handler
|
||||
* to deal with some special needs such as degrading the process
|
||||
* <a href="http://en.wikipedia.org/wiki/User_identifier_(Unix)">UID</a> from
|
||||
* a <a href="http://en.wikipedia.org/wiki/Superuser">superuser</a> to a
|
||||
* normal user and changing the current VM security manager for better
|
||||
* security. To support such a case,
|
||||
* the {@link #setParentHandler(ChannelHandler) parentHandler} property is
|
||||
* provided.
|
||||
*
|
||||
* <h3>Configuring a child channel pipeline</h3>
|
||||
*
|
||||
* Every child channel has its own {@link ChannelPipeline} and you can
|
||||
* configure it in two ways.
|
||||
*
|
||||
* {@linkplain #setPipeline(ChannelPipeline) The first approach} is to use
|
||||
* the default pipeline property and let the bootstrap to clone it for each
|
||||
* new child channel:
|
||||
*
|
||||
* <pre>
|
||||
* ServerBootstrap b = ...;
|
||||
* ChannelPipeline p = b.getPipeline();
|
||||
*
|
||||
* // Add handlers to the pipeline.
|
||||
* p.addLast("encoder", new EncodingHandler());
|
||||
* p.addLast("decoder", new DecodingHandler());
|
||||
* p.addLast("logic", new LogicHandler());
|
||||
* </pre>
|
||||
*
|
||||
* {@linkplain #setPipelineFactory(ChannelPipelineFactory) The second approach}
|
||||
* is to specify a {@link ChannelPipelineFactory} by yourself and have full
|
||||
* control over how a new pipeline is created, at the cost of more complexity:
|
||||
*
|
||||
* <pre>
|
||||
* ServerBootstrap b = ...;
|
||||
* b.setPipelineFactory(new MyPipelineFactory());
|
||||
*
|
||||
* public class MyPipelineFactory implements ChannelPipelineFactory {
|
||||
* // Create a new pipeline for a new child channel and configure it here ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author The Netty Project (netty-dev@lists.jboss.org)
|
||||
* @author Trustin Lee (tlee@redhat.com)
|
||||
*
|
||||
@ -57,22 +136,66 @@ public class ServerBootstrap extends Bootstrap {
|
||||
|
||||
private volatile ChannelHandler parentHandler;
|
||||
|
||||
/**
|
||||
* Creates a new instance with no {@link ChannelFactory} set.
|
||||
* {@link #setFactory(ChannelFactory)} must be called at once before any
|
||||
* I/O operation is requested.
|
||||
*/
|
||||
public ServerBootstrap() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified initial {@link ChannelFactory}.
|
||||
*/
|
||||
public ServerBootstrap(ChannelFactory channelFactory) {
|
||||
super(channelFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional {@link ChannelHandler} which intercepts an event
|
||||
* of a new bound server-side channel which accepts incoming connections.
|
||||
*
|
||||
* @return the parent channel handler.
|
||||
* {@code null} if no parent channel handler is set.
|
||||
*/
|
||||
public ChannelHandler getParentHandler() {
|
||||
return parentHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an optional {@link ChannelHandler} which intercepts an event of
|
||||
* a new bound server-side channel which accepts incoming connections.
|
||||
*
|
||||
* @param parentHandler
|
||||
* the parent channel handler.
|
||||
* {@code null} to unset the current parent channel handler.
|
||||
*/
|
||||
public void setParentHandler(ChannelHandler parentHandler) {
|
||||
this.parentHandler = parentHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new channel which is bound to the local address which were
|
||||
* specified in the current {@code "localAddress"} option. This method is
|
||||
* similar to the following code:
|
||||
*
|
||||
* <pre>
|
||||
* ServerBootstrap b = ...;
|
||||
* b.connect(b.getOption("localAddress"));
|
||||
* </pre>
|
||||
*
|
||||
* @return a new bound channel which accepts incoming connections
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if {@code "localAddress"} option was not set
|
||||
* @throws ClassCastException
|
||||
* if {@code "localAddress"} option's value is
|
||||
* neither a {@link SocketAddress} nor {@code null}
|
||||
* @throws ChannelException
|
||||
* if failed to create a new channel and
|
||||
* bind it to the local address
|
||||
*/
|
||||
public Channel bind() {
|
||||
SocketAddress localAddress = (SocketAddress) getOption("localAddress");
|
||||
if (localAddress == null) {
|
||||
@ -81,6 +204,15 @@ public class ServerBootstrap extends Bootstrap {
|
||||
return bind(localAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new channel which is bound to the specified local address.
|
||||
*
|
||||
* @return a new bound channel which accepts incoming connections
|
||||
*
|
||||
* @throws ChannelException
|
||||
* if failed to create a new channel and
|
||||
* bind it to the local address
|
||||
*/
|
||||
public Channel bind(final SocketAddress localAddress) {
|
||||
final BlockingQueue<ChannelFuture> futureQueue =
|
||||
new LinkedBlockingQueue<ChannelFuture>();
|
||||
|
@ -22,8 +22,8 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper classes which enable an easy implementation of typical client and
|
||||
* server socket initialization.
|
||||
* Helper classes which enable an easy implementation of typical client side
|
||||
* and server side channel initialization.
|
||||
*
|
||||
* @apiviz.landmark
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user