聊天室优化——扩展序列化算法
写一个枚举类
来管理不同的序列化和反序列化方式
public interface Serializer {
/**
* 【反序列化方法】
* 将字节数组还原为java对象
* @param clazz:需要还原为java中什么类型的对象
* @param bytes
* @return
* @param <T>
*/
<T> T deserialize(Class<T> clazz, byte[] bytes);
/**
* 【序列化方法】
* 将object对象转为字节数组
* @param object
* @return
* @param <T>
*/
<T> byte[] serialize(T object) throws IOException;
// 使用内部枚举:管理序列化算法
enum Algorithm implements Serializer {
Java { // java自带的序列化方式
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
T msg = (T) ois.readObject(); // 因为序列化的时候是把msg对象进行序列化,所以反序列化也是对msg对象进行操作
return msg;
}catch (Exception e) {
throw new RuntimeException("反序列化失败", e);
}
}
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
byte[] bytes = bos.toByteArray();
return bytes;
}catch (IOException e) {
throw new RuntimeException("序列化失败", e);
}
}
},
Json { // google的序列化方式(gson)
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
String json = new String(bytes, StandardCharsets.UTF_8);
T msg = new Gson().fromJson(json, clazz);
return msg;
}
@Override
public <T> byte[] serialize(T object) throws IOException {
String json = new Gson().toJson(object);
return json.getBytes(StandardCharsets.UTF_8);
}
}
}
}
自定义编码、解码器
最终如下:
@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {
/**
* 编码器,最好固定字符要是2的整数倍
* @param ctx
* @param msg
* @param out
* @throws Exception
*/
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
// 1. 魔数:4字节的魔数
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 版本:1字节
out.writeByte(1);
/*
3. 序列化算法:1字节
0 - jdk
1 - json
*/
out.writeByte(Config.getSerializerAlgorithm().ordinal()); // 序列化方式(从配置中读取)
// 4. 指令类型:1字节
out.writeByte(msg.getMessageType());
// 5. 请求序号:4字节
out.writeInt(msg.getSequenceId());
/*
6. 目前固定字符是15字节(4 + 1 + 1 + 1 + 4 + 4)
固定字符最好要是2的整数倍
所以添加一个无意义的字节
*/
out.writeByte(0xff);
// 7. 消息正文
byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
// 8. 正文长度:4字节
out.writeInt(bytes.length);
// 9. 写入内容
out.writeBytes(bytes);
}
/**
* 解码
* @param ctx
* @param in
* @param out
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
/*
readXxx():让读指针向后走
getXxx():只读,不会改变读指针
*/
// 1. 魔数
int magicNum = in.readInt();
// 2. 版本号
byte version = in.readByte();
// 3. 序列化方式
byte serializerType = in.readByte(); // 0 - java;1 - json
// 4. 指令类型
byte messageType = in.readByte();
// 5. 请求序号
int sequenceId = in.readInt();
// 6. 无意义的字符(只用于填充)
in.readByte();
// 7. 正文长度
int length = in.readInt();
// 8. 正文内容
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
// 找到反序列化算法
Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerType];
// 确定具体消息类型
Class<? extends Message> messageClazz = Message.getMessageClass(messageType);
// 反序列化
Message msg = algorithm.deserialize(messageClazz, bytes);
log.debug("magicNum:{}, version:{}, serializerType:{}, messageType:{}, sequenceId:{}", magicNum, version, serializerType, messageType, sequenceId);
log.debug("msg:{}", msg);
/*
!netty规定!:解码出来的消息必须存入out这个形参中,不然接下来的handler就无法获得消息
*/
out.add(msg);
}
}
其中:
序列化方式是从配置文件中读取,读取后通过枚举的对象转换为byte
参数调优
CONNECT_TIMEOUT_MILLIS
SocketChannel参数,在客户端建立连接时,如果在指定毫秒内无法连接,就会抛出timeout异常。
- 客户端:通过
.option()
方法配置参数 - 服务端:
- 给ServerSocketChannel配置参数:
new ServerBootstrap().option()
——影响服务端自身的监听行为(如绑定端口的超时) - 给SocketChannel配置参数:
new ServerBootstrap().childOption()
——影响服务端接受的客户端连接的参数
- 给ServerSocketChannel配置参数:
客户端:
@Slf4j
public class Test01ConnectTimeout {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 5s内没有和服务器建立连接就会出现超时异常
.group(group)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
future.sync().channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
log.debug("timeout");
}
}
}
SO_BACKLOG
属于ServerSocketChannel参数
还没有完成三次握手:半连接队列
已经完成三次握手:全连接队列
因为服务器的处理能力是有限的,所以需要把之前已经三次握手的连接信息放入连接队列里。
注意,会先放入全连接队列,再调用accept()
只有accept()处理不了时,才会把连接信息放入全连接队列中堆积
Netty应用:RPC框架
客户端与服务器之间的RPC通信
服务器
@Slf4j
public class RpcServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler(); // rpc请求消息处理器
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProtocolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(RPC_HANDLER);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
rpc请求消息处理器:
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) {
RpcResponseMessage response = new RpcResponseMessage();
try {
HelloService service = (HelloService) ServicesFactory.getService(Class.forName(msg.getInterfaceName()));
Method method = service.getClass().getMethod(msg.getMethodName(), msg.getParameterTypes());
Object invoke = method.invoke(service, msg.getParameterValue());
response.setReturnValue(invoke); // 设置正常信息
}catch (Exception e) {
e.printStackTrace();
response.setExceptionValue(e); // 设置异常信息
}
ctx.writeAndFlush(response);
}
// 测试代码
public static void main(String[] args) throws Exception {
RpcRequestMessage message = new RpcRequestMessage(
1,
"com.itheima.server.service.HelloService",
"sayHello",
String.class,
new Class[]{String.class},
new Object[]{"xiaolin"}
);
HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));
Method sayHello = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());
Object invoke = sayHello.invoke(service, message.getParameterValue());
System.out.println(invoke);
}
}
通过反射,远程调用HelloService里的方法
GSON类型不支持问题
public class Test03Gson {
public static void main(String[] args) {
System.out.println(new Gson().toJson(String.class));
}
}
【
代码报错
】:Exception in thread “main” java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: java.lang.String. Forgot to register a type adapter?
【原因
】:缺少了一个类型转换器,需要自己补一个
修改后代码如下:
public class Test03Gson {
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();
System.out.println(gson.toJson(String.class)); // "java.lang.String"
}
static class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
@Override
public Class<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
String str = json.getAsString();
try {
return Class.forName(str);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public JsonElement serialize(Class<?> src, Type type, JsonSerializationContext context) { // class -> json
return new JsonPrimitive(src.getName());
}
}
}
序列化与反序列化算法:
public interface Serializer {
/**
* 【反序列化方法】
* 将字节数组还原为java对象
* @param clazz:需要还原为java中什么类型的对象
* @param bytes
* @return
* @param <T>
*/
<T> T deserialize(Class<T> clazz, byte[] bytes);
/**
* 【序列化方法】
* 将object对象转为字节数组
* @param object
* @return
* @param <T>
*/
<T> byte[] serialize(T object) throws IOException;
// 使用内部枚举:管理序列化算法
enum Algorithm implements Serializer {
Java { // java自带的序列化方式
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
T msg = (T) ois.readObject(); // 因为序列化的时候是把msg对象进行序列化,所以反序列化也是对msg对象进行操作
return msg;
}catch (Exception e) {
throw new RuntimeException("反序列化失败", e);
}
}
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
byte[] bytes = bos.toByteArray();
return bytes;
}catch (IOException e) {
throw new RuntimeException("序列化失败", e);
}
}
},
Json { // google的序列化方式(gson)
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
String json = new String(bytes, StandardCharsets.UTF_8);
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create(); // 定义一个带转换器的gson
T msg = gson.fromJson(json, clazz);
return msg;
}
@Override
public <T> byte[] serialize(T object) throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create(); // 定义一个带转换器的gson
String json = gson.toJson(object);
return json.getBytes(StandardCharsets.UTF_8);
}
}
}
class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
@Override
public Class<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
String str = json.getAsString();
try {
return Class.forName(str);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public JsonElement serialize(Class<?> src, Type type, JsonSerializationContext context) { // class -> json
return new JsonPrimitive(src.getName());
}
}
}
客户端
@Slf4j
public class RpcClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProtocolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(RPC_HANDLER);
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
// 发送请求消息
ChannelFuture future = channel.writeAndFlush(new RpcRequestMessage(
1,
"com.itheima.server.service.HelloService",
"sayHello",
String.class,
new Class[]{String.class},
new Object[]{"xiaolin"}
)).addListener(promise -> { // 调试
if(!promise.isSuccess()) { // 失败 - 打印信息
Throwable cause = promise.cause();
log.error("error", cause);
}
});
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
获取channel(channel是单例的)
因为RPC客户端需要与服务器保持长连接,而不是每次请求都建立新的连接,所以这里Client创建与服务器之间的channel通道时,只要是单例的即可。
@Slf4j
public class RpcClientManager {
private static Channel channel = null;
private static final Object LOCK = new Object();
// 获取唯一的channel对象
public static Channel getChannel() {
if(channel != null) {
return channel;
}
// 单例模式 - 双重检查锁
synchronized(LOCK) { // 如果同时有两个线程到了这里,只会有一个线程获取锁,此时就需要再里边在进行一次判空
if(channel != null) {
return channel;
}
initChannel(); // 初始化channel
return channel;
}
}
/*初始化channel*/
private static void initChannel() {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProtocolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(RPC_HANDLER);
}
});
try {
channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().addListener(future -> { // channel关闭时触发
group.shutdownGracefully();
});
} catch (Exception e) {
log.error("client error", e);
}
}
}
调用代码如下:
public static void main(String[] args) {
RpcRequestMessage message = new RpcRequestMessage(
1,
"com.itheima.server.service.HelloService",
"sayHello",
String.class,
new Class[]{String.class},
new Object[]{"xiaolin"}
);
getChannel().writeAndFlush(message);
}
【
问题
】上边这种调用方式并不直观,对于我们来说,可能更想通过helloService.sayHello("xiaolin");
这个方法来调用。
【解决
】对于上边的问题,可以考虑用代理来解决
代理
public static void main(String[] args) {
HelloService proxyService = getProxyService(HelloService.class);
System.out.println(proxyService.sayHello("xiaolin"));
}
/*创建代理类*/
public static<T> T getProxyService(Class<T> serviceClass) {
ClassLoader loader = serviceClass.getClassLoader(); // 类加载器
Class<?>[] interfaces = new Class[]{serviceClass};
int sequecedId = SequenceIdGenerator.nextId();
Object o = Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> {
// 1. 将方法调用转换为消息对象
RpcRequestMessage message = new RpcRequestMessage(
sequecedId,
serviceClass.getName(), // 接口全类名
method.getName(), // 方法类名
method.getReturnType(), // 方法返回值类型
method.getParameterTypes(), // 方法参数类型
args // 实际参数值
);
// 2. 发送消息对象
getChannel().writeAndFlush(message);
// 3. 准备一个空的Promise对象,用来异步接收结果
DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
RpcResponseMessageHandler.PROMISES.put(sequecedId, promise);
// 4. 等待promise的结果
promise.await();
if(promise.isSuccess()) {
// 调用正常
return promise.getNow();
}else {
// 调用失败
throw new RuntimeException(promise.cause());
}
});
return (T)o;
}
把结果存放到Promise中:
@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {
public static final Map<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>(); // 序号 - 用来接受结果的Promise对象
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {
// 拿到空的promise
Promise<Object> promise = PROMISES.remove(msg.getSequenceId()); // 返回对应的value值,并且从集合中移除
if(promise != null) {
Object returnValue = msg.getReturnValue();
Exception exceptionValue = msg.getExceptionValue();
// 填充结果
if(exceptionValue != null) {
promise.setFailure(exceptionValue);
}else {
promise.setSuccess(returnValue);
}
}
log.debug("{}", msg);
}
}
用异步的网络调用来存放结果。