目前,大家都选择Netty做为游戏服务器框架网络通信的框架,而且目前也有很多优秀的产品是基于Netty开发的。它的稳定性,易用性和高效率性已得到广泛的认同。在游戏服务器开发中,选择netty一般就意味着我们要使用长连接来建立与客户端的通信,并且是自定义协议,在网络开发中,我们不得不处理断包,粘包的问题,因为Tcp/ip是基于数据流的传输,包与包之间没有明确的界限,而且于由网络路由的复杂性,大包有可能分成小包,小包也有可能被组装成大包进行传输。而Netty就考虑到了这一点,而且它用一个类就帮我们处理了这个问题,这个类就是:LengthFieldBasedFrameDecoder。这里是它的API说明:http://netty.io/4.1/api/index.html

这里简单翻译一下,以供参考。

这个解码器是用来动态分割消息包的,这些消息包都带有一个表示消息长度的值。当你需要解码一个二进制流的包时,有一个表示消息内容长度或整个包长度的包头是非常有用的。LengthFieldBasedFrameDecoder解码器提供一些参数的配置,它可以解码任何一种带包长度信息的包。这些包经常出现在client/server模式的网络通信协议中,下面是一些例子,它们可以帮助你去选择哪个配置来使用。

这段代码是Netty服务启动时的配置

public class ServerManager {

private int port;

public ServerManager(int port) {

this.port = port;

}

//参考的官方例子

public void run() throws Exception {

EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap b = new ServerBootstrap(); // (2)

b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)

.childHandler(new ChannelInitializer<SocketChannel>() { // (4)

@Override

public void initChannel(SocketChannel ch) throws Exception {

//这里就是添加解码器的地方,它有几种不同的构造方法。下面是带全部参数的构造方法,这些参数的作用将在下面的例子中说明,这里没有赋值。ByteOrder可以选择编码是大端还是小端(关于大端或小端的问题,不明白的请自行百度),maxFrameLength表示接收到的包的最大长度。

ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast));

ch.pipeline().addLast(new ServerHandler());

}

}).option(ChannelOption.SO_BACKLOG, 128) // (5)

.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

// Bind and start to accept incoming connections.

ChannelFuture f = b.bind(port).sync(); // (7)

// Wait until the server socket is closed.

// In this example, this does not happen, but you can do that to

// gracefully

// shut down your server.

f.channel().closeFuture().sync();

} finally {

workerGroup.shutdownGracefully();

bossGroup.shutdownGracefully();

}

}

}

1)2个字节的包头记录包长,0 字节偏移,解码后不跳过包头。

这个例子中,包头表示包长度的值是12,它表示的是包的内容”HELLO,WORLD”的长度。默认来说,解码器会把这个包头的长度假设为包头后面所有字节的长度,因为这个包可以被下面的这个配置解码。

lengthFieldOffset   = 0

lengthFieldLength   = 2

lengthAdjustment    = 0

initialBytesToStrip = 0 (= do not strip header)

BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)

+--------+----------------+      +--------+----------------+

| Length | Actual Content |----->| Length | Actual Content |

| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |

+--------+----------------+      +--------+----------------+

Before Decode表示的是解码之前接收到的完整的数据包的包结构,After Decode表示的是解码完成后,传给下一层过滤器的包结构。在上面的服务器启动代码中,Before Decode就是客户端传过来的包,而After Decode就是经过这个解码器之后,传到ServerHandler的public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 方法中的Object msg的结构,是一个ByteBuf类型。下面所有的例子都是如此。

public class ServerHandler implements ChannelInboundHandler {

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void channelActive(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

ByteBuf byteBuf = (ByteBuf) msg;

//读取包的包长度

int len = byteBuf.readInt();

//剩下的就是包内容了。

.........

}

public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

// TODO Auto-generated method stub

}

public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {

// TODO Auto-generated method stub

}

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

// TODO Auto-generated method stub

//cause.printStackTrace();

}

2) 2个字节的包头记录包长,0 字节偏移,解码后跳过包头。

我们可以根据ByteBuf.readableBytes(), 方法来获取包的长度值,所以,有时候我们希望解码后,可以跳过表示信息长度的包头。下面这个例子就实现了它,跳过2 个字节的包头信息。

lengthFieldOffset   = 0

lengthFieldLength   = 2

lengthAdjustment    = 0

initialBytesToStrip = 2 (= the length of the Length field)

BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)

+--------+----------------+      +----------------+

| Length | Actual Content |----->| Actual Content |

| 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |

+--------+----------------+      +----------------+

这样我们在public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception中得到的msg就只是包含了包内容的信息,而不包括包头的信息了。

3) 2个字节的包头记录包长,0 字节偏移,包头表示包长度的值代表的整个包的长度,包括包头占的字节数。

大部分情况下,包长度代表的是包内容的长度,比如之前的例子。但是,在有些协议中,包长度代表的是整个协议传输包的长度,包括包头的长度。下面这个例子中,我们指定一个非0的lengthAdjustment值,因为下面这个例子中的包长度总是比包的内容长度多2个字节,所以我们指定lengthAdjustment = -2 作为补偿。

lengthFieldOffset   =  0

lengthFieldLength   =  2

lengthAdjustment    = -2 (= the length of the Length field)

initialBytesToStrip =  0

BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)

+--------+----------------+      +--------+----------------+

| Length | Actual Content |----->| Length | Actual Content |

| 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |

+--------+----------------+      +--------+----------------+

解码后,收到的msg信息和1)中是一样的。

4) 5字节的包头,3字节表示包的长度,这3个字节在包头的末尾。不跳过包头

这个例子是1)的一个变种。2字节表示整个包的大小(不包括这2个字节数),3字节表示包内容的长度。

lengthFieldOffset   = 2 (= the length of Header 1)

lengthFieldLength   = 3

lengthAdjustment    = 0

initialBytesToStrip = 0

BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)

+----------+----------+----------------+            +----------+----------+----------------+

| Header 1 |  Length  | Actual Content |----->    | Header 1 |  Length  | Actual Content |

|  0xCAFE  | 0x00000C   | "HELLO, WORLD" |        |  0xCAFE  | 0x00000C | "HELLO, WORLD" |

+----------+----------+----------------+               +----------+----------+----------------+

Header1的值是15,Length是12

5) 4 字节的包头,在包的中间有2字节长度表示包内容的长度,解码后跳过第一个包头和包长度的值

这个例子是上面所有例子的一个综合,在包头信息中,包长度前面有一个预设的包头,包长度后面,有一个额外的包头,预设包头影响lengthFieldOffset的值,额外的包头影响lengthAdjustment的值,这里设置一个非0的值给initialBytesToStrip 表示跳过预设包头和包长度的值。

lengthFieldOffset   = 1 (= the length of HDR1)

lengthFieldLength   = 2

lengthAdjustment    = 1 (= the length of HDR2)

initialBytesToStrip = 3 (= the length of HDR1 + LEN)

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

+------+--------+------+----------------+      +------+----------------+

| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

+------+--------+------+----------------+      +------+----------------+

6) 4 字节的包头,在包的中间有2字节长度表示包内容的长度,解码后跳过第一个包头和包长度的值,包长度的值代表的是整个包的长度。

这个例子与上面的例子类似,只是这里包长度表示的整个包的长度

lengthFieldOffset   =  1

lengthFieldLength   =  2

lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)

initialBytesToStrip = 3

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

+------+--------+------+----------------+      +------+----------------+

| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

+------+--------+------+----------------+      +------+----------------+

通过以上几种例子的配置,我们可以灵活的定义我们的协议格式,通过简单的配置Netty的解码器,就可以完成消息的解码,又方便,又安全。

转载请注明,来自游戏技术网:http://www.youxijishu.com

打赏

Netty自定义协议解析原理与应用的更多相关文章

  1. [转]netty对http协议解析原理

    本文主要介绍netty对http协议解析原理,着重讲解keep-alive,gzip,truncked等机制,详细描述了netty如何实现对http解析的高性能. 1 http协议 1.1 描述 标示 ...

  2. netty对http协议解析原理解析

    本文主要介绍netty对http协议解析原理,着重讲解keep-alive,gzip,truncked等机制,详细描述了netty如何实现对http解析的高性能. 1 http协议 1.1 描述 标示 ...

  3. netty对http协议解析原理解析(转载)

    本文主要介绍netty对http协议解析原理,着重讲解keep-alive,gzip,truncked等机制,详细描述了netty如何实现对http解析的高性能. 1 http协议 1.1 描述 标示 ...

  4. netty 自定义协议

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

  5. 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)

    一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行)   LineBasedFrameDecoder是回 ...

  6. netty自定义协议 心跳 断线重连源码

    https://github.com/aa1356889/NettyHeartbeat

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

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

  8. 这一次搞懂Spring自定义标签以及注解解析原理

    前言 在上一篇文章中分析了Spring是如何解析默认标签的,并封装为BeanDefinition注册到缓存中,这一篇就来看看对于像context这种自定义标签是如何解析的.同时我们常用的注解如:@Se ...

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

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

随机推荐

  1. MySQL MEB常见用法

    1. 备份成镜像 备份: ./mysqlbackup --socket=/usr/local/mysql-advanced--linux-glibc2.-x86_64/data/mysql.sock ...

  2. https证书链不完整

    公司的一个域名,用浏览器打开能正常访问,但是在linux下使用curl命令,总是报错,报错信息如下: curl: (60) Peer certificate cannot be authenticat ...

  3. DDD实战进阶第一波(九):开发一般业务的大健康行业直销系统(实现经销商上下文仓储与领域逻辑)

    上篇文章主要讲述了经销商上下文的需求与POCO对象,这篇文章主要讲述该界限上下文的仓储与领域逻辑的实现. 关于界限上下文与EF Core数据访问上下文参考产品上下文相应的实现,这里不再累述. 因为在经 ...

  4. MySQL 的索引优化

    索引类似大学图书馆建书目索引,可以提高数据检索的效率,降低数据库的IO成本.MySQL在300万条记录左右性能开始逐渐下降,虽然官方文档说500~800w记录,所以大数据量建立索引是非常有必要的.My ...

  5. Visual Studio Live Share不完全指北

    Visual Studio Live Share是什么? 是VS的一个实时协作开发的扩展工具. github地址:https://github.com/MicrosoftD...文档地址:https: ...

  6. Roundcube 1.2.2 - Remote Code Execution

    本文简要记述一下Roundcube 1.2.2远程代码执行漏洞的复现过程. 漏洞利用条件 Roundcube必须配置成使用PHP的mail()函数(如果没有指定SMTP,则是默认开启) PHP的mai ...

  7. 搭建centos7的开发环境3-Spark安装配置

    说起大数据开发,必然就会提到Spark,在这片博文中,我们就介绍一下Spark的安装和配置. 这是Centos7开发环境系列的第三篇,本篇的安装会基于之前的配置进行,有需要的请回复搭建centos7的 ...

  8. php判断图片是否存在的几种方法

    在我们日常的开发中,经常需要用到判断图片是否存在,存在则显示,不存在则显示默认图片,那么我们用到的判断有哪些呢?今天我们就来看下几个常用的方法: 1.getimagesize()函数 getimage ...

  9. STL-Deque 源码剖析

    G++ ,cygnus\cygwin-b20\include\g++\stl_deque.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Comp ...

  10. 微信小程序AES解密失败

    微信小程序分享群获取群id时后端接口返回"微信AES解密失败",后来定位到原因是服务端用于解密的session_key失效.用户获取到openID存在缓存后,就不会每次login获 ...