利用Netty构建自定义协议的通信
在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如: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构建自定义协议的通信的更多相关文章
- netty 自定义协议
		netty 自定义协议 netty 是什么呢? 相信很多人都被人问过这个问题.如果快速准确的回复这个问题呢?网络编程框架,netty可以让你快速和简单的开发出一个高性能的网络应用.netty是一个网络 ... 
- Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇
		目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ... 
- Docker | 第五章:构建自定义镜像
		前言 上一章节,主要是介绍了下Dockerfile的一些常用命令的说明.我们知道,利用Dockerfile可以构建一个新的镜像,比如运行Java环境,就需要一个JDK环境的镜像,但直接使用公共的镜像时 ... 
- 物联网架构成长之路(35)-利用Netty解析物联网自定义协议
		一.前言 前面博客大部分介绍了基于EMQ中间件,通信协议使用的是MQTT,而传输的数据为纯文本数据,采用JSON格式.这种方式,大部分一看就知道是熟悉Web开发.软件开发的人喜欢用的方式.由于我也是做 ... 
- Netty自定义协议解析原理与应用
		目前,大家都选择Netty做为游戏服务器框架网络通信的框架,而且目前也有很多优秀的产品是基于Netty开发的.它的稳定性,易用性和高效率性已得到广泛的认同.在游戏服务器开发中,选择netty一般就意味 ... 
- netty使用MessageToByteEncoder 自定义协议(四)
		开发应用程序与应用程序之间的通信,程序之前通信 需要定义协议,比如http协议. 首先我们定义一个协议类 package com.liqiang.SimpeEcode; import java.sql ... 
- netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端
		本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ... 
- 【转】Netty之解决TCP粘包拆包(自定义协议)
		1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ... 
- Netty之解决TCP粘包拆包(自定义协议)
		1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ... 
随机推荐
- SQLSERVER走起 APP隆重推出
			SQLSERVER走起 APP隆重推出 为方便大家查看本微信公众以前推送的文章,QQ群里面的某位SQLSERVER重度爱好者开发了<SQLSERVER走起>的APP 以供大家一起交流 网页 ... 
- 我的MYSQL学习心得(一) 简单语法
			我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ... 
- WPF 有用博客地址
			增加智能感知的RichTextBox扩展控件(WPF) WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式.水印.Label标签. ... 
- PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)
			100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ... 
- 缓存、队列(Memcached、redis、RabbitMQ)
			本章内容: Memcached 简介.安装.使用 Python 操作 Memcached 天生支持集群 redis 简介.安装.使用.实例 Python 操作 Redis String.Hash.Li ... 
- MongoDB系列(一):简介及安装
			什么是MongoDB MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在为应用提供可扩展的高 ... 
- Django admin定制化,User字段扩展[原创]
			前言 参考上篇博文,我们利用了OneToOneField的方式使用了django自带的user,http://www.cnblogs.com/caseast/p/5909248.html , 但这么用 ... 
- stringstream的基本用法
			原帖地址:https://zhidao.baidu.com/question/580048330.htmlstringstream是字符串流.它将流与存储在内存中的string对象绑定起来.在多种数据 ... 
- PHP static静态属性和静态方法
			这里分析了php面向对象中static静态属性和静态方法的调用.关于它们的调用(能不能调用,怎么样调用),需要弄明白了他们在内存中存放位置,这样就非常容易理解了.静态属性.方法(包括静态与非静态)在内 ... 
- BPM公文管理解决方案分享
			一.方案概述 公文作为一种规范性文书,具有法律性.指导性.政令性强的特点,是企事业单位政令上通下达的重要方式.及时.准确.安全地处理.控制和管理公文,方能保障企事业单位正常运转,确保组织权威和政令畅通 ... 
