基于Java提供的对象输入/输出流ObjectInputStream和ObjectOutputStream,可以直接把Java对象作为可存储的字节数组写入文件,也可以传输到网络上。对程序员来说,基于JDK默认的序列号机制可以避免操作底层的字节数组,从而提高开发效率。
java.io.ObjectOutput序列化和反序列化。
1、服务端开发
(1)在服务端ChannelPipeline中添加ObjectDecoder解码器。
(2)在服务端ChannelPipeline中添加ObjectEncoder编码器。
(3)需要序列化的POJO类实现Serializable接口。
(4)构造服务端处理类,处理客户端请求。
请求POJO类
package com.serial.java.pojo;
import java.io.Serializable;
/**
*
* @author leeka
* @notice
* 1、实现Serializable接口
* 2、生成默认的序列化ID
* 3、重写toString()方法,方便输出
*/
public class SubscribeReq implements Serializable {
private static final long serialVersionUID = 1L;
private int subReqID;
private String userName;
private String productName;
private String phoneNumber;
private String address;
public int getSubReqID() {
return subReqID;
}
public void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "SubscribeReq [subReqID=" + subReqID + ", userName=" + userName + ", productName=" + productName
+ ", phoneNumber=" + phoneNumber + ", address=" + address + "]";
}
}
响应POJO类
package com.serial.java.pojo;
import java.io.Serializable;
/**
*
* @author leeka
* @notice
* 1、实现Serializable接口
* 2、生成默认的序列化ID
* 3、重写toString()方法,方便输出
*/
public class SubscribeResp implements Serializable{
private static final long serialVersionUID = 1L;
private int subReqID;
private int respCode;
private String desc;
public int getSubReqID() {
return subReqID;
}
public void setSubReqID(int subReqID) {
this.subReqID = subReqID;
}
public int getRespCode() {
return respCode;
}
public void setRespCode(int respCode) {
this.respCode = respCode;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "SubscribeResp [subReqID=" + subReqID + ", respCode=" + respCode + ", desc=" + desc + "]";
}
}
服务端入口
package com.serial.java;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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 SubReqServer {
public void bind(int port)throws Exception{
//配置服务端NIO线程组
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 ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new ObjectDecoder(1024*1024//为防止异常码流和解码错位导致的内存溢出,将对象序列化后的最大字节数组长度设为1M
, ClassResolvers.weakCachingConcurrentResolver(//创建线程安全的WeakReferenceMap对类加载器进行缓存
this.getClass().getClassLoader())))
.addLast(new ObjectEncoder())//对实现了Serializable的POJO对象进行编码
.addLast(new SubReqServerHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
}finally{
//退出时释放资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception{
int port = 8084;
if(args!=null && args.length > 0){
port = Integer.valueOf(args[0]);
}
new SubReqServer().bind(port);
}
}
自定义服务端处理类
package com.serial.java;
import com.serial.java.pojo.SubscribeReq;
import com.serial.java.pojo.SubscribeResp;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
SubscribeReq req = (SubscribeReq) msg;
if("leeka".equalsIgnoreCase(req.getUserName())){
System.out.println("service accept client subscribe req:["+ req +"]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
private SubscribeResp resp(int subReqID){
SubscribeResp resp = new SubscribeResp();
resp.setSubReqID(subReqID);
resp.setRespCode(0);
resp.setDesc("Netty book order succeed, 3 days later, sent to the designated address");
return resp;
}
// @Override
// public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// ctx.flush();
// }
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
//super.exceptionCaught(ctx, cause);
ctx.close();
}
}
2、客户端开发
(1)将Netty对象编码器和解码器添加到ChannelPipeline。
(2)客户端一次构造十个请求,最后一次性发送给服务端。
(3)自定义客户端处理类打印服务端响应的消息。
客户端入口
package com.serial.java;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
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 SubReqClient {
public void connect(int port,String host)throws Exception{
//配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
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 ObjectDecoder(1024
, ClassResolvers.cacheDisabled(//禁止对类加载器进行缓存
this.getClass().getClassLoader())))
.addLast(new ObjectEncoder())//对实现了Serializable的POJO对象进行编码
.addLast(new SubReqClientHandler());
};
});
//发起异步连接操作
ChannelFuture f = b.connect(host,port).sync();
//等待客户端链路关闭
f.channel().closeFuture().sync();
}finally{
//退出,释放资源
group.shutdownGracefully();
}
}
public static void main(String[] args)throws Exception {
int port = 8084;
if(args!=null && args.length > 0){
port = Integer.valueOf(args[0]);
}
new SubReqClient().connect(port, "127.0.0.1");
}
}
客户端处理类
package com.serial.java;
import java.util.logging.Logger;
import com.serial.java.pojo.SubscribeReq;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class SubReqClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger.getLogger(SubReqClientHandler.class.getName());
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ctx.write(req(i));
}
ctx.flush();
}
private SubscribeReq req(int i){
SubscribeReq r = new SubscribeReq();
r.setSubReqID(i);
r.setAddress("浙江省杭州市西湖区");
r.setPhoneNumber("1516844444"+i);
r.setProductName("Netty权威指南系列"+i);
r.setUserName("leeka");
return r;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("receive server response:["+msg+"]");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
logger.warning("unexpected exception from downstream:"+ cause.getMessage());
ctx.close();
}
}
Java序列化虽然很方便,但也有它的缺点: (1)无法跨语言;(2)序列化后码流大;(3)序列化性能低。目前业界主流的编解码框架有Google的Protobuf、Facebook的Thrift、Jboss的Marshalling等,大家不妨结合这些主流的编解码框架进行编解码操作。
OVER