1. BootStrap startup code
The code on the client side begins
EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new EchoClientHandler()); }}); ChannelFuture f = b.connect(HOST, PORT).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); }Copy the code
All required for client initialization:
- EventLoopGroup: The EventLoopGroup must be specified for both the server and client. In this example, the NioEventLoopGroup is specified, representing a NIO EventLoopGroup
- ChannelType: specifies the Channel type. Because it is a client, NioSocketChannel is used
- Handler: Sets the data Handler
2.NioSocketChannel initialization and instantiation process
For those of you who have read the NioServerSocketChannel source code analysis, NioServerSocketChannel is a layer of encapsulation for Java NIO ServerSocketChannel. NioSocketChannel also encapsulates Java SocketChannel. NioSocketChannel’s class hierarchy is as follows:
In addition to TCP, Netty supports many other connection protocols, each with NIO(asynchronous IO) and OIO(old-io) versions. Different types of channels correspond to different blocking types of connections over different protocols. Here are some common Channel types:
NioSocketChannel, which stands for asynchronous client TCP Socket connections. NioServerSocketChannel, an asynchronous server TCP Socket connection. NioDatagramChannel, asynchronous UDP connection NioSctpChannel, asynchronous client Sctp connection. NioSctpServerChannel, asynchronous Sctp server side connection. OioSocketChannel: synchronous client TCP Socket connection. OioServerSocketChannel: Synchronous server TCP Socket connection. OioDatagramChannel, synchronous UDP connection OioSctpChannel, synchronous Sctp server connection. OioSctpServerChannel: Synchronous client TCP Socket connection.Copy the code
The channel() method is very familiar, similar to the server-side factory class that creates a NioSocketChannel for subsequent channel creation.
NioSocketChannel instantiation is the process of the real: the Bootstrap. Connect – > Bootstrap. DoResolveAndConnect – > AbstractBootstrap. InitAndRegister
Here is the initAndRegister method:
final ChannelFuture initAndRegister() {
Channel channel = null;
channel = channelFactory.newChannel();
init(channel);
ChannelFuture regFuture = config().group().register(channel);
}Copy the code
In newChannel, a newChannel instance is obtained through newInstance on the class object, thus invoking the default constructor for NioSocketChannel. There is a chain of superclass constructor calls, values to AbstractChannel initialization pipleLine and unsafe. This process is similar to NioServerSocketChannel and will not be described here.
3. Connect to the client
The client connects by calling the Connect method of Bootstrap. In connect, some parameter initialization takes place and the result is a call to the doConnect method, which looks like this:
private static void doConnect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) { final Channel channel = connectPromise.channel(); channel.eventLoop().execute(new Runnable() { @Override public void run() { if (localAddress == null) { channel.connect(remoteAddress, connectPromise); } else { channel.connect(remoteAddress, localAddress, connectPromise); } connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); }}); }Copy the code
The eventLoop thread calls the Connect method of a Channel. What is the type of this Channel? We examined this in the Channel initialization section, where the Channel type is NioSocketChannel. Channel.connect calls DefaultChannelPipeline#connect:
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return pipeline.connect(remoteAddress, localAddress, promise);
}Copy the code
Pipeline connect code is as follows:
public final ChannelFuture connect(
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, localAddress, promise);
}Copy the code
Tail field is a TailContext instance, and TailContext AbstractChannelHandlerContext subclasses, and not implemented the connect method, So call here is actually AbstractChannelHandlerContext. Connect, we see the realization of this method:
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
return promise;
}Copy the code
The code above there is a key, namely the final AbstractChannelHandlerContext next = findContextOutbound (), call findContextOutbound method here, From DefaultChannelPipeline two-way chain table of tail, forward to find the first outbound AbstractChannelHandlerContext to true:
private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (! ctx.outbound); return ctx; }Copy the code
Head is an instance of HeadContext that implements the ChannelOutboundHandler interface and has an outbound field of true. So in findContextOutbound, Find AbstractChannelHandlerContext object is actually a head, and then call AbstractChannelHandlerContext invokeConnect method, its code is as follows:
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { if (invokeHandler()) { remoteAddress, localAddress, promise); } else { connect(remoteAddress, localAddress, promise); }}Copy the code
The invokeConnect method executes the head’s connect method:
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}Copy the code
The unsafe connect method is very simple, simply calling the unsafe connect method, which was retrieved from the NioSocketChannel when constructing the HeadContext node:
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, false, true);
unsafe = pipeline.channel().unsafe();
setAddComplete();
}Copy the code
Rao a big circle, back to NioSocketChannel unsafe, unsafe Channel is an internal interface, the connect method in AbstractNioChannel. AbstractNioUnsafe:
public final void connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { boolean wasActive = isActive(); if (doConnect(remoteAddress, localAddress)) { fulfillConnectPromise(promise, wasActive); } else { ... }}Copy the code
The doConnect method is implemented in NioSocketChannel:
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
}
//SocketUtils
public static boolean connect(final SocketChannel socketChannel, final SocketAddress remoteAddress)
throws IOException {
return socketChannel.connect(remoteAddress);
}Copy the code
First of all, is to obtain the Java NIO SocketChannel, from NioSocketChannel javaChannel return SocketChannel object; Then the socketutils. connect method is called to connect the Socket at the Java NIO level.