编解码
为什么需要编解码
Java是基于IO流的,不管是文件还是网络数据,Java都把数据的输入输出抽象成了基本的IO流。这样的好处就是易于理解易于操作。然后IO流是只能读写字节的,但是经常我们的数据是储存在内存中的一个对象或者说是一个数据结构实例。因此就需要把这些对象转换成字节后然后再进行数据传输,比如说一个学生类对象,如果转换成字节数组,然后写到一个文件或者输出到网络中。
常见的编解码方案
Java中最先学到的就是JDK自带的序列化工具Serializable接口,虽然这个接口没有任何方法,但通过实现这个接口就标志着这个类可以被JVM序列化成一个字节序列。
下面的代码是将一个学生类序列化后写到一个文件中,文件的格式不重要,反正文件都是存的二进制数据。
package study.netty;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestJavaSerialable {
public static class Student implements Serializable {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
public static void main(String[] args) {
Student s = new Student();
s.setName("xiaobing");
s.setAge(22);
s.setSex("男");
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.obj"));
oos.writeObject(s);
oos.flush();
oos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行这段代码后会在工程目录生成一个student.obj文件,打开这个文件会发现一些完全不认识的字符,这些就是student对象序列成的字节码数据。然后,我们可以把数据从文件中读出来,并且读成一个Student的对象。
package study.netty;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestJavaSerialable {
public static class Student implements Serializable {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
public static void main(String[] args) {
// Student s = new Student();
// s.setName("xiaobing");
// s.setAge(22);
// s.setSex("男");
try {
// ObjectOutputStream oos = new ObjectOutputStream(new
// FileOutputStream("student.obj"));
// oos.writeObject(s);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.obj"));
Student s = (Student) ois.readObject();
ois.close();
System.out.println("s.name=" + s.getName());
System.out.println("s.age=" + s.getAge());
System.out.println("s.sex=" + s.getSex());
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行这段代码可以把刚才写到student.obj文件中的对象数据读出来并封装到一个Student对象中,这样,就实现了Java对象的序列化和反序列化。看起来还是很方便的。但是JDK默认的序列化也有很多缺点,最大的缺点就是不能跨语言,也就是说Java序列化的数据,不能被其他语言所理解,JDK序列化和反序列化只能在Java语言中通用。再者,JDK的默认序列化的码流太大,也就是序列化后产生的数据太多,这样会非常浪费内存资源,也会严重影响网络传输效率,网络传输是个很耗资源的操作,网络传输的数据应该越少越好。如果Student类没有实现Seriazable接口的话,通过Java默认序列化操作将会抛出异常。
当然,还有一些其他的序列化的方案,比如说支持Java、C++和Python的Google Protobuf,是将对象解析成XML或者JSON数据,还有Facebook的Thrift和JBoss Marshalling框架,支持大部分主流编程语言。
通常在网络数据传输中,使用XML和JSON两种数据格式最多,因为这两种数据格式是基于文本的,很容易理解,而且数据量比较小。JSON比XML会更优秀一些,因为JSON更轻量级而且更轻结构,比如说我以前在一个项目中,使用了XML,有一个XML节点的数据是一个XML标签,但这个标签是不规范的,我在用XML进行解析的时候,解析器就把这个数据当成了一个XML标签来解析,由于是不规范的,就解析异常了,当时很头大。但是如果使用JSON的话就不会出现这种情况。也许很多年前XML还是网络数据的标准格式,但现在,使用JSON的越来越多。
Netty对JDK序列化的支持
下面的代码是对Echo例子的改进,可以在服务器端和客户端器之间传输一个Student对象。
package study.netty;
import java.util.Date;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
public class NettyEchoServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
ChannelFuture future = bootstrap.bind(8888);
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class EchoChannelHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf buffer = (ByteBuf) msg;
// byte[] bts = new byte[buffer.readableBytes()];
// buffer.readBytes(bts);
// String str = new String(bts, "utf-8");
// System.out.println(new Date().getTime() + ":" + str);
Student student = (Student) msg;
System.out.println(student);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(1024,ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new EchoChannelHandler());
}
}
}
package study.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
public class NettyEchoClient {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(1024,ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888);
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
private static class EchoClientHandler extends ChannelHandlerAdapter {
private ByteBuf msgBuf;
private byte[] msgBts;
public EchoClientHandler() {
msgBts = "please connect".getBytes();
}
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// for (int i = 0; i < 100; i++) {
// msgBuf = Unpooled.buffer(msgBts.length);
// msgBuf.writeBytes(msgBts);
// ctx.writeAndFlush(msgBuf);
// }
Student student = new Student();
student.setName("xiaobing");
student.setAge(22);
student.setSex("男");
ctx.writeAndFlush(student);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
}
}
}
package study.netty;
import java.io.Serializable;
public class Student implements Serializable{
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
运行上面 的代码,执行结果是:
Student [name=xiaobing, age=22, sex=男]
客户端创建了一个Student实例,通过Netty网络传输给了服务器端,服务器端接收到打印到控制台。之所以这么方便,是因为Netty为我们实现好了JDK默认序列化与反序列化的编码和解码器,这样,我们在应用的时候,只需要把JDK默认的序列化方式的编码和解码器添加到数据处理队列中就可以了,Netty框架会帮我们自己处理。其他几种序列化的方案也有相应的Netty实现,只需要做相应的替换即可,这也是Netty另一个优点,可以方便地进行组件的替换与组合,更加方便开发。当然,我们也可以自己实现自己的序列化方式,其实就是自己实现一个编码和解码器,继承ChannelHandlerAdapter即可。