十、編解碼技術--Java序列化
Java自身的序列化機制,就是隻需要序列化的POJO物件實現Java.io.Serializable介面,根據實際情況生成序列ID,這個類就能夠通過java.io.ObjectInput和java.io.ObjectOutput序列化和反序列化。
服務端:Netty服務端接收到客戶端的使用者訂購請求訊息
訂購訊息
package NettySerialization;
import java.io.Serializable;
/**
* Created by L_kanglin on 2017/6/23.
* 訂購請求POJO類定義
*/
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 + '\'' +
'}';
}
}
服務端接收到請求訊息,對使用者名稱進行合法性校驗。如果合法,則構造訂購成功的應答訊息返回給客戶端。訂購訊息如下:
package NettySerialization;
import java.io.Serializable;
/**
* Created by L_kanglin on 2017/6/23.
*/
public class SubscribeResp implements Serializable{
private static final long serialVersionUID = 1L;
//訂購編號
private int subReqID;
//訂購結果:0 表示成功
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 NettySerialization;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* Created by L_kanglin on 2017/6/13.
*/
public class SubReqServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(1024 * 1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new SubReqServerHandler());
}
});
//繫結埠,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服務端監聽埠關閉
f.channel().closeFuture().sync();
} finally {
//優雅退出,釋放執行緒池資源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port=8080;
if(args !=null&& args.length>0){
port=Integer.valueOf(args[0]);
}
new SubReqServer().bind(port);
}
}
ObjectDecoder,負責對實現Serializable的POJO物件進行解碼;
使用weakCachingConcurrentResolver建立執行緒安全的WeakReferenceMap對類載入器進行快取,它支援多執行緒併發訪問,當虛擬機器記憶體不足時,會釋放快取中的記憶體,防止記憶體洩漏。為了防止異常碼流和解碼錯位導致的記憶體溢位,這裡將單個物件最大序列化後的位元組陣列長度設定為1M。
ObjectEncoder可以在訊息傳送時自動將實現Serializable的POJO物件進行編碼,因此使用者無須親自對物件進行手工序列化,只需要關注自己的業務邏輯處理即可。物件序列化和反序列化都由Netty的物件編碼器解決。
將訂購處理handler SubReqServerHandler新增到ChannelPipeline的尾部用於業務邏輯處理。
package NettySerialization;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* Created by L_kanglin on 2017/6/23.
*/
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeReq req= (SubscribeReq) msg;
if("LiKanglin".equalsIgnoreCase(req.getUserName())){
System.out.println("Service accept client subscribe req : [" + req.toString() + "]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
private SubscribeResp resp(int subReqID){
SubscribeResp resp=new SubscribeResp();
resp.setSubReqID(subReqID);
resp.setRespCode(0);
resp.setDesc("Netty is lovaly");
return resp;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close(); //關閉異常,關閉鏈路
}
}
客戶端設計思路:
(1)建立客戶端時,將Netty物件解碼器和編碼器新增到ChannelPipeline;
(2)鏈路被啟用的時候構造訂購請求訊息傳送,為了檢驗Netty的Java序列化功能是否支援TCP粘包和拆包,客戶端一次構造10條訂購請求,最後一次性發給服務端
(3)客戶端訂購處理handler將接收到的訂購響應訊息打印出來。
具體程式碼實現如下:
package NettySerializationClient;
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;
/**
* Created by L_kanglin on 2017/6/23.
*/
public class SubReqClient {
public void connnect(int port,String host) throws InterruptedException {
//配置客戶端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())));
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new SubReqClientHandler());
}
});
//發起非同步連線操作
ChannelFuture f =b.connect(host,port).sync();
//等待客戶端鏈路關閉
f.channel().closeFuture().sync();
}finally {
//優雅退出,釋放NIO執行緒組
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port=8080;
if(args!=null && args.length>0){
port=Integer.valueOf(args[0]);
}
new SubReqClient().connnect(port,"127.0.0.1");
}
}
package NettySerializationClient;
import NettySerialization.SubscribeReq;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* Created by L_kanglin on 2017/6/23.
*/
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();
}
private SubscribeReq subReq(int i){
SubscribeReq req = new SubscribeReq();
req.setAddress("武漢市華中科技大學");
req.setPhoneNumber("155XXXXXXXXXXXXXX");
req.setProductName("Netty In Action");
req.setSubReqID(i);
req.setUserName("LiKanglin");
return req;
}
@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();
}
}
ClassResolvers.cacheDisabled(this.getClass().getClassLoader()))禁止對類載入器進行快取。
服務端執行如下:
Service accept client subscribe req : [SubscribeReq{subReqID=0, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=1, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=2, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=3, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=4, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=5, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=6, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=7, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=8, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
Service accept client subscribe req : [SubscribeReq{subReqID=9, userName='LiKanglin', productName='Netty In Action', phoneNumber='155XXXXXXXXXXXXXX', address='武漢市華中科技大學'}]
客戶端執行如下:
Receive server response : [SubscribeResp{subReqID=0, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=1, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=2, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=3, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=4, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=5, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=6, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=7, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=8, respCode=0, desc='Netty is lovaly'}]
Receive server response : [SubscribeResp{subReqID=9, respCode=0, desc='Netty is lovaly'}]