All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dongliu.prettypb.rpc.client.RpcClientChannel Maven / Gradle / Ivy

There is a newer version: 0.3.5
Show newest version
package net.dongliu.prettypb.rpc.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import net.dongliu.prettypb.rpc.common.PeerInfo;
import net.dongliu.prettypb.rpc.exception.ServiceException;
import net.dongliu.prettypb.rpc.listener.TcpConnectionEventListener;
import net.dongliu.prettypb.rpc.protocol.*;
import net.dongliu.prettypb.rpc.utils.Handlers;
import net.dongliu.prettypb.runtime.ExtensionRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeoutException;

/**
 * An RpcClientChannel is constructed once when a connection is established
 * with a server ( on both client and server ). The same RpcClientChannel
 * instance is used for the duration of the connection.
 *
 * If a client reconnects, a new RpcClientChannel instance is constructed.
 *
 * @author Dong Liu
 */
public class RpcClientChannel implements AutoCloseable {

    // below fields are updated when connectOnce been called
    /**
     * client peer info
     */
    private volatile PeerInfo clientPeer;

    /**
     * server peer info. available after connect
     */
    private volatile PeerInfo serverPeer;

    /**
     * determined by if client want to use compress and if server support compress
     */
    private volatile boolean useCompress;

    /**
     * the socket channel this rpc channel hold
     */
    private Channel channel;

    private final ServerInfo serverInfo;
    private final Bootstrap bootstrap;
    /**
     * CorrelationId for connect request and response
     */
    private final int connectCorrelationId;
    private final boolean tryUseCompress;
    private final int connectTimeout;
    private final List listeners;

    private static Logger logger = LoggerFactory.getLogger(RpcClientChannel.class);
    private final ExtensionRegistry extensionRegistry;
    private RpcClientHandler rpcClientHandler;

    public RpcClientChannel(ServerInfo serverInfo, Bootstrap bootstrap, int connectCorrelationId,
                            boolean tryUseCompress, int connectTimeout,
                            List listeners,
                            ExtensionRegistry extensionRegistry) {
        this.serverInfo = serverInfo;
        this.bootstrap = bootstrap;
        this.connectCorrelationId = connectCorrelationId;
        this.tryUseCompress = tryUseCompress;
        this.connectTimeout = connectTimeout;
        this.listeners = listeners;
        this.extensionRegistry = extensionRegistry;
    }

    /**
     * connect and setup pipeline.
     *
     * @throws java.io.IOException
     */
    public void connect() throws IOException, TimeoutException, InterruptedException {
        // Make a new connection.
        InetSocketAddress remoteAddress = new InetSocketAddress(serverInfo.getHost(),
                serverInfo.getPort());
        ChannelFuture connectFuture = bootstrap.connect(remoteAddress).awaitUninterruptibly();

        if (!connectFuture.isSuccess()) {
            throw new IOException("Failed to connect to " + remoteAddress, connectFuture.cause());
        }

        channel = connectFuture.channel();
        InetSocketAddress connectedAddress = (InetSocketAddress) channel.localAddress();
        clientPeer = new PeerInfo(connectedAddress.getHostName(), connectedAddress.getPort());
        sendConnectMsg();

        // connect response
        ClientConnectResponseHandler connectResponseHandler = (ClientConnectResponseHandler)
                channel.pipeline().get(Handlers.CLIENT_CONNECT);
        if (connectResponseHandler == null) {
            throw new IllegalStateException("No connectResponse handler in channel pipeline.");
        }

        ConnectResponse connectResponse = connectResponseHandler.waitResponse(connectTimeout);
        checkConnectResponse(channel, connectCorrelationId, connectResponse);

        if (connectResponse.hasServerPID()) {
            this.serverPeer = new PeerInfo(remoteAddress.getHostName(), remoteAddress.getPort(),
                    connectResponse.getServerPID());
        } else {
            this.serverPeer = new PeerInfo(remoteAddress.getHostName(), remoteAddress.getPort());
        }

        this.useCompress = connectResponse.isCompress();
        logger.info("connect to {}:{}, use compress: {}", serverInfo.getHost(),
                serverInfo.getPort(), useCompress);

        this.rpcClientHandler = completePipeline(this.useCompress, channel.pipeline());
        rpcClientHandler.notifyOpened();
    }

    private void sendConnectMsg() {
        ConnectRequest connectRequest = new ConnectRequest();
        connectRequest.setClientHostName(clientPeer.getHostName());
        connectRequest.setClientPort(clientPeer.getPort());
        connectRequest.setClientPID(clientPeer.getPid());
        connectRequest.setCorrelationId(connectCorrelationId);
        connectRequest.setCompress(tryUseCompress);

        WirePayload payload = new WirePayload();
        payload.setConnectRequest(connectRequest);
        channel.writeAndFlush(payload);
    }

    private void checkConnectResponse(Channel channel, int correlationId,
                                      ConnectResponse connectResponse)
            throws IOException, TimeoutException {
        if (connectResponse == null) {
            channel.close().awaitUninterruptibly();
            throw new TimeoutException("connect to rpc server error, response is null");
        }
        if (connectResponse.hasErrorCode()) {
            channel.close().awaitUninterruptibly();
            throw new IOException("connect response error: " + connectResponse.getErrorCode());
        }
        if (!connectResponse.hasCorrelationId()) {
            channel.close().awaitUninterruptibly();
            throw new IOException("connect response missing connectCorrelationId.");
        }
        if (connectResponse.getCorrelationId() != correlationId) {
            channel.close().awaitUninterruptibly();
            throw new IOException("Connect CorrelationId mismatch, sent " + correlationId
                    + " received " + connectResponse.getCorrelationId());
        }
    }

    /**
     * After RPC handshake has taken place, remove the RPC handshake
     * {@link ClientConnectResponseHandler} and add a {@link RpcClientHandler}
     * and {@link net.dongliu.prettypb.rpc.server.RpcServerHandler} to complete the Netty client side Pipeline.
     *
     * @return
     */
    private RpcClientHandler completePipeline(boolean compress, ChannelPipeline p) {

        if (compress) {
            p.addBefore(Handlers.FRAME_DECODER, Handlers.COMPRESSOR,
                    ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
            p.addAfter(Handlers.COMPRESSOR, Handlers.DECOMPRESSOR,
                    ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
        }

        TcpConnectionEventListener informer = new TcpConnectionEventListener() {
            @Override
            public void connectionClosed(RpcClientChannel rpcClientChannel) {
                for (TcpConnectionEventListener listener : listeners) {
                    listener.connectionClosed(rpcClientChannel);
                }
            }

            @Override
            public void connectionOpened(RpcClientChannel rpcClientChannel) {
                for (TcpConnectionEventListener listener : listeners) {
                    listener.connectionOpened(rpcClientChannel);
                }
            }
        };
        RpcClientHandler rpcClientHandler = new RpcClientHandler(informer, this, extensionRegistry);
        p.replace(Handlers.CLIENT_CONNECT, Handlers.RPC_CLIENT, rpcClientHandler);

        return rpcClientHandler;
    }

    public void writeRequest(ClientCallTask task, RpcRequest rpcRequest) {
        WirePayload payload = new WirePayload();
        payload.setRpcRequest(rpcRequest);
        if (!rpcClientHandler.registerTask(task)) {
            throw new ServiceException("duplicated correlation id, or task set closed");
        }
        channel.writeAndFlush(payload);
    }

    public PeerInfo getServerPeer() {
        return serverPeer;
    }

    public PeerInfo getClientPeer() {
        return clientPeer;
    }

    public boolean isUseCompress() {
        return useCompress;
    }

    public ServerInfo getServerInfo() {
        return serverInfo;
    }

    @Override
    public void close() {
        if (this.channel != null) {
            this.channel.close().awaitUninterruptibly();
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy