上一篇 netty入门篇(1)

一、编码解码技术

如何评价一个编解码技术:

  • 是否支持跨语言,或者说支持的语言是否丰富
  • 编码码流大小,影响传输速度
  • 编码和解码的性能,即时间
  • 类库是否精致,API是否方便
  • 使用难度

1. Java序列化缺点

Java也提供了序列化技术,在工业化工程中有以下缺点:

  • 无法跨语言
  • 序列化后的码流太大
  • 序列化的性能太差

下面我们来测试以下jdk序列化的问题

创建一个测试类UserInfo:

 import java.io.Serializable;
import java.nio.ByteBuffer; /**
* @author Administrator
* @version 1.0
* @date 2014年2月23日
*/
public class UserInfo implements Serializable { /**
* 默认的序列号
*/
private static final long serialVersionUID = 1L; private String userName; private int userID; public UserInfo buildUserName(String userName) {
this.userName = userName;
return this;
} public UserInfo buildUserID(int userID) {
this.userID = userID;
return this;
} /**
* @return the userName
*/
public final String getUserName() {
return userName;
} /**
* @param userName the userName to set
*/
public final void setUserName(String userName) {
this.userName = userName;
} /**
* @return the userID
*/
public final int getUserID() {
return userID;
} /**
* @param userID the userID to set
*/
public final void setUserID(int userID) {
this.userID = userID;
} /**
* 将当前对象转换一个byte[]数组
* @return
*/
public byte[] codeC() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
//写入userName长度和内容
byte[] value = this.userName.getBytes();
buffer.putInt(value.length);
buffer.put(value);
//直接写入Id
buffer.putInt(this.userID);
buffer.flip();
value = null;
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
} public byte[] codeC(ByteBuffer buffer) {
buffer.clear();
byte[] value = this.userName.getBytes();
buffer.putInt(value.length);
buffer.put(value);
buffer.putInt(this.userID);
buffer.flip();
value = null;
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
}

其中的codeC是最朴素的编码方法,我们来和它比较以下

比较大小:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream; /**
* @author Administrator
* @version 1.0
* @date 2014年2月23日
*/
public class TestUserInfo { /**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
UserInfo info = new UserInfo();
info.buildUserID(100).buildUserName("Welcome to Netty");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(info);
os.flush();
os.close();
byte[] b = bos.toByteArray();
System.out.println("The jdk serializable length is : " + b.length);
bos.close();
System.out.println("-------------------------------------");
System.out.println("The byte array serializable length is : "
+ info.codeC().length); } }

结果有点不能接受,这么一点就大了6倍

"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\java" -Didea.launcher.port=7537 "-Didea.launcher.bin.path=C:\dev\JetBrains\IntelliJ IDEA 2016.2.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_102\jre\lib\rt.jar;G:\projects-helloworld\netty\target\classes;G:\repo\maven\io\netty\netty-all\4.1.5.Final\netty-all-4.1.5.Final.jar;C:\dev\JetBrains\IntelliJ IDEA 2016.2.1\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain demo.codec.serializable.TestUserInfo
The jdk serializable length is : 117
-------------------------------------
The byte array serializable length is : 24

比较下时间

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer; /**
* @author Administrator
* @version 1.0
* @date 2014年2月23日
*/
public class PerformTestUserInfo { /**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
UserInfo info = new UserInfo();
info.buildUserID(100).buildUserName("Welcome to Netty");
int loop = 1000000;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
long startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
os.writeObject(info);
os.flush();
os.close();
byte[] b = bos.toByteArray();
bos.close();
}
long endTime = System.currentTimeMillis();
System.out.println("The jdk serializable cost time is : "
+ (endTime - startTime) + " ms"); System.out.println("-------------------------------------"); ByteBuffer buffer = ByteBuffer.allocate(1024);
startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
byte[] b = info.codeC(buffer);
}
endTime = System.currentTimeMillis();
System.out.println("The byte array serializable cost time is : "
+ (endTime - startTime) + " ms"); } }

运行结果,jdk的慢了10倍都不止

The jdk serializable cost time is  : 1928 ms
-------------------------------------
The byte array serializable cost time is : 164 ms

2. 主流的编解码框架简介

  • Google的Protobuf
  • Facebook的Thrift
  • JBoss Marshalling

这里主要介绍这3种,还有其他著名比如Hryo等等...

Google ProtoBuf

google内部久经考验。它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关方法和属性。

特点:

  •   结构化数据存储格式
  •   性能高效
  •   语言无关、平台无关、扩展性
  •   官方支持Java、C++和Python三种语言

(1) ProtoBuf使用二进制编码,而不是XML,尽管XML的可读性和扩展性都不错,但是XML牺牲的空间和时间开销太大,不适合高性能框架

(2) ProtoBuf另一个吸引人的地方是数据描述文件和代码生成机制

下面的图很有说服力,为什么这么多人选择Google的Protobuf

性能对比:

 码流对比:

Facebook的Thrift

对当时的Facebook而言,thrift用于解决各系统间大数量的传输通信问题,因此可以多种语言,C++ C# Cocoa Erlang Haskell Java Perl PHP Python Ruby和Smalltalk

  • Thrift可以作为高性能的通信中间件,支持数据序列化的多种类型的RPC服务。
  • 适用于静态数据交换,即事先确定好它的数据结构,当数据结构变化时,必须重新编辑IDL文件,生成代码和编译。
  • 相对于XML和Json在性能和传输大小上有明显优势。

Thrift主要由5部分组成:

(1) 语言系统和IDL编译器:负责由用户给定的IDL文件生成相应语言接口代码;

(2) TProtocol: RPC协议层,可以选择多种不同的序列化方式,例如Binary和Json;

(3) TTransport:RPC传输层,同样可以选择不同的传输层实现,例如socket NIO和MemoryBuffer等;

(4) Tprocessor: 作为协议层和用户提供的服务实现的纽带,负责调用服务实现的接口;

(5) TServer:聚合TProtocol、TTransport和TProcessor等对象。

关注协议的话就是关于于Tprotocol层,其支持3中典型的编解码方式:

  • 通用二进制
  • 压缩二进制
  • 优化可选字段的压缩编解码

下图展示同等测试条件下的编解码耗时信息:

JBoss Marshalling

JBoss内部使用,不能跨语言,可以看做是jdk的进化版... 拥有优点如下:

  • 可插拔的类解析器、更加便捷的类加载定制策略,通过一个接口实现定制;
  • 可插拔的对象替换方式,不需要继续的方式;
  • 可插拔的预定义类缓存表,可以减小序列化的字节数组长度,提升常用类型的序列化对象性能;
  • 无须实现java.io.Serializable接口,实现序列化;
  • 利用了缓存技术提升性能

二、MessagePack编解码技术

2.1 介绍

高效、性能、跨语言、码流小、支持的语言由Java Python Ruby Hashkell C# OCaml Lua Go C C++等。

pom文件,guava是额外可以不用.

 <!-- https://mvnrepository.com/artifact/org.msgpack/msgpack -->
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>

Java API

import com.google.common.collect.Lists;
import org.msgpack.MessagePack;
import org.msgpack.template.Templates; import java.util.List; /**
* Created by carl.yu on 2016/12/15.
*/
public class ApiDemo {
public static void main(String[] args) throws Exception {
//使用了guava
List<String> src = Lists.newArrayList("msgpack", "kumofs", "viver");
MessagePack msgpack = new MessagePack();
//序列化
byte[] raw = msgpack.write(src);
//反序列化
List<String> dst1 = msgpack.read(raw, Templates.tList(Templates.TString));
System.out.println(dst1);
}
}

2.2 编写Encoder和Decoder

注意,要使用Messagepack,需要在实体类前加上注解@Message.

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack; import java.util.List; /**
* Created by carl.yu on 2016/12/15.
*/
public class MsgpackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//将msg中的字节写到array中
System.out.println("开始进行解码...");
final byte[] array;
final int length = msg.readableBytes();
array = new byte[length];
msg.getBytes(msg.readerIndex(), array, 0, length);
MessagePack msgpack = new MessagePack();
Object result = msgpack.read(array);
out.add(result);
}
}
import com.google.common.base.Throwables;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack; /**
* Created by carl.yu on 2016/12/15.
*/
public class MsgpackEncoder extends MessageToByteEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
//负责将POJO对象编码为byte数组
MessagePack msgpack = new MessagePack();
byte[] raw = null;
try {
raw = msgpack.write(msg);
} catch (Exception e) {
e.printStackTrace();
Throwables.propagateIfPossible(e);
}
out.writeBytes(raw);
}
}

分别用MessagePack进行编解码

2.3 编写Server和ServerHandler

 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.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* Created by carl.yu on 2016/12/15.
*/
public class EchoServer {
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, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
//读数据的时候用decoder解码
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
//写数据的时候用encoder编码
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
//
ch.pipeline().addLast(new EchoServerHandler());
}
}); // 绑定端口,同步等待成功
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 = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
}

主要在于2个编解码器。

在MessagePack编码器之前增加了LengthFieldPrepender,它将在ByteBuf之前增加字节的消息长度。

然后使用LengthFieldBasedFrameDecoder根据消息长度进行解码,工作原理如图:

这样获取到的永远是整包消息,非常简单的解决了烦人的半包问题

2.4 编写Client和ClientHandler

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.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender; /**
* Created by carl.yu on 2016/12/15.
*/
public class EchoClient { 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)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
//读数据的时候用decoder解码
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
//写数据的时候用encoder编码
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder()); ch.pipeline().addLast(new EchoClientHandler(100));
}
}); // 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync(); // 当代客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} /**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoClient().connect(port, "127.0.0.1");
}
}
import demo.codec.serializable.UserInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; /**
* Created by carl.yu on 2016/12/15.
*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private final int sendNumber; public EchoClientHandler(int sendNumber) {
this.sendNumber = sendNumber;
} private UserInfo[] userInfo() {
UserInfo[] userInfos = new UserInfo[sendNumber];
UserInfo userInfo = null;
for (int i = 0; i < sendNumber; i++) {
userInfo = new UserInfo();
userInfos[i] = userInfo;
userInfo.setUserID(i);
userInfo.setUserName("ABDCEFG-->" + i);
}
return userInfos;
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
/* UserInfo userInfo = new UserInfo();
userInfo.setUserID(0);
userInfo.setUserName("ABDCEFG-->" + 0);*/
UserInfo[] userInfos = userInfo();
for (int i = 0; i < userInfos.length; i++) {
ctx.writeAndFlush(userInfos[i]);
}
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端收到信息:" + msg);
// ctx.write(msg);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// ctx.flush();
// ctx.close();
}
}

后面我们会更加详细的讲解LengthFieldPrepender和LengthFieldBasedFrameDecoder,这里只需要明白用来解决半包问题即可。

三、Google Protobuf

3.1 测试Google Protobuf

准备环境:

SubscribeReq.proto:

package netty;
option java_package="demo.codec.protobuf";
option java_outer_classname="SubscribeReqProto"; message SubscribeReq{
required int32 subReqID = ;
required string userName = ;
required string productName = ;
repeated string address = ;
}

SubscribeResp.proto

package netty;
option java_package="demo.codec.protobuf";
option java_outer_classname="SubscribeRespProto"; message SubscribeResp{
required int32 subReqID = ;
required int32 respCode = ;
required string desc = ;
}

这里不详细介绍google protobuf的语法:https://developers.google.com/protocol-buffers/docs/proto?hl=zh-CN

build.bat

protoc ./proto/*.proto --java_out=../main/java

pause

google protobuf依赖maven:

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>

运行build.bat,生成:

下面我们运行以下代码来了解Protobuf的用法:

import com.google.protobuf.InvalidProtocolBufferException;

import java.util.ArrayList;
import java.util.List; /**
* @author Administrator
* @version 1.0
* @date 2014年2月23日
*/
public class TestSubscribeReqProto { // 编码方法: Object->byte[]
private static byte[] encode(SubscribeReqProto.SubscribeReq req) {
return req.toByteArray();
} // 解码方法: bayte[] -> Object
private static SubscribeReqProto.SubscribeReq decode(byte[] body)
throws InvalidProtocolBufferException {
return SubscribeReqProto.SubscribeReq.parseFrom(body);
} /**
* 创建实例
*
* @return
*/
private static SubscribeReqProto.SubscribeReq createSubscribeReq() {
//(1) Builder模式
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq
.newBuilder();
builder.setSubReqID(1);
builder.setUserName("Lilinfeng");
builder.setProductName("Netty Book");
List<String> address = new ArrayList<>();
address.add("NanJing YuHuaTai");
address.add("BeiJing LiuLiChang");
address.add("ShenZhen HongShuLin");
builder.addAllAddress(address);
return builder.build();
} /**
* @param args
* @throws InvalidProtocolBufferException
*/
public static void main(String[] args)
throws InvalidProtocolBufferException {
SubscribeReqProto.SubscribeReq req = createSubscribeReq();
System.out.println("Before encode : " + req.toString());
SubscribeReqProto.SubscribeReq req2 = decode(encode(req));
System.out.println("After decode : " + req.toString());
System.out.println("Assert equal : --> " + req2.equals(req)); } }

3.2 开发图书订购服务端

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.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
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, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
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 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 = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new SubReqServer().bind(port);
}
}

我们来注意以下编解码器的顺序:

(1) ProtobufVarint32FrameDecoder : 半包问题

(2) ProtobufDecoder:解码

(3) ProtobufVarint32LenghtFiedldPrepender:半包问题

(4) ProtobufEncoder:编码

于是逻辑处理部分可以直接使用类:

 import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
@Sharable
public class SubReqServerHandler extends ChannelInboundHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
if ("Lilinfeng".equalsIgnoreCase(req.getUserName())) {
System.out.println("Service accept client subscribe req : ["
+ req.toString() + "]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
} private SubscribeRespProto.SubscribeResp resp(int subReqID) {
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp
.newBuilder();
builder.setSubReqID(subReqID);
builder.setRespCode(0);
builder.setDesc("Netty book order succeed, 3 days later, sent to the designated address");
return builder.build();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 发生异常,关闭链路
}
}

3.3 图书订购客户端开发

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.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
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
public 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 {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} /**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new SubReqClient().connect(port, "127.0.0.1");
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.ArrayList;
import java.util.List; /**
* @author lilinfeng
* @version 1.0
* @date 2014年2月14日
*/
public class SubReqClientHandler extends ChannelInboundHandlerAdapter { /**
* Creates a client-side handler.
*/
public SubReqClientHandler() {
} @Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.write(subReq(i));
}
ctx.flush();
} private SubscribeReqProto.SubscribeReq subReq(int i) {
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq
.newBuilder();
builder.setSubReqID(i);
builder.setUserName("Lilinfeng");
builder.setProductName("Netty Book For Protobuf");
List<String> address = new ArrayList<>();
address.add("NanJing YuHuaTai");
address.add("BeiJing LiuLiChang");
address.add("ShenZhen HongShuLin");
builder.addAllAddress(address);
return builder.build();
} @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) {
cause.printStackTrace();
ctx.close();
}
}

在不怎么了解Protobuf实现和使用细节的情况 下,我们就可以轻松支持Google Protobuf编码

3.4 注意事项

ProtobufDecoder仅仅负责解码,因此在ProtobufDecoder前面,一定要能够处理半包的解码器,有以下3种方式:

(1) 使用Netty提供的ProtobufVarint32FrameDecoder,它可以处理半包消息;

(2) 继承Netty提供的通用半包解码器LengthFieldBasedFrameDecoder;

(3) 继承ByteToMessageDecoder,自己处理..

半包问题必须解决,否则服务器无法正常工作。

四、JBoss Marshalling编解码

暂时略。可以参考netty初探(2)

netty中级篇(2)的更多相关文章

  1. netty入门篇(1)

    上一篇 nio简介  下一篇 netty中级篇(2) 一.为什么选择Netty Netty是最流行的框架之一.健壮性.功能.性能.可定制性和可扩展性在同类框架中首屈一指,因此被大规模使用,例如ROCK ...

  2. Java工程师学习指南 中级篇

    Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...

  3. Java工程师学习指南(中级篇)

    Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...

  4. Python 正则表达式入门(中级篇)

    Python 正则表达式入门(中级篇) 初级篇链接:http://www.cnblogs.com/chuxiuhong/p/5885073.html 上一篇我们说在这一篇里,我们会介绍子表达式,向前向 ...

  5. 25个增强iOS应用程序性能的提示和技巧(中级篇)(3)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(3) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  6. 25个增强iOS应用程序性能的提示和技巧(中级篇)(2)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(2) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  7. 25个增强iOS应用程序性能的提示和技巧--中级篇

    25个增强iOS应用程序性能的提示和技巧--中级篇 标签: ios性能优化内存管理 2013-12-13 10:55 738人阅读 评论(0) 收藏 举报  分类: IPhone开发高级系列(34)  ...

  8. Python3学习(2)-中级篇

    Python3学习(1)-基础篇 Python3学习(2)-中级篇 Python3学习(3)-高级篇 切片:取数组.元组中的部分元素 L=['Jack','Mick','Leon','Jane','A ...

  9. django-url调度器-中级篇

    在初级篇中,我们接触了: 1.url 的简单编写 2.两种传参的方式 3.捕获的参数总是字符串 4.为视图设置默认参数 …… 在中级篇中将更进一步. 包含其它的URLconfs 当网站非常大的时候,将 ...

随机推荐

  1. 【分享】LateX排版软件学习教程合集

    来源于:http://www.hejizhan.com/html/xueke/416/x416_13.html  LATEX2e科技排版指南.pdf 8.3 MB  An Example LaTeX ...

  2. vim实用笔记

    vim实用笔记   真是不知不觉过了一年,前段时间忙着考试什么的,没再写笔记写博客,考完又懒懒地玩了几天.这几天其实都在读别人的博客,感受一下大神的工作和生活感悟,感受一下过来人的经历和经验,对自己总 ...

  3. Java泛型学习笔记--Java泛型和C#泛型比较学习(一)

    总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型 ...

  4. Push Notification总结系列之移动客户端定位服务

    Push Notification系列概括: 1.Push Notification简介和证书说明及生成配置 2.Push Notification的iOS处理代码和Provider详解 3.Push ...

  5. DevExpress 学习使用之 NavBarControl

    TNND,没辙啊,没用过那么高级的玩意儿,暂时也没找到中文的详细帮助,简直就是蚂蚁搬家似的摸索,一点儿点儿来吧. 先是NavBarControl的界面样子,貌似可以通过 PaintStyleKind ...

  6. IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)

    EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...

  7. TOGAF企业连续体和工具之企业连续体构成及架构划分

    TOGAF企业连续体和工具之企业连续体构成及架构划分 又回头看了之前文章的评论,本人也同样感慨这些文章的确像政治课本般的虚无缥缈,所以对费力看完却觉得无从下手的看官致以诚挚的歉意和理解,因为这个问题也 ...

  8. Oracle修改字段类型和长度

    Oracle修改字段名 alter table 表名 rename column 旧字段名 to 新字段名 Oracle修改字段类型和长度 alter table 表名 modify 字段名 数据类型 ...

  9. easyui获取当前点击对象tabs的title

    现在如果要关闭一个tab,只能点击该tab上面的x号.现增加双击tab使其关闭. 可使用jquery的bind函数绑定dblclick双击事件 tabs的关闭方法为close 要传一个title参数表 ...

  10. spring redis入门

    小二,上菜!!! 1. 虚拟机上安装redis服务 下载tar包,wget http://download.redis.io/releases/redis-2.8.19.tar.gz. 解压缩,tar ...