Netty实现RPC简直不要那么简单,只需要用户实现相应的序列化、反序列化和tcp拆包粘包问题就行
步骤:
- 定义服务接口和实现服务接口
- 定义RPC请求实体和响应实体
- 选择序列化框架
- 定义通信协议,解决tcp拆包粘包问题
- 定义RPC服务端和RPC客户端,Netty实现
步骤1:
- 服务接口
public interface CalculatorService {
double add(double op1, double op2);
double substract(double op1, double op2);
double multiply(double op1, double op2);
}
- 服务接口实现
public class CalculatorServiceImpl implements CalculatorService {
@Override
public double add(double op1, double op2) {
return op1 + op2;
}
@Override
public double substract(double op1, double op2) {
return op1 - op2;
}
@Override
public double multiply(double op1, double op2) {
return op1 * op2;
}
public static void main(String[] args) {
CalculatorService service = new CalculatorServiceImpl();
System.out.println(service.add(1.0, 2.0));
}
}
步骤2:
- 请求实体
public class RpcRequest {
private String id;
private String serviceName;
private String methodName;
private double param1;
private double param2;
public RpcRequest() {
super();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public double getParam1() {
return param1;
}
public void setParam1(double param1) {
this.param1 = param1;
}
public double getParam2() {
return param2;
}
public void setParam2(double param2) {
this.param2 = param2;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((methodName == null) ? 0 : methodName.hashCode());
long temp;
temp = Double.doubleToLongBits(param1);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(param2);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((serviceName == null) ? 0 : serviceName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RpcRequest other = (RpcRequest) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (methodName == null) {
if (other.methodName != null)
return false;
} else if (!methodName.equals(other.methodName))
return false;
if (Double.doubleToLongBits(param1) != Double.doubleToLongBits(other.param1))
return false;
if (Double.doubleToLongBits(param2) != Double.doubleToLongBits(other.param2))
return false;
if (serviceName == null) {
if (other.serviceName != null)
return false;
} else if (!serviceName.equals(other.serviceName))
return false;
return true;
}
@Override
public String toString() {
return "RpcRequest [id=" + id + ", serviceName=" + serviceName + ", methodName=" + methodName + ", param1="
+ param1 + ", param2=" + param2 + "]";
}
}
- 响应实体
public class RpcResponse {
private String id;
private String serviceName;
private String methodName;
private String cause;
private double result;
public RpcResponse() {
super();
}
public String getId() {
return id;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public void setId(String id) {
this.id = id;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getCause() {
return cause;
}
public void setCause(String cause) {
this.cause = cause;
}
public double getResult() {
return result;
}
public void setResult(double result) {
this.result = result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cause == null) ? 0 : cause.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((methodName == null) ? 0 : methodName.hashCode());
long temp;
temp = Double.doubleToLongBits(this.result);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RpcResponse other = (RpcResponse) obj;
if (cause == null) {
if (other.cause != null)
return false;
} else if (!cause.equals(other.cause))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (methodName == null) {
if (other.methodName != null)
return false;
} else if (!methodName.equals(other.methodName))
return false;
if (Double.doubleToLongBits(result) != Double.doubleToLongBits(other.result))
return false;
return true;
}
@Override
public String toString() {
return "RpcResponse [id=" + id + ", methodName=" + methodName + ", cause=" + cause + ", result=" + result + "]";
}
}
步骤3:
- 序列化接口
public interface Serializer {
public <T> Object deserialize(byte[] bytes, Class<T> clazz);
public <T> byte[] serialize(T obj);
}
- protobuf序列化
public class ProtobufSerializer implements Serializer {
private static Objenesis objenesis = new ObjenesisStd(true);
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>();
private static <T> Schema<T> getSchema(Class<T> cls) {
@SuppressWarnings("unchecked")
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
if (schema != null) {
cachedSchema.put(cls, schema);
}
}
return schema;
}
@Override
public <T> byte[] serialize(T obj) {
@SuppressWarnings("unchecked")
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new RpcException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
@Override
public <T> Object deserialize(byte[] bytes, Class<T> clazz) {
try {
T message = objenesis.newInstance(clazz);
Schema<T> schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(bytes, message, schema);
return message;
} catch (Exception e) {
throw new RpcException(e.getMessage(), e);
}
}
public static void main(String[] args) {
RpcRequest request = new RpcRequest();
request.setMethodName("add");
request.setParam1(1.0);
request.setParam2(3.0);
ProtobufSerializer sr = new ProtobufSerializer();
byte[] bytes = sr.serialize(request);
System.out.println(bytes.length);
RpcRequest rr = (RpcRequest) sr.deserialize(bytes, RpcRequest.class);
System.out.println(rr.toString());
}
}
步骤4:
- 基于长度的变长包解决tcp拆包粘包问题
- 解码
public class RpcDecode extends ByteToMessageDecoder {
private Class<?> genericClass;
private Serializer serializer;
public RpcDecode(Class<?> genericClass, Serializer serializer) {
this.genericClass = genericClass;
this.serializer = serializer;
}
@Override
public final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 4) {
return;
}
in.markReaderIndex();
int dataLength = in.readInt();
if (dataLength < 0) {
ctx.close();
}
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data);
Object obj = serializer.deserialize(data, genericClass);
out.add(obj);
}
}
- 编码
public class RpcEncode extends MessageToByteEncoder<Object> {
private Class<?> genericClass;
private Serializer serializer;
public RpcEncode(Class<?> genericClass, Serializer serializer) {
this.genericClass = genericClass;
this.serializer = serializer;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (genericClass.isInstance(in)) {
byte[] data = serializer.serialize(in);
out.writeInt(data.length);
out.writeBytes(data);
}
}
}
步骤5:
- RPC服务端
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private Thread thread;
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void start(final RpcBuilder rpcBuilder) throws Exception {
thread = new Thread(new Runnable() {
@Override
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcDecode(RpcRequest.class, SerializerFactory.getSerializer()))
.addLast(new RpcEncode(RpcResponse.class, SerializerFactory.getSerializer()))
.addLast(new NettyServerHandler(rpcBuilder));
}
})
.option(ChannelOption.SO_TIMEOUT, 100)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
logger.info("接口服务器端启动成功......");
Channel serviceChannel = future.channel().closeFuture().sync().channel();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
});
thread.start();
}
public Thread getThread() {
return this.thread;
}
public void stop() throws Exception {
if (thread != null && thread.isAlive()) {
thread.interrupt();
}
logger.info("接口服务器端停止......");
}
}
- RPC客户端
public class NettyClient {
private Channel channel;
private static ConcurrentHashMap<String, SynchronousQueue<RpcResponse>> mapInfo = new ConcurrentHashMap<>();
public static void putSunchronousQuee(String id, SynchronousQueue<RpcResponse> queue) {
mapInfo.put(id, queue);
}
public static SynchronousQueue<RpcResponse> getSynchronousQueue(String id) {
return mapInfo.get(id);
}
public static void removeById(String id) {
mapInfo.remove(id);
}
public void sendRpcRequest(RpcRequest rpcRequest) throws Exception {
try {
this.channel.writeAndFlush(rpcRequest).sync();
} catch (Exception e) {
throw e;
}
}
public void init(String host, int port, final Serializer serializer) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcEncode(RpcRequest.class, serializer))
.addLast(new RpcDecode(RpcResponse.class, serializer))
.addLast(new NettyClientHandler());
}
})
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_KEEPALIVE, true);
this.channel = bootstrap.connect(host, port).sync().channel();
System.out.println("接口服务端连接成功......");
}
public boolean isValidate() {
if (this.channel != null) {
return this.channel.isActive();
}
return false;
}
public void close() {
if (this.channel != null) {
if (this.channel.isOpen()) {
this.channel.close();
}
}
}
}