net.dongliu.prettypb.rpc.client.RpcClientChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of prettypb-rpc Show documentation
Show all versions of prettypb-rpc Show documentation
proto rpc libs, compatible with proto-rpc-pro
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();
}
}
}