转载:http://blog.csdn.net/top_code/article/details/50901623

在上一篇文章中,我们使用Netty4本身自带的ObjectDecoder,ObjectEncoder来实现POJO对象的传输,但其使用的是Java内置的序列化,由于Java序列化的性能并不是很好,所以很多时候我们需要用其他高效的序列化方式,例如 protobuf,Hessian, Kryo,Jackson,fastjson等。

本文中Java序列化不是重点,对Java序列化不熟悉的同学的请自行查找资料学习,本篇我们重点介绍如何构造我们的Encoder和Decoder 。

流式传输特点

In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer. Unfortunately, the buffer of a stream-based transport is not a queue of packets but a queue of bytes. It means, even if you sent two messages as two independent packets, an operating system will not treat them as two messages but as just a bunch of bytes. Therefore, there is no guarantee that what you read is exactly what your remote peer wrote. For example, let us assume that the TCP/IP stack of an operating system has received three packets: 
 
Because of this general property of a stream-based protocol, there’s high chance of reading them in the following fragmented form in your application: 

Therefore, a receiving part, regardless it is server-side or client-side, should defrag the received data into one or more meaningful frames that could be easily understood by the application logic. In case of the example above, the received data should be framed like the following: 

通常情况下有下面几种解决方案:

  1. 消息定长
  2. 在包尾增加一个标识,通过这个标志符进行分割
  3. 将消息分为两部分,也就是消息头和消息尾,消息头中写入要发送数据的总长度,通常是在消息头的第一个字段使用int值(如果消息很大可以考虑用long值)来标识发送数据的长度。

本文中采用第三种方案,自定义Encoder/Decoder进行对象的传输。

准备工作

JDK 7 
Eclipse Juno 
Maven 3.3

序列化框架

本篇我们使用Kryo对POJO对象进行序列化,当然也可以采用protobuf,Hessian做序列化,有兴趣的同学可以自己动手试试。

1、添加Kyro 依赖

<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>3.0.3</version>
</dependency>

2、自定义Encoder 
首先我们实现一个Encoder,继承自MessageToByteEncoder

package com.ricky.codelab.netty.ch3.serialiaztion;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; import java.io.ByteArrayOutputStream; import org.apache.commons.io.IOUtils; import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Output;
import com.ricky.codelab.netty.model.Car; /**
* 自定义Encoder
* @author Ricky
*
*/
public class KyroMsgEncoder extends MessageToByteEncoder<Car> { private Kryo kryo = new Kryo(); @Override
protected void encode(ChannelHandlerContext ctx, Car msg, ByteBuf out) throws Exception { byte[] body = convertToBytes(msg); //将对象转换为byte
int dataLength = body.length; //读取消息的长度
out.writeInt(dataLength); //先将消息长度写入,也就是消息头
out.writeBytes(body); //消息体中包含我们要发送的数据
} private byte[] convertToBytes(Car car) { ByteArrayOutputStream bos = null;
Output output = null;
try {
bos = new ByteArrayOutputStream();
output = new Output(bos);
kryo.writeObject(output, car);
output.flush(); return bos.toByteArray();
} catch (KryoException e) {
e.printStackTrace();
}finally{
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(bos);
}
return null;
} }

在KyroMsgEncoder中我们需要覆盖 encode(ChannelHandlerContext ctx, Car msg, ByteBuf out) 方法,其主要用来将要传输的对象转换为byte数组。

3、自定义Decoder 
自定义Decoder 需继承ByteToMessageDecoder类,并覆盖其decode方法。

package com.ricky.codelab.netty.ch3.serialiaztion;

import java.io.ByteArrayInputStream;
import java.util.List; import org.apache.commons.io.IOUtils; import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.ricky.codelab.netty.model.Car; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; /**
* 自定义Decoder
* @author Ricky
*
*/
public class KyroMsgDecoder extends ByteToMessageDecoder { public static final int HEAD_LENGTH = 4; private Kryo kryo = new Kryo(); @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < HEAD_LENGTH) { //这个HEAD_LENGTH是我们用于表示头长度的字节数。 由于Encoder中我们传的是一个int类型的值,所以这里HEAD_LENGTH的值为4.
return;
}
in.markReaderIndex(); //我们标记一下当前的readIndex的位置
int dataLength = in.readInt(); // 读取传送过来的消息的长度。ByteBuf 的readInt()方法会让他的readIndex增加4
if (dataLength < 0) { // 我们读到的消息体长度为0,这是不应该出现的情况,这里出现这情况,关闭连接。
ctx.close();
} if (in.readableBytes() < dataLength) { //读到的消息体长度如果小于我们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
in.resetReaderIndex();
return;
} byte[] body = new byte[dataLength]; //传输正常
in.readBytes(body);
Object o = convertToObject(body); //将byte数据转化为我们需要的对象
out.add(o);
} private Object convertToObject(byte[] body) { Input input = null;
ByteArrayInputStream bais = null;
try {
bais = new ByteArrayInputStream(body);
input = new Input(bais); return kryo.readObject(input, Car.class);
} catch (KryoException e) {
e.printStackTrace();
}finally{
IOUtils.closeQuietly(input);
IOUtils.closeQuietly(bais);
} return null;
} }

在KyroMsgDecoder中覆盖父类的decode(ChannelHandlerContext ctx, ByteBuf in, List out)方法,将byte数组转换为对象。

服务端程序

KyroTransferServer.java

package com.ricky.codelab.netty.ch3;

import com.ricky.codelab.netty.ch3.serialiaztion.KyroMsgDecoder;
import com.ricky.codelab.netty.ch3.serialiaztion.KyroMsgEncoder;
import com.ricky.codelab.netty.util.Constant; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* Netty4.x 自定义Decoder,Encoder进行对象传递
* @author Ricky
*
*/
public class KyroTransferServer { private final int port; public KyroTransferServer(int port) {
this.port = port;
} public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new KyroMsgEncoder(),
new KyroMsgDecoder(),
new KyroServerHandler());
}
}); // Bind and start to accept incoming connections.
b.bind(port).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception { new KyroTransferServer(Constant.PORT).run();
}
}

KyroServerHandler.java

package com.ricky.codelab.netty.ch3;

import com.ricky.codelab.netty.model.Car;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; public class KyroServerHandler extends ChannelInboundHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception { System.out.println("server receive msg:"+msg); Car car = new Car();
car.setName("K5");
car.setBrand("KIA");
car.setPrice(24.5);
car.setSpeed(196); System.out.println("server write msg:"+car);
ctx.writeAndFlush(car);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
} }

客户端程序

KyroTransferClient.java

package com.ricky.codelab.netty.ch3;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 com.ricky.codelab.netty.ch3.serialiaztion.KyroMsgDecoder;
import com.ricky.codelab.netty.ch3.serialiaztion.KyroMsgEncoder;
import com.ricky.codelab.netty.model.Car;
import com.ricky.codelab.netty.util.Constant; public class KyroTransferClient {
private String host;
private int port;
private Car message; public KyroTransferClient(String host, int port, Car message) {
this.host = host;
this.port = port;
this.message = message;
} public void send() throws InterruptedException { Bootstrap bootstrap = new Bootstrap();
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try {
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new KyroMsgEncoder(),
new KyroMsgDecoder(),
new KyroClientHandler(message));
}
}); ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception { Car message = new Car();
message.setName("X5");
message.setBrand("BMW");
message.setPrice(52.6);
message.setSpeed(200); new KyroTransferClient(Constant.HOST, Constant.PORT, message).send();
}
}

KyroClientHandler.java

package com.ricky.codelab.netty.ch3;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import com.ricky.codelab.netty.model.Car; public class KyroClientHandler extends ChannelInboundHandlerAdapter { private final Car message; /**
* Creates a client-side handler.
*/
public KyroClientHandler(Car message) {
this.message = message;
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// Send the message to Server
super.channelActive(ctx); System.out.println("client send message:"+message);
ctx.writeAndFlush(message);
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
// you can use the Object from Server here
System.out.println("client receive msg:"+msg);
ctx.close();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception { cause.printStackTrace();
ctx.close();
}
}

运行测试

先运行服务端程序,然后运行客户端程序,将看到控制台有内容输出

服务端程序输出

server receive msg:Car [name=X5, brand=BMW, price=52.6, speed=200.0] 
server write msg:Car [name=K5, brand=KIA, price=24.5, speed=196.0]

客户端程序输出

client send message:Car [name=X5, brand=BMW, price=52.6, speed=200.0] 
client receive msg:Car [name=K5, brand=KIA, price=24.5, speed=196.0]

Netty自定义Encoder/Decoder进行对象传递的更多相关文章

  1. 自定义Encoder/Decoder进行对象传递

    转载:http://blog.csdn.net/top_code/article/details/50901623 在上一篇文章中,我们使用Netty4本身自带的ObjectDecoder,Objec ...

  2. netty 自定义通讯协议

    Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,例如http协议中,就是通过HttpRequestDecoder对ByteBuf数据流进行处理,转换成http的对象.基于 ...

  3. jsp页面间对象传递方法

    严格的来说不能叫做JSP页面间的对象传递,实际应该是页面间对象共享的方法: 1. 通过servletcontext,也就是application对象了,但这种情况要求在同一个web应用下,      ...

  4. WebService CXF学习:复杂对象传递(List,Map)

    转自:https://blog.csdn.net/z69183787/article/details/35988335 第一步:创建存储复杂对象的类(因为WebServices的复杂对象的传递,一定要 ...

  5. netty源码分析 - Recycler 对象池的设计

    目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...

  6. 【转】asp.net中利用session对象传递、共享数据[session用法]

    来自:http://blog.unvs.cn/archives/session-transfer-method.html 下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值 ...

  7. java 对象传递 是 值传递 还是 引用传递?

    这个问题说实话我感觉没有太大的意义. 按第一印象和c++的一些思想去理解的话对象传递是引用传递,因为传递过去的对象的值能被改变. 但是又有很多人,不知道从哪里扣出来一句,java中只有值传递,没有引用 ...

  8. Oracle自定义数据类型 2 (调用对象方法)

    调用对象方法 调用对象方法基于类型创建表后,就可以在查询中调用对象方法 A. 创建基于对象的表语法: create   table   <表名>   of   <对象类型>意义 ...

  9. asp.net中利用session对象传递、共享数据[session用法]

    下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值: 首先定义将一个文本值或单独一个值赋予session,如下: session[“name”]=textbox1.text ...

随机推荐

  1. collision weaknesses

    15.1. hashlib — Secure hashes and message digests — Python 3.5.6 documentation https://docs.python.o ...

  2. Django中URL的解析和反查

    add by zhj: 如果想用reverse(namespace1:namespace2:...:namespaceN:name)反查url(注意:用reverse('polls:index')方法 ...

  3. Java压缩多个文件并导出

    controller层: /** * 打包压缩下载文件 */ @RequestMapping(value = "/downLoadZipFile") public void dow ...

  4. 4.windows如何导入python包

    python链接:https://www.python.org/downloads/release/python-2715/ pip链接:https://pypi.org/project/pip/#f ...

  5. MySQL 储存过程-原理、语法、函数详细说明

    Mysql储存过程是一组为了完成特定功能的SQL语句集,经过编译之后存储在数据库中,当需要使用该组SQL语句时用户只需要通过指定储存过程的名字并给定参数就可以调用执行它了,简而言之就是一组已经写好的命 ...

  6. Python 之各种推导式玩法

    推导式套路 之前我们已经学习了最简单的列表推导式和生成器表达式.但是除此之外,其实还有字典推导式.集合推导式等等. 下面是一个以列表推导式为例的推导式详细格式,同样适用于其他推导式. variable ...

  7. 异常来自 HRESULT:0x80070057 (E_INVALIDARG)

    莫名其妙的编译总会报错 异常来自 HRESULT:0x80070057 (E_INVALIDARG) 未能加载程序集....... 几次删除引用然后重新引用程序集还是报错 奔溃中.... 网上搜索还真 ...

  8. 线性表:实现单链表和子类栈(Stack)及单向队列(Queue) [C++]

    刚刚开始学习c++.之前c的内容掌握的也不多,基本只是一本概论课的程度,以前使用c的struct写过的链表.用python写过简单的数据结构,就试着把两者用c++写出来,也是对c++的class,以及 ...

  9. 2017-2018 ACM-ICPC East Central North America Regional Contest (ECNA 2017) Solution

    A:Abstract Art 题意:给出n个多边形,求n个多边形分别的面积和,以及面积并 思路:模板 #include <bits/stdc++.h> using namespace st ...

  10. 2018-2019 Всероссийская командная олимпиада школьников по программированию, интернет-тур + отборы регионов (ВКОШП 18, интернет-тур) Solution

    A: 水. #include<bits/stdc++.h> using namespace std; typedef long long ll; const ll INFLL = 0x3f ...