接着上面的服务端继续讲解客户端的开发
1,客户端的核心代码实现
public class RpcClientLoader extends RpcServerBase {
//单例模式的设计
private volatile static RpcClientLoader rpcClientLoader;
//执行的线程池操作
private static ListeningExecutorService threadPoolExecuter = MoreExecutors.listeningDecorator(RpcExecuter.getExecuter(10, 50, -1));
private EventLoopGroup work = new NioEventLoopGroup(PROCESS);
//发送消息的句柄
private PushMessageHandler pushMessageHandler;
//单例操作涉及到数据安全采用锁机制
private ReentrantLock lock = new ReentrantLock();
private Condition connect = lock.newCondition();
private Condition push = lock.newCondition();
/**
* 单例构造函数
*/
private RpcClientLoader() {
}
/**
* 单例提供对外的访问变量
* @return
*/
public static RpcClientLoader getInstance() {
return RpcClientLoaderHelp.rpcClientLoader;
}
/**
* 内部类构造安全的单例模式
*/
private static class RpcClientLoaderHelp {
private static RpcClientLoader rpcClientLoader = new RpcClientLoader();
}
/**
* 加载对应的ip和协议
* @param inetAddress
*/
public synchronized void init(String inetAddress) {
String[] addrs = inetAddress.split(FinalStaticUtil.DELIMITER);
if (addrs.length == 3) {
//设置当前的通信协议
setRpcSerializeProtocol(ProtocolSelecter.getProtocol(addrs[0]));
String host = addrs[1];
int port = Integer.valueOf(addrs[2]);
//netty链接的时候需要该对象
InetSocketAddress serviceAddr = new InetSocketAddress(host, port);
//采用google提供的guava框架中的线程池操作类
//PushInitTask类是主要设计netty客户端连接和相关netty句柄的操作实现类
ListenableFuture<Boolean> future = threadPoolExecuter.submit(new PushInitTask(work, serviceAddr, rpcSerializeProtocol));
//对连接结果进行控制,guava框架中的内容
Futures.addCallback(future, new FutureCallback<Boolean>() {
//连接成功后的处理
//成功的核心处理就是把发送消息的句柄传递回来,供以后客户端发送消息使用
@Override
public void onSuccess(Boolean result) {
try {
lock.lock();
if (null == pushMessageHandler) {
push.await();
}
if (result && null != pushMessageHandler) {
connect.signalAll();
}
} catch (Exception e) {
} finally {
lock.unlock();
}
}
//连接失败后的处理
@Override
public void onFailure(Throwable t) {
// TODO
}
}, threadPoolExecuter);
}
}
/**
* 在netty的执行过程中设置相应的服务器状态
* @param channelState
*/
public void setChannelState(ChannelState channelState) {
state = channelState;
}
/**
*
*/
public synchronized void close() {
pushMessageHandler.close();
threadPoolExecuter.shutdown();
work.shutdownGracefully();
//设置服务器状态
state = ChannelState.CLOSED;
}
/**
* 对外提供netty发送消息的句柄的访问,保证数据安全,并且保证该句柄必须存在
* @return
* @throws InterruptedException
*/
public PushMessageHandler getPushMessageHandler() throws InterruptedException {
try {
lock.lock();
//如果发送消息的句柄还没有设置则还没有连接上服务端
if (null == pushMessageHandler) {
connect.await();
}
return pushMessageHandler;
} finally {
lock.unlock();
}
}
/**
* 设计netty的客户端消息发送句柄,该方法主要提供给netty链接成功后的操作
* @param pushMessageHandler
*/
public void setPushMessageHandler(PushMessageHandler pushMessageHandler) {
try {
lock.lock();
this.pushMessageHandler = pushMessageHandler;
//该方法是在client连接到server后的操作,所以是连接成功,通知可以发送消息了
push.signalAll();
} finally {
lock.unlock();
}
}
}
2,netty客户端的实现
public class PushInitTask implements Callable<Boolean> {
private EventLoopGroup eventLoopGroup;
private InetSocketAddress address;
private RpcSerializeProtocol protocol;
public PushInitTask (EventLoopGroup eventLoopGroup, InetSocketAddress address, RpcSerializeProtocol protocol) {
this.eventLoopGroup = eventLoopGroup;
this.address = address;
this.protocol = protocol;
}
/**
* 下面是netty的标准实现
* 采用长连接的机制
* @return
* @throws Exception
*/
@Override
public Boolean call() throws Exception {
//执行对netty的客户端操作
Bootstrap bootstrap = new Bootstrap();
//下面的参数需要在后期的升级开发中进行配置的功能升级
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000);
//设置通道流,根据序列化的机制采用不同的管道流
bootstrap.handler(new PushChannelHander(protocol));
//链接服务端
ChannelFuture future = null;
try {
future = bootstrap.connect(address);
} catch (Exception e) {
return false;
}
//设置服务器为正常状态
RpcClientLoader.getInstance().setChannelState(ChannelState.ALIVE);
//将发送消息的句柄设置到相应的类中,给系统提示可以进行消息发送
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
//连接成功后的消息发送句柄
RpcClientLoader.getInstance().setPushMessageHandler(
channelFuture.channel().pipeline().get(PushMessageHandler.class));
}
});
return true;
}
}
3,netty客户端的句柄通道,客户端发送消息及接受反馈的操作主要体现在下面的配置
public class PushChannelHander extends ChannelInitializer<SocketChannel> {
private RpcSerializeProtocol protocol;
public PushChannelHander (RpcSerializeProtocol protocol) {
this.protocol = protocol;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获得netty的通道,并对该通道设置相应的句柄
ChannelPipeline pipeline = socketChannel.pipeline();
//设置句柄的具体实现方法
PushPipelineFrame.initPipeline(pipeline, protocol);
}
}
public class PushPipelineFrame {
/**
* 设置相应的编码解码操作
*
* @param pipeline
* @param protocol
*/
public static void initPipeline(ChannelPipeline pipeline, RpcSerializeProtocol protocol) {
switch (protocol) {
case HESSIONSERIALIZE:
HessionMessageCodec codec = new HessionMessageCodec();
pipeline.addLast(new HessionMessageDecoder(codec));
pipeline.addLast(new HessionMessageEncoder(codec));
pipeline.addLast(new PushMessageHandler());
break;
case KRYOSERIALIZE:
break;
}
}
}
4,消息的实际操作
public class PushMessageHandler extends ChannelInboundHandlerAdapter {
private ConcurrentHashMap<String, PushCallback> callbacks = new ConcurrentHashMap<String, PushCallback>();
//执行的数据通道,需要由通道进行数据的传输
private volatile Channel channel;
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
this.channel = ctx.channel();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
/**
* 服务器端返回的数据结果
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
RpcResponse rpcResponse = (RpcResponse) msg;
String requestId = rpcResponse.getRequestId();
if (callbacks.containsKey(requestId)) {
PushCallback callback = callbacks.get(requestId);
//将结果放置在客户端的回调函数中
callback.doEnd(rpcResponse);
callbacks.remove(requestId);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//super.exceptionCaught(ctx, cause);
ctx.close();
}
/**
* 客户端执行数据发送的真是操作,可以通过netty来实现
* @param rpcRequest
* @return
*/
public PushCallback pushMessage(RpcRequest rpcRequest) {
PushCallback callback = new PushCallback(rpcRequest);
callbacks.put(rpcRequest.getRequestId(), callback);
//数据写入通道并刷新到服务端
channel.writeAndFlush(rpcRequest);
return callback;
}
/**
* 客户端关闭通道的处理
* 仅仅在客户端程序停止时调用
*/
public void close() {
channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
根据netty的特点,客户端的整体流程如下:
1,用户根据服务器的特点启动辅助类,保证服务是可以正常运行,并且调用netty的创建
2,用户线程创建Bootstrap,通过api设置netty客户端的相关参数
3,创建客户端的Reactor的线程组,该线程组主要处理客户端连接,io读写的NioEventLoopGroup,可以指定线程的数量,默认是cpu内核的2倍
4,通过Bootstrap的channelFactory和用户指定的channel类型,用作客户端处理的NioSocketChannel,区别于服务端的NioServerSocketChannel
5,创建ChannelHandlerPipeline,用户调度和执行网络事件,就是我们增加的handler功能
6,异步发起tcp连接,判断是否连接成功,如果注册成功,则通过继续用户数据的接受和消息的发送,如果没有连接成功,则等待链接结果
7,注册对应的网络监听状态到多路复用器,并且轮询个channel,处理连接的结果
8,连接成功,设置future结果,发送连接成功事件,出发channelpipeline执行
9,有channelpipeline调度执行系统和用户的channelhandler,执行自定义的业务逻辑