Demo3实现如下功能,客户端发送一个Request对象(包含id,方法名,方法参数列表和调用参数列表)到服务端,服务端解析后调用客户端指定的方法,并返回一个Response对象(包含id,是否异常,异常,返回值)。客户端和服务端均基于netty实现。代码已上传到http://download.csdn.net/detail/mrbcy/9747693
要点解析
netty
这个真的很重要,具体的参考官方的guide吧,写得通俗易懂。http://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-9
对象的编解码
对象的编码和解码基本使用了Demo1中写的ProtoStuffUtil工具类,它可以将一个对象转换成byte[],或者从一个byte[]转换成一个对象。但是这里还有一个问题,由于tcp协议的特性,对方多次发送的数据我们可能一次性就收到了,或者发送了一次,但是我们分多次收到。为了解决这个问题,我在netty的编码部分做了处理,在对象的数据之前加了一个字节来存放对象数据的长度。解码部分也做了相应的处理,先读取数据的长度,然后等到有足够的数据再来读取对象。关键代码如下:
编码部分:
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) throws Exception {
if(clazz.isInstance(msg)){
byte[] data = ProtostuffUtil.serializer(msg);
ByteBuf encoded = ctx.alloc().buffer(data.length+4);
encoded.writeInt(data.length);
encoded.writeBytes(data);
ctx.write(encoded, promise);
}
}
解码部分:
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
// 前4字节记录长度
if (in.readableBytes() < 4) {
return;
}
in.markReaderIndex();
int dataLength = in.readInt();
if (dataLength < 0) {
// 出错了
ctx.close();
}
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
//将ByteBuf转换为byte[]
byte[] data = new byte[dataLength];
in.readBytes(data);
//将data转换成object
@SuppressWarnings("unchecked")
Object obj = ProtostuffUtil.deserializer(data, clazz);
out.add(obj);
}
方法调用部分
这里涉及了反射的知识。服务端拿到客户端传入的request对象后,根据客户端指定的方法和参数来调用,然后把结果通过response发送给客户端。关键代码如下:
RpcResponse response = new RpcResponse();
try {
RpcRequest request = (RpcRequest) msg;
System.out.println("服务器收到调用请求:" + request);
response.setId(request.getId());
Method method = SampleServiceImpl.class.getDeclaredMethod(request.getMethodName(), request.getParamTypes());
Object result = method.invoke(serviceImpl, request.getArgs());
response.setResult(result);
} catch (Exception e) {
response.setSuccess(false);
response.setError(e);
}
写到这一步,框架的核心功能已经实现了一大部分,接下来就是用Spring了。