1、编解码技术
基于java提供的对象输入\输出流ObjectInputStream和ObjectOutputStream,可以直接把java对象作为存储的字节数组写入文件,也可以传输到网络上。基于Jdk的默认序列化机制可以避免操作底层数组,从而提升开发效率。
java序列化的目的主要有两个:
网络传输
对象持久化。
基于netty的nio网络开发,主要是java对象的网络传输。在远程调用的时候需要将java对象编码为字节数组或是ByteBuffer对象进行传输,当远程服务读取到ByteBuffer对象或者字节数组的时候,需要将其解码为发送时的java对象。
虽然Java序列化提供了实现,但有以下缺点:
1、无法跨语言
2、序列化后码流太大
目前业界主流的编解码框架如下:
Google 的Protocol Buffers,它的数据结构以.proto文件进行描述,通过代码生成工具生成对应数据结构的POJO对象和Protobuf相关的方法和属性。
它的特点如下:
结构化数据存储格式(xml,json等)
高效的编码解码性能
语言无关、平台无关、扩展性好。
protobuf使用二进制编码,在空间和性能上相对于jdk的序列化有很大的性能优势。
另外还有Facebook的Thrift、JBoss Marshalling。
2、google Protocol Buffers使用
下载protobuf-2.5.0.tar.gz ,然后进行解压安装,依据
README.txt进行安装
配置好环境变量后,可以执行如下命令输入protoc - - version检查是否安装成功。
其具体的安装可以参考
msg.proto文件内容如下:
option java_package = "zou.domain";
option java_outer_classname = "PersonProbuf";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
message CountryInfo {
required string name = 1;
required string code = 2;
optional int32 number = 3;
}
}
message AddressBook {
repeated Person person = 1;
}
上面的描述文件指定了类的路径,依据类的名称PersonProbuf,这里定义了Person类和AddressBook。
然后执行如下命令:
protoc --java_out=./ ./msg.proto 前面是指定输出的路劲,后面是指定要生成代码的文件。此时会在本路劲下生成zou/domain下的类PersonProbuf。其测试如下:
public class PersonProbufTest {
public static void main(String[] args) throws Exception {
PersonProbuf.Person p = createPerson();
System.out.println("before encode " + p.toString());
PersonProbuf.Person p2 = decode(encode(p));
System.out.println("after encode " + p2.toString());
System.out.println("equal ==" + p2.toString().equals(p.toString()));
}
private static byte[] encode(PersonProbuf.Person p) {
return p.toByteArray();
}
private static PersonProbuf.Person decode(byte[] body) throws InvalidProtocolBufferException {
return PersonProbuf.Person.parseFrom(body);
}
private static PersonProbuf.Person createPerson() {
PersonProbuf.Person.Builder builder = PersonProbuf.Person.newBuilder();
builder.setName("zouxh").setEmail("zouxiaohu87@126.com").setId(10);
builder.addPhone(PersonProbuf.Person.PhoneNumber.newBuilder().setNumber("2").setType(PersonProbuf.Person.PhoneType.HOME).build());
return builder.build();
}
}
里面已经包含了对象转换为字节数字,以及字节数组转换为对象,另外还有对应的构造方法去设置对象。
下面给出通过pb进行Nio的通信的例子:
option java_package = "zou.domain";
option java_outer_classname = "SubscribeReqProto";
message SubscribeReq {
required string userName = 1;
required int32 subReqID = 2;
required string productName = 3;
repeated string address = 4;
}
option java_package = "zou.domain";
option java_outer_classname = "SubscribeRespProto";
message SubscribeResp {
required int32 subReqID= 1;
required int32 respCode=3;
required string desc=4;
}
通过pb生成对应的java代码。
Server端代码如下:
public class SubReqServer {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
}
}
new SubReqServer().bind(port);
}
public void bind(int port) throws Exception {
//配置服务端的nio线程组,
//EventLoopGroup是一个线程组,它包含了一组nio线程,专门用于网络事件的处理,实际上他们就是Reactor线程组
//这里创建2个的原因是一个用于服务端接受客户的连接,另一个用于SockentChannel的网络读写。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭;
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//arg0.pipeline().addLast(new TimeServerHandler());
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new SubReqServerHandle());
}
}
}
看到这里添加了对应的解码器,ProtobufVarint32FrameDecoder主要是进行读半包的问题,ProtobufDecoder是解码器,其参数指定了是对哪种类型进行解码。
ServerHandler如下:
public class SubReqServerHandle extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
if ("zouxiaohu".equalsIgnoreCase(req.getUserName())) {
System.out.println("Service accept client subscribe req : [ " + req.toString() + "]");
//马上进行发送
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
private SubscribeRespProto.SubscribeResp resp(int subReqId) {
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqId);
builder.setRespCode(0);
builder.setDesc("Netty book order succedd , 3 days later,sent to the designated address");
return builder.build();
}
}
Client代码如下:
public class SubReqClient {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
}
}
new SubReqClient().connet(port, "127.0.0.1");
}
public void connet(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
//客户端禁用nangle算法
b.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 ProtobufVarint32FrameDecoder());
ch.pipeline().addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(new SubReqClientHandler());
}
});
//发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
//等待客户端关闭
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
ClientHandler代码如下:
public class SubReqClientHandler extends ChannelHandlerAdapter {
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ctx.write(subReq(i));
}
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Receive server response : [ " + msg + " ]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
private SubscribeReqProto.SubscribeReq subReq(int i) {
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(i);
builder.setUserName("zouxiaohu");
builder.setProductName("Netty Book For protobuf");
List<String> address = new ArrayList<String>();
address.add("NanJing YuHuaTai");
address.add("BeiJing LiuLiChang");
builder.addAllAddress(address);
return builder.build();
}
}