相对服务端,客户端的实现就简单了许多,因为编码器已经实现好了,实现客户端的处理器:
package com.info.core;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomClientHandler extends SimpleChannelInboundHandler<Protocol<Response>> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Protocol<Response> msg) throws Exception {
log.info("receive rpc server result");
long requestId = msg.getHeader().getRequestId();
CustomFuture<Response> future = RequestHolder.REQUEST_MAP.remove(requestId);
future.getPromise().setSuccess(msg.getContent()); //返回结果
}
}
这里稍微麻烦的是获取结果的方法,因为netty
对我们java的nio
进行了封装,提供了多路复用的IO
处理方式,极大的提高了效率。因此,netty
对我们的future
对象进行了拓展,netty
提供的Promise
对象是可写的 Future
,用于设置IO
操作的结果。
package com.info.core;
import io.netty.util.concurrent.Promise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor
@Getter
@Setter
public class CustomFuture<T> {
//Promise是可写的 Future, Future自身并没有写操作相关的接口,
// Netty通过 Promise对 Future进行扩展,用于设置IO操作的结果
private Promise<T> promise;
}
同时,我们需要一个管理器来管理请求id与封装结果的CustomFuture
对象之间的映射关系。
package com.info.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
// 保存requestid和future的对应结果
public class RequestHolder {
public static final AtomicLong REQUEST_ID = new AtomicLong();
public static final Map<Long, CustomFuture> REQUEST_MAP = new ConcurrentHashMap<>();
}
这样我们就可以通过promise
获取我们的调用结果了。
同样的,客户端也需要一个初始化器把我们所写的主键联系起来使之生效。
package com.info.protocol.netty.client;
import com.info.protocol.netty.core.codec.CustomDecoder;
import com.info.protocol.netty.core.codec.CustomEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LoggingHandler;
public class CustomClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler())
.addLast(new CustomEncoder())
.addLast(new CustomDecoder())
.addLast(new CustomClientHandler());
}
}
最后需要把我们的初始化器添加到客户端处理逻辑中。客户端相处理逻辑对简单,连接服务端发送请求。
package com.info.protocol;
import com.info.core.CustomClientInitializer;
import com.info.core.Protocol;
import com.info.core.Request;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class NettyClient {
private final Bootstrap bootstrap;
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
private String serviceAddress;
private int servicePort;
public NettyClient(String serviceAddress, int servicePort) {
log.info("begin init NettyClient");
bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new CustomClientInitializer());
this.serviceAddress = serviceAddress;
this.servicePort = servicePort;
}
public void sendRequest(Protocol<Request> protocol) throws InterruptedException {
ChannelFuture future = bootstrap.connect(this.serviceAddress, this.servicePort).sync();
future.addListener(listener -> {
if (future.isSuccess()) {
log.info("connect rpc server {} success.", this.serviceAddress);
} else {
log.error("connect rpc server {} failed .", this.serviceAddress);
future.cause().printStackTrace();
eventLoopGroup.shutdownGracefully();
}
});
log.info("begin transfer data");
// 发送数据
future.channel().writeAndFlush(protocol);
}
}
至此,主体代码实现完毕,下节内容开始整合各个模块准备测试。
系列文章传送门如下:
手写RPC(一) 絮絮叨叨
手写RPC(二) 碎碎念
手写RPC(三) 基础结构搭建
手写RPC(四) 核心模块网络协议模块编写 ---- netty服务端
手写RPC(五) 核心模块网络协议模块编写 ---- 自定义协议
手写RPC(六) 核心模块网络协议模块编写 ---- 实现编解码器
手写RPC(八) provider、consumer 实现
手写RPC(九) 测试
手写RPC(十) 优化
关于 LengthFieldBasedFrameDecoder 不得不说的事