基於java序列化的Netty編解碼技術

    基於Java提供的對象輸入/輸出流ObjectInputStream和ObjectOutputStream,可以直接把Java對象作爲可存儲的字節數組寫入文件,也可以傳輸到網絡上。對程序員來說,基於JDK默認的序列號機制可以避免操作底層的字節數組,從而提高開發效率。

    相信大多數Java程序員接觸到的第一種序列化或者編解碼技術就是Java默認序列化,只需要序列化的POJO對象實現java.io.Serializable接口,根據實際情況生成序列ID,這個類就能通過java.io.ObjectInput和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

發佈了48 篇原創文章 · 獲贊 2 · 訪問量 9975
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章