package io.bitchat.client; import cn.hutool.core.lang.Assert; import io.bitchat.core.LoadBalancer; import io.bitchat.packet.PendingPackets; import io.bitchat.core.ServerAttr; import io.bitchat.core.init.Initializer; import io.bitchat.lang.constants.ResultCode; import io.bitchat.packet.Packet; import io.bitchat.packet.factory.PacketFactory; import io.bitchat.packet.Payload; import io.bitchat.packet.factory.PayloadFactory; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CompletableFuture; /** * @author houyi */ @Slf4j public class GenericClient implements Client { private ServerAttr serverAttr; private volatile boolean connected = false; private Channel channel = null; public GenericClient(ServerAttr serverAttr) { this.serverAttr = serverAttr; Initializer.init(); } public GenericClient(LoadBalancer loadBalancer) { Assert.notNull(loadBalancer, "loadBalancer can not be null"); this.serverAttr = loadBalancer.nextServer(); Initializer.init(); } @Override public void connect() { Assert.notNull(serverAttr, "serverAttr can not be null"); EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ClientInitializer(GenericClient.this)); } }); ChannelFuture future = bootstrap.connect(serverAttr.getAddress(), serverAttr.getPort()); future.addListener(new GenericFutureListener<Future<? super Void>>() { @Override public void operationComplete(Future<? super Void> f) throws Exception { channel = future.channel(); if (f.isSuccess()) { connected = true; log.info("[{}] Has connected to {} successfully", GenericClient.class.getSimpleName(), serverAttr); } else { log.warn("[{}] Connect to {} failed, cause={}", GenericClient.class.getSimpleName(), serverAttr, f.cause().getMessage()); // fire the channelInactive and make sure // the {@link HealthyChecker} will reconnect channel.pipeline().fireChannelInactive(); } } }); } @Override public CompletableFuture<Packet> sendRequest(Packet request) { // create a promise CompletableFuture<Packet> promise = new CompletableFuture<>(); if (!connected) { String msg = "Not connected yet!"; log.debug(msg); Payload payload = PayloadFactory.newErrorPayload(ResultCode.BIZ_FAIL.getCode(), msg); promise.complete(PacketFactory.newResponsePacket(payload, request.getId())); return promise; } Long id = request.getId(); PendingPackets.add(id, promise); ChannelFuture future = channel.writeAndFlush(request); future.addListener(new GenericFutureListener<Future<? super Void>>() { @Override public void operationComplete(Future<? super Void> f) throws Exception { if (!f.isSuccess()) { CompletableFuture<Packet> pending = PendingPackets.remove(id); if (pending != null) { pending.completeExceptionally(f.cause()); } } } }); return promise; } }