在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如:HTTP,Telnet,FTP,SMTP等等。

在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优秀的网络通信框架,可以帮助我们完成自定义协议的通信。

一般而言,我们制定的协议需要两个部分:

  • Header : 协议头部,放置一些Meta信息。
  • Content : 应用之间交互的信息主体。

例如:

| Version | Content-Length | SessionId | Content |

其中Version,Content-Length,SessionId就是Header信息,Content就是交互的主体。给这个协议起一个名字叫做luck,依照luck协议,我们构建一个类。

// 消息的头部
public class LuckHeader { // 协议版本
private int version;
// 消息内容长度
private int contentLength;
// 服务名称
private String sessionId; public LuckHeader(int version, int contentLength, String sessionId) {
this.version = version;
this.contentLength = contentLength;
this.sessionId = sessionId;
} public int getVersion() {
return version;
} public void setVersion(int version) {
this.version = version;
} public int getContentLength() {
return contentLength;
} public void setContentLength(int contentLength) {
this.contentLength = contentLength;
} public String getSessionId() {
return sessionId;
} public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
} // 消息的主体
public class LuckMessage { private LuckHeader luckHeader;
private String content; public LuckMessage(LuckHeader luckHeader, String content) {
this.luckHeader = luckHeader;
this.content = content;
} public LuckHeader getLuckHeader() {
return luckHeader;
} public void setLuckHeader(LuckHeader luckHeader) {
this.luckHeader = luckHeader;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} @Override
public String toString() {
return String.format("[version=%d,contentLength=%d,sessionId=%s,content=%s]",
luckHeader.getVersion(),
luckHeader.getContentLength(),
luckHeader.getSessionId(),
content);
}
}

那么我们在Netty中如何去对这种自定义的协议编码(Encode)呢?

Netty中对数据进行编码解码需要利用Codec组件,Codec组件中分为:

  • Encoder : 编码器,将出站的数据从一种格式转换成另外一种格式。
  • Decoder : 解码器,将入站的数据从一种格式转换成另外一种格式。

LuckDecoder.java

public class LuckDecoder extends ByteToMessageDecoder {

    @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // 获取协议的版本
int version = in.readInt();
// 获取消息长度
int contentLength = in.readInt();
// 获取SessionId
byte[] sessionByte = new byte[36];
in.readBytes(sessionByte);
String sessionId = new String(sessionByte); // 组装协议头
LuckHeader header = new LuckHeader(version, contentLength, sessionId); // 读取消息内容
byte[] content = in.readBytes(in.readableBytes()).array(); LuckMessage message = new LuckMessage(header, new String(content)); out.add(message);
}
}

LuckEncoder.java

@ChannelHandler.Sharable
public class LuckEncoder extends MessageToByteEncoder<LuckMessage> { @Override
protected void encode(ChannelHandlerContext ctx, LuckMessage message, ByteBuf out) throws Exception { // 将Message转换成二进制数据
LuckHeader header = message.getLuckHeader(); // 这里写入的顺序就是协议的顺序. // 写入Header信息
out.writeInt(header.getVersion());
out.writeInt(message.getContent().length());
out.writeBytes(header.getSessionId().getBytes()); // 写入消息主体信息
out.writeBytes(message.getContent().getBytes());
}
}

编写一个逻辑控制层,展现server接收到的协议信息:

public class NettyLuckHandler extends SimpleChannelInboundHandler<Message> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
// 简单地打印出server接收到的消息
System.out.println(msg.toString());
}
}

编写完成之后,把编解码器逻辑控制器放入初始化组件中:

public class NettyLuckInitializer extends ChannelInitializer<SocketChannel> {

    private static final LuckEncoder ENCODER = new LuckEncoder();

    @Override
protected void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); // 添加编解码器, 由于ByteToMessageDecoder的子类无法使用@Sharable注解,
// 这里必须给每个Handler都添加一个独立的Decoder.
pipeline.addLast(ENCODER);
pipeline.addLast(new LuckDecoder()); // 添加逻辑控制层
pipeline.addLast(new NettyLuckHandler()); }
}

编写一个服务端启动类:

public class NettyLuckServer {

    // 指定端口号
private static final int PORT = 8888; public static void main(String args[]) throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try { ServerBootstrap serverBootstrap = new ServerBootstrap();
// 指定socket的一些属性
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 指定是一个NIO连接通道
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new NettyLuckInitializer()); // 绑定对应的端口号,并启动开始监听端口上的连接
Channel ch = serverBootstrap.bind(PORT).sync().channel(); System.out.printf("luck协议启动地址:127.0.0.1:%d/\n", PORT); // 等待关闭,同步端口
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}

光有服务端并不行,没法测试我们的server是不是成功了。所以我们还需要编写一个客户端程序。

LuckClientInitializer.java

public class LuckClientInitializer extends ChannelInitializer<SocketChannel> {

    private static final LuckEncoder ENCODER = new LuckEncoder();

    @Override
protected void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); // 添加编解码器, 由于ByteToMessageDecoder的子类无法使用@Sharable注解,
// 这里必须给每个Handler都添加一个独立的Decoder.
pipeline.addLast(ENCODER);
pipeline.addLast(new LuckDecoder()); // and then business logic.
pipeline.addLast(new NettyLuckClientHandler()); }
}

LuckClientHandler.java

public class LuckClientHandler extends SimpleChannelInboundHandler<LuckMessage> {

    @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, LuckMessage message) throws Exception {
System.out.println(message);
}
}

LuckClient.java

public class LuckClient {

    public static void main(String args[]) throws InterruptedException {

        EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new NettyLuckInitializer()); // Start the connection attempt.
Channel ch = b.connect("127.0.0.1", 8888).sync().channel(); int version = 1;
String sessionId = UUID.randomUUID().toString();
String content = "I'm the luck protocol!"; LuckHeader header = new LuckHeader(version, content.length(), sessionId);
LuckMessage message = new LuckMessage(header, content);
ch.writeAndFlush(message); ch.close(); } finally {
group.shutdownGracefully();
}
}
}

先运行NettyLuckServer.java,然后再去运行LuckClient.java可以看到控制的输出

四月 15, 2016 11:31:34 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x92534c29] REGISTERED
四月 15, 2016 11:31:34 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x92534c29] BIND(0.0.0.0/0.0.0.0:8888)
luck协议启动地址:127.0.0.1:8888
四月 15, 2016 11:31:34 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x92534c29, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
四月 15, 2016 11:31:54 下午 io.netty.handler.logging.LoggingHandler logMessage
信息: [id: 0x92534c29, L:/0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0x67a91c6b, L:/127.0.0.1:8888 - R:/127.0.0.1:53585]
[version=1,contentLength=22,sessionId=cff7b3ea-1188-4314-abaa-de04db32d39f,content=I'm the luck protocol!]

服务端顺利解析出了我们自定义的luck协议。

利用Netty构建自定义协议的通信的更多相关文章

  1. netty 自定义协议

    netty 自定义协议 netty 是什么呢? 相信很多人都被人问过这个问题.如果快速准确的回复这个问题呢?网络编程框架,netty可以让你快速和简单的开发出一个高性能的网络应用.netty是一个网络 ...

  2. Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇

    目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ...

  3. Docker | 第五章:构建自定义镜像

    前言 上一章节,主要是介绍了下Dockerfile的一些常用命令的说明.我们知道,利用Dockerfile可以构建一个新的镜像,比如运行Java环境,就需要一个JDK环境的镜像,但直接使用公共的镜像时 ...

  4. 物联网架构成长之路(35)-利用Netty解析物联网自定义协议

    一.前言 前面博客大部分介绍了基于EMQ中间件,通信协议使用的是MQTT,而传输的数据为纯文本数据,采用JSON格式.这种方式,大部分一看就知道是熟悉Web开发.软件开发的人喜欢用的方式.由于我也是做 ...

  5. Netty自定义协议解析原理与应用

    目前,大家都选择Netty做为游戏服务器框架网络通信的框架,而且目前也有很多优秀的产品是基于Netty开发的.它的稳定性,易用性和高效率性已得到广泛的认同.在游戏服务器开发中,选择netty一般就意味 ...

  6. netty使用MessageToByteEncoder 自定义协议(四)

    开发应用程序与应用程序之间的通信,程序之前通信 需要定义协议,比如http协议. 首先我们定义一个协议类 package com.liqiang.SimpeEcode; import java.sql ...

  7. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  8. 【转】Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

  9. Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

随机推荐

  1. 前端极易被误导的css选择器权重计算及css内联样式的妙用技巧

    记得大学时候,专业课的网页设计书籍里面讲过css选择器权重的计算:id是100,class是10,html标签是5等等,然后全部加起来的和进行比较... 我只想说:真是误人子弟,害人不浅! 最近,在前 ...

  2. 理解CSS边框border

    前面的话   边框是CSS盒模型属性中默默无闻的一个普通属性,CSS3的到来,但得边框属性重新焕发了光彩.本文将详细介绍CSS边框 基础样式   边框是一条以空格分隔的集合样式,包括边框粗细(边框宽度 ...

  3. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  4. linux基础学习笔记

    我用的是centOS7.0版本的系统.linux的shell终端窗口类似于wind的command窗口 shell命令提示符格式:用户名@主机名:目录名 提示符 @前面的是已登录的用户名,@之后的为计 ...

  5. 了解PHP中的Array数组和foreach

    1. 了解数组 PHP 中的数组实际上是一个有序映射.映射是一种把 values 关联到 keys 的类型.详细的解释可参见:PHP.net中的Array数组    . 2.例子:一般的数组 这里,我 ...

  6. 参考bootstrap中的popover.js的css画消息弹框

    前段时间小颖的大学同学给小颖发了一张截图,图片类似下面这张图: 小颖当时大概的给她说了下,其实小颖也不知道上面那个三角形怎么画嘻嘻,给她说了DOM结构,具体的css让她自己百度,今天小颖自己参考boo ...

  7. 谈谈一些有趣的CSS题目(二)-- 从条纹边框的实现谈盒子模型

    开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...

  8. 使用SecureCRT连接虚拟机(ubuntu)配置记录

    这种配置方法,可以非常方便的操作虚拟机里的Linux系统,且让VMware在后台运行,因为有时候我直接在虚拟机里操作会稍微卡顿,或者切换速度不理想,使用该方法亲测本机效果确实ok,特此记录. Secu ...

  9. .NET CoreCLR开发人员指南(上)

    1.为什么每一个CLR开发人员都需要读这篇文章 和所有的其他的大型代码库相比,CLR代码库有很多而且比较成熟的代码调试工具去检测BUG.对于程序员来说,理解这些规则和习惯写法非常的重要. 这篇文章让所 ...

  10. BPM任务管理解决方案分享

    一.方案概述任务是企业管理者很多意志的直接体现,对于非常规性事务较多的企业,经常存在各类公司下达的各种任务跟进难.监控难等问题,任务不是完成效果不理解,就是时间超期,甚至很多公司管理层下达的任务都不了 ...