对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
java序列化是java编解码技术中的一种,不同的编解码的优缺点,参考之前的博客。https://blog.csdn.net/weixin_41262453/article/details/88980701
ByteArrayOutputStream类实现了将数据写入字节数组的输出流。 当数据写入缓冲区时,缓冲区会自动增长。ByteArrayOutputStream.write(int b) 将指定的字节写入此字节数组输出流。 此方式需要自己手写大小端转字节序列的函数,麻烦。
public class Test1 {
public static void main(String[] args) throws IOException {
int id = 101;
int age = 21;
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
arrayOutputStream.write(int2bytes(id));
arrayOutputStream.write(int2bytes(age));
byte[] byteArray = arrayOutputStream.toByteArray();
System.out.println(Arrays.toString(byteArray));
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteArray);
byte[] idBytes = new byte[4];
arrayInputStream.read(idBytes);
System.out.println("id:" + bytes2int(idBytes));
byte[] ageBytes = new byte[4];
arrayInputStream.read(ageBytes);
System.out.println("age:" + bytes2int(ageBytes));
}
/**
* 大端字节序列(先写高位,再写低位)
* 百度下 大小端字节序列
* @param i
* @return
*/
public static byte[] int2bytes(int i){
byte[] bytes = new byte[4];
bytes[0] = (byte)(i >> 3*8);
bytes[1] = (byte)(i >> 2*8);
bytes[2] = (byte)(i >> 1*8);
bytes[3] = (byte)(i >> 0*8);
return bytes;
}
/**
* 大端
* @param bytes
* @return
*/
public static int bytes2int(byte[] bytes){
return (bytes[0] << 3*8) |
(bytes[1] << 2*8) |
(bytes[2] << 1*8) |
(bytes[3] << 0*8);
}
}
使用NIO中的字节缓冲区类Bytebuf,提供了许多序列化方法。使用方便,但是在使用时要定义大小,不能自动扩容。
public class Test2 {
public static void main(String[] args) {
int id = 101;
int age = 21;
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putInt(id);
buffer.putInt(age);
byte[] array = buffer.array();
System.out.println(Arrays.toString(buffer.array()));
//反序列化
ByteBuffer buffer2 = ByteBuffer.wrap(array);
System.out.println("id:"+buffer2.getInt());
System.out.println("age:"+buffer2.getInt());
}
}
使用时需要导入Netty的jar包。
public class Test3 {
public static void main(String[] args) {
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
buffer.writeInt(101);
buffer.writeDouble(80.1);
byte[] bytes = new byte[buffer.writerIndex()];
buffer.readBytes(bytes);
System.out.println(Arrays.toString(bytes));
"abc".getBytes();
//=====================反序列化===========================
ChannelBuffer wrappedBuffer = ChannelBuffers.wrappedBuffer(bytes);
System.out.println(wrappedBuffer.readInt());
System.out.println(wrappedBuffer.readDouble());
}
}
Serializer底层的实现就是两个ChannelBuffer, writeBuffer和readBuffer。
把对象转换为字节序列的过程称为对象的序列化;把字节序列恢复为对象的过程称为对象的反序列化。需要序列化的两个类:Player 和Resource ,继承Serializer抽象类要做的事情就是重写write和read方法。
public class Player extends Serializer{
private long playerId;
private int age;
private List<Integer> skills = new ArrayList<>();
private Resource resource = new Resource();
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public long getPlayerId() {
return playerId;
}
public void setPlayerId(long playerId) {
this.playerId = playerId;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Integer> getSkills() {
return skills;
}
public void setSkills(List<Integer> skills) {
this.skills = skills;
}
@Override
protected void read() {
this.playerId = readLong();
this.age = readInt();
this.skills = readList(Integer.class);
this.resource = read(Resource.class);
}
@Override
protected void write() {
writeLong(playerId);
writeInt(age);
writeList(skills);
writeObject(resource);
}
}
public class Resource extends Serializer {
private int gold;
public int getGold() {
return gold;
}
public void setGold(int gold) {
this.gold = gold;
}
@Override
protected void read() {
this.gold = readInt();
}
@Override
protected void write() {
writeInt(gold);
}
}
测试序列化对象:
public class Test4 {
public static void main(String[] args) {
Player player = new Player();
player.setPlayerId(10001);
player.setAge(22);
player.getSkills().add(101);
player.getResource().setGold(99999);
byte[] bytes = player.getBytes();
System.out.println(Arrays.toString(bytes));
//==============================================
Player player2 = new Player();
player2.readFromBytes(bytes);
System.out.println(player2.getPlayerId() + " "+player2.getAge() + " "+ Arrays.toString(player2.getSkills().toArray())+" " +player2.getResource().getGold());
}
}
Protobuf系列化操作参考https://blog.csdn.net/weixin_41262453/article/details/88980701#Google_Protobuf_451 ,将上面的player序列化,进行测试。
player.proto配置文件参考
序列化后得到的PlayerModule有1000多行就不放了,测试代码如下:
import java.util.Arrays;
import com.proto.PlayerModule.PBPlayer;
import com.proto.PlayerModule.PBPlayer.Builder;
public class PB2Bytes {
public static void main(String[] args) throws Exception {
byte[] bytes = toBytes();
toPlayer(bytes);
}
/**
* 序列化
*/
public static byte[] toBytes(){
//获取一个PBPlayer的构造器
Builder builder = PlayerModule.PBPlayer.newBuilder();
//设置数据
builder.setPlayerId(101).setAge(20).setName("peter").addSkills(1001);
//构造出对象
PBPlayer player = builder.build();
//序列化成字节数组
byte[] byteArray = player.toByteArray();
System.out.println(Arrays.toString(byteArray));
return byteArray;
}
/**
* 反序列化
* @param bs
* @throws Exception
*/
public static void toPlayer(byte[] bs) throws Exception{
PBPlayer player = PlayerModule.PBPlayer.parseFrom(bs);
System.out.println("playerId:" + player.getPlayerId());
System.out.println("age:" + player.getAge());
System.out.println("name:" + player.getName());
System.out.println("skills:" + (Arrays.toString(player.getSkillsList().toArray())));
}
}
此处提供了一个自定义的编解码器用于传输我们已经序列化的对象,可用于了解常见通信框架的编解码器底层序列化工作原理。
首先设计数据包的格式:
* 数据包格式
* +——----——+——-----——+——----——+——----——+——-----——+
* | 包头 | 模块号 | 命令号 | 长度 | 数据 |
* +——----——+——-----——+——----——+——----——+——-----——+
*
* 包头4字节
* 模块号2字节short
* 命令号2字节short
* 长度4字节(描述数据部分字节长度)
因此我们要定义一个包头,首先采用一个不常用的4字节数据作为包头:
public interface ConstantValue {
/**
* 包头
*/
public static final int FLAG = -32523523;
}
其次定义请求,请求中包括请求模块、命令号、数据
public class Request {
/**
* 请求模块
*/
private short module;
/**
* 命令号
*/
private short cmd;
/**
* 数据部分
*/
private byte[] data;
public short getModule() {
return module;
}
public void setModule(short module) {
this.module = module;
}
public short getCmd() {
return cmd;
}
public void setCmd(short cmd) {
this.cmd = cmd;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public int getDataLength(){
if(data == null){
return 0;
}
return data.length;
}
}
响应的有响应:Response
public class Response {
/**
* 请求模块
*/
private short module;
/**
* 命令号
*/
private short cmd;
/**
* 状态码
*/
private int stateCode;
/**
* 数据部分
*/
private byte[] data;
public short getModule() {
return module;
}
public void setModule(short module) {
this.module = module;
}
public short getCmd() {
return cmd;
}
public void setCmd(short cmd) {
this.cmd = cmd;
}
public int getStateCode() {
return stateCode;
}
public void setStateCode(int stateCode) {
this.stateCode = stateCode;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public int getDataLength(){
if(data == null){
return 0;
}
return data.length;
}
}
返回的状态字:
public interface StateCode {
/**
* 成功
*/
public static int SUCCESS = 0;
/**
* 失败
*/
public static int FAIL = 1;
}
RequestEncoder首先需要继承Netty3.10.5中OneToOneEncoder,RequestEncoder将请求Request对象实现序列化到ChannelBuffer ,返回ChannelBuffer 缓存区buffer。
/**
* 请求编码器
*
* 数据包格式
* +——----——+——-----——+——----——+——----——+——-----——+
* | 包头 | 模块号 | 命令号 | 长度 | 数据 |
* +——----——+——-----——+——----——+——----——+——-----——+
*
* 包头4字节
* 模块号2字节short
* 命令号2字节short
* 长度4字节(描述数据部分字节长度)
*/
public class RequestEncoder extends OneToOneEncoder{
@Override
protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception {
Request request = (Request)(rs);
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
//包头
buffer.writeInt(ConstantValue.FLAG);
//module
buffer.writeShort(request.getModule());
//cmd
buffer.writeShort(request.getCmd());
//长度
buffer.writeInt(request.getDataLength());
//data
if(request.getData() != null){
buffer.writeBytes(request.getData());
}
return buffer;
}
}
RequestDecoder同样要继承Netty3.10.5中FrameDecoder,Request解码器要做的事情就是从接收到的字节流缓存区读取出数据,进行判断,若包头正确,可读长度大于基本长度,则将字节流依次读取出Request类的各个成员变量,返回Request对象。
/**
* 请求解码器
*
* 数据包格式
* +——----——+——-----——+——----——+——----——+——-----——+
* | 包头 | 模块号 | 命令号 | 长度 | 数据 |
* +——----——+——-----——+——----——+——----——+——-----——+
*
* 包头4字节
* 模块号2字节short
* 命令号2字节short
* 长度4字节(描述数据部分字节长度)
*
*
*/
public class RequestDecoder extends FrameDecoder{
/**
* 数据包基本长度
*/
public static int BASE_LENTH = 4 + 2 + 2 + 4;
@Override
protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
//可读长度必须大于基本长度
if(buffer.readableBytes() >= BASE_LENTH){
//防止socket字节流攻击
if(buffer.readableBytes() > 2048){
buffer.skipBytes(buffer.readableBytes());
}
//记录包头开始的index
int beginReader;
while(true){
beginReader = buffer.readerIndex();
buffer.markReaderIndex();
if(buffer.readInt() == ConstantValue.FLAG){
break;
}
//未读到包头,略过一个字节
buffer.resetReaderIndex();
buffer.readByte();
//长度又变得不满足
if(buffer.readableBytes() < BASE_LENTH){
return null;
}
}
//模块号
short module = buffer.readShort();
//命令号
short cmd = buffer.readShort();
//长度
int length = buffer.readInt();
//判断请求数据包数据是否到齐
if(buffer.readableBytes() < length){
//还原读指针
buffer.readerIndex(beginReader);
return null;
}
//读取data数据
byte[] data = new byte[length];
buffer.readBytes(data);
Request request = new Request();
request.setModule(module);
request.setCmd(cmd);
request.setData(data);
//继续往下传递
return request;
}
//数据包不完整,需要等待后面的包来
return null;
}
}
ResponseEncoder与RequestEncoder实现原理类似。
/**
* 请求编码器
*
* 数据包格式
* +——----——+——-----——+——----——+——----——+——-----——+——-----——+
* | 包头 | 模块号 | 命令号 | 状态码 | 长度 | 数据 |
* +——----——+——-----——+——----——+——----——+——-----——+——-----——+
*
* 包头4字节
* 模块号2字节short
* 命令号2字节short
* 长度4字节(描述数据部分字节长度)
*/
public class ResponseEncoder extends OneToOneEncoder{
@Override
protected Object encode(ChannelHandlerContext context, Channel channel, Object rs) throws Exception {
Response response = (Response)(rs);
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
//包头
buffer.writeInt(ConstantValue.FLAG);
//module
buffer.writeShort(response.getModule());
//cmd
buffer.writeShort(response.getCmd());
//状态码
buffer.writeInt(response.getStateCode());
//长度
buffer.writeInt(response.getDataLength());
//data
if(response.getData() != null){
buffer.writeBytes(response.getData());
}
return buffer;
}
}
ResponseDecoder相比RequestDecoder,还会多解析一个状态码。
/**
* response解码器
*
* 数据包格式
* +——----——+——-----——+——----——+——----——+——-----——+——-----——+
* | 包头 | 模块号 | 命令号 | 状态码 | 长度 | 数据 |
* +——----——+——-----——+——----——+——----——+——-----——+——-----——+
*
* 包头4字节
* 模块号2字节short
* 命令号2字节short
* 长度4字节(描述数据部分字节长度)
*/
public class ResponseDecoder extends FrameDecoder{
/**
* 数据包基本长度
*/
public static int BASE_LENTH = 4 + 2 + 2 + 4;
@Override
protected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {
//可读长度必须大于基本长度
if(buffer.readableBytes() >= BASE_LENTH){
//记录包头开始的index
int beginReader = buffer.readerIndex();
while(true){
if(buffer.readInt() == ConstantValue.FLAG){
break;
}
}
//模块号
short module = buffer.readShort();
//命令号
short cmd = buffer.readShort();
//状态码
int stateCode = buffer.readInt();
//长度
int length = buffer.readInt();
if(buffer.readableBytes() < length){
//还原读指针
buffer.readerIndex(beginReader);
return null;
}
byte[] data = new byte[length];
buffer.readBytes(data);
Response response = new Response();
response.setModule(module);
response.setCmd(cmd);
response.setStateCode(stateCode);
response.setData(data);
//继续往下传递
return response;
}
//数据包不完整,需要等待后面的包来
return null;
}
}
发送的对象模型:FightRequest
public class FightRequest extends Serializer{
/**
* 副本id
*/
private int fubenId;
/**
* 次数
*/
private int count;
public int getFubenId() {
return fubenId;
}
public void setFubenId(int fubenId) {
this.fubenId = fubenId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
protected void read() {
this.fubenId = readInt();
this.count = readInt();
}
@Override
protected void write() {
writeInt(fubenId);
writeInt(count);
}
}
返回的对象模型:
public class FightResponse extends Serializer{
/**
* 获取金币
*/
private int gold;
public int getGold() {
return gold;
}
public void setGold(int gold) {
this.gold = gold;
}
@Override
protected void read() {
this.gold = readInt();
}
@Override
protected void write() {
writeInt(gold);
}
}
测试代码:服务端Client和HiHandler ,由于Request和Response的Encoder和Decoder都是继承了Netty中的类进行设计的,所以可以直接pipeline.addLast,因此兼容原有的Netty框架。并且在channel.write(request)时也是直接write request对象,request对象中的data就是需要发送的fightRequest模型的序列化字节码。
public class Client {
public static void main(String[] args) throws InterruptedException {
//服务类
ClientBootstrap bootstrap = new ClientBootstrap();
//线程池
ExecutorService boss = Executors.newCachedThreadPool();
ExecutorService worker = Executors.newCachedThreadPool();
//socket工厂
bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));
//管道工厂
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new ResponseDecoder());
pipeline.addLast("encoder", new RequestEncoder());
pipeline.addLast("hiHandler", new HiHandler());
return pipeline;
}
});
//连接服务端
ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 30000));
Channel channel = connect.sync().getChannel();
System.out.println("client start");
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入");
int fubenId = Integer.parseInt(scanner.nextLine());
int count = Integer.parseInt(scanner.nextLine());
FightRequest fightRequest = new FightRequest();
fightRequest.setFubenId(fubenId);
fightRequest.setCount(count);
Request request = new Request();
request.setModule((short) 1);
request.setCmd((short) 1);
request.setData(fightRequest.getBytes());
//发送请求
channel.write(request);
}
}
}
public class HiHandler extends SimpleChannelHandler {
/**
* 接收消息
*/
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Response message = (Response)e.getMessage();
if(message.getModule() == 1){
if(message.getCmd() == 1){
FightResponse fightResponse = new FightResponse();
fightResponse.readFromBytes(message.getData());
System.out.println("gold:" + fightResponse.getGold());
}else if(message.getCmd() == 2){
}
}else if (message.getModule() == 1){
}
}
/**
* 捕获异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
System.out.println("exceptionCaught");
super.exceptionCaught(ctx, e);
}
/**
* 新连接
*/
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelConnected");
super.channelConnected(ctx, e);
}
/**
* 必须是链接已经建立,关闭通道的时候才会触发
*/
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelDisconnected");
super.channelDisconnected(ctx, e);
}
/**
* channel关闭的时候触发
*/
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelClosed");
super.channelClosed(ctx, e);
}
}
客户端Server 和HelloHandler:messageReceived中接收的消息此时就是Request对象,从Request对象的data取出的数据是序列化的fightRequest 字节码,要再经过该类中的readFromBytes方法转换,才能完全转成发送过来的fightRequest 。
public class Server {
public static void main(String[] args) {
//服务类
ServerBootstrap bootstrap = new ServerBootstrap();
//boss线程监听端口,worker线程负责数据读写
ExecutorService boss = Executors.newCachedThreadPool();
ExecutorService worker = Executors.newCachedThreadPool();
//设置niosocket工厂
bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
//设置管道的工厂
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new RequestDecoder());
pipeline.addLast("encoder", new ResponseEncoder());
pipeline.addLast("helloHandler", new HelloHandler());
return pipeline;
}
});
bootstrap.bind(new InetSocketAddress(30000));
System.out.println("start!!!");
}
}
public class HelloHandler extends SimpleChannelHandler {
/**
* 接收消息
*/
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Request message = (Request)e.getMessage();
if(message.getModule() == 1){
if(message.getCmd() == 1){
FightRequest fightRequest = new FightRequest();
fightRequest.readFromBytes(message.getData());
System.out.println("fubenId:" +fightRequest.getFubenId() + " " + "count:" + fightRequest.getCount());
//回写数据
FightResponse fightResponse = new FightResponse();
fightResponse.setGold(9999);
Response response = new Response();
response.setModule((short) 1);
response.setCmd((short) 1);
response.setStateCode(StateCode.SUCCESS);
response.setData(fightResponse.getBytes());
ctx.getChannel().write(response);
}else if(message.getCmd() == 2){
}
}else if (message.getModule() == 1){
}
}
/**
* 捕获异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
System.out.println("exceptionCaught");
super.exceptionCaught(ctx, e);
}
/**
* 新连接
*/
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelConnected");
super.channelConnected(ctx, e);
}
/**
* 必须是链接已经建立,关闭通道的时候才会触发
*/
@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelDisconnected");
super.channelDisconnected(ctx, e);
}
/**
* channel关闭的时候触发
*/
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("channelClosed");
super.channelClosed(ctx, e);
}
}