netty为啥要二次开发
很早之前就看过李林峰写的netty的书,但是感觉没有直接用到还是理解不够深入,现在的公司有两套自己基于Netty开发的系统,感觉才真正理解为啥要这么做
借用别人文章回顾下 https://www.cnblogs.com/carl10086/p/6183030.html
健壮性、功能、性能(预置了选多的编码功能,支持多种主流协议)、可定制性(通过ChannelHandler对通信框架进行灵活的扩展)和可扩展性
由于一个完整的包会被TCP拆分为多个包进行发送,也有可能将多个小的包封装成一个大包进行发送,所以会出现粘包、拆包的问题
例子将msg转换为Netty的ByteBuf对象,通过ByteBuf的readableBytes获取缓冲区可读的字节数,根据可读的字节数创建byte数组。将缓冲区的内容读取到byte数组中
问题的解决方法:定长,加开始结束符号,消息头包含长度消息体存储消息等
下面是解决了和未解决的代码,注释掉的是有包问题的代码,解决问题的主要是增加了两个Handler如下描述
package com.netty.helloword; 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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class Server {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024).option(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChildChannelHandler());
ChannelFuture f = serverBootstrap.bind(port).sync();
System.out.println("Server start");
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
System.out.println("release...");
}
} private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ServerHandler());
}
} public static void main(String[] args) throws Exception {
int port = 9998;
new Server().bind(port);
}
}
package com.netty.helloword; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; public class ServerHandler extends ChannelInboundHandlerAdapter {
private int counter; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf buf = (ByteBuf) msg;
// byte[] req = new byte[buf.readableBytes()];
// buf.readBytes(req);
// String body = new String(req, "UTF-8").substring(0, req.length
// - System.getProperty("line.separator").length());
String body = (String) msg;
System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
? new java.util.Date(System.currentTimeMillis()).toString()
: "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
package com.netty.helloword; 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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class Client {
public void connect(int port, String host) throws Exception {
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 LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ClientChannelHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
} private class ClientChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
} public static void main(String[] args) throws Exception {
new Client().connect(9998, "127.0.0.1");
}
}
package com.netty.helloword; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil; public class ClientHandler extends ChannelInboundHandlerAdapter { // private final ByteBuf firstMessage;
private int counter; private byte[] req; public ClientHandler() {
// byte[] req = "QUERY TIME ORDER".getBytes();
// firstMessage = Unpooled.buffer(req.length);
// firstMessage.writeBytes(req);
req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
} @Override
public void channelActive(ChannelHandlerContext ctx) {
// ctx.writeAndFlush(firstMessage);
ByteBuf message = null;
for (int i = 0; i < 100; i++) {
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf buf = (ByteBuf) msg;
// byte[] req = new byte[buf.readableBytes()];
// buf.readBytes(req);
// String body = new String(req, "UTF-8");
String body = (String) msg;
System.out.println("Now is : " + body);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
LineBasedFrameDecoder:
(1) 遍历ByteBuf中的可读字节,判断看是否有"\n"或者"\r\n",如果有,就以此为结束位置,从可读索引到结束位置区间的字节就组成了一行
(2) 是一个以换行符为结束标志的解码器,支持携带结束符或者不携带结束符2种方式,同时支持配置单行的最大长度。
(3) 超过单行最大长度直接抛异常
StringDecoder:
(1) 将接收的对象转换为字符串
(2) 继续调用后面的Handler
DelimiterBasedFrameDecoder:
自定义特殊符号分割,有2个参数,一个为单行最大长度,一个为自定义符号对象
FixedLengthFrameDecoder:
定长切分
------------------------------------------------------------------------------------------------------------------------------------------
提供了对于序列化反序列化的各种支持,因为各种序列化方式的速度和大小差距较大,所以可以根据需要提高性能
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder()); ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
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());
------------------------------------------------------------------------------------------------------------------------------------------
Http/JSON/Websocket服务器相关支持,上面文章中的例子已经非常清楚了
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder()); // 请求消息解码器
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));// 目的是将多个消息转换为单一的request或者response对象
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());//响应解码器
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());//目的是支持异步大文件传输()
------------------------------------------------------------------------------------------------------------------------------------------
为什么要开发私有协议栈
由于现代软件的复杂性,一个大型软件系统往往会被人为地拆分称为多个模块,另外随着移动互联网的兴起,网站的规模越来越大,业务功能越来越多,往往需要集群和分布式部署。模块之间的通信就需要进行跨节点通信。
传统的Java应用中节点通信的常用方式:
- rmi远程服务调用
- Java Socket + Java序列化
- RPC框架 Thrift、Apache的Avro等
- 利用标准的公有协议进行跨节点调用,例如HTTP+XML,Restful+JSON或WebService
下面使用Netty设计私有协议
除了链路层的物理连接外,还需要对请求和响应消息进行编解码。 在请求和应答之外,还需要控制和管理类指令,例如链路建立的握手信息,链路检测的心跳信息。这些功能组合到一起后,就会形成私有协议。
- 每个Netty节点(Netty进程)之间建立长连接,使用Netty协议进行通信。
- Netty节点没有客户端和服务端的区别,谁首先发起连接,谁就是客户端。
协议栈功能描述:
- 基于Netty的NIO通信框架,提供高性能的异步通信能力;
- 提供消息的编解码框架,实现POJO的序列化和反序列化
- 提供基于IP地址的白名单接入认证机制;
- 链路的有效性校验机制;
- 链路的断线重连机制;
具体步骤:
- Netty协议栈客户端发送握手请求信息,携带节点ID等有效身份认证信息;
- Netty协议服务端对握手请求消息进行合法性校验,包括节点ID有效性校验、节点重复登录校验和IP地址合法性校验,校验通过后,返回登录成功的握手应答消息;
- 链路建立成功之后,客户端发送业务消息;
- 链路成功之后,服务端发送心跳消息;
- 链路建立成功之后,客户端发送心跳消息;
- 链路建立成功之后,服务端发送业务消息;
- 服务端退出时,服务端关闭连接,客户端感知对方关闭连接后,被动关闭客户端连接。
netty为啥要二次开发的更多相关文章
- Netty入门二:开发第一个Netty应用程序
Netty入门二:开发第一个Netty应用程序 时间 2014-05-07 18:25:43 CSDN博客 原文 http://blog.csdn.net/suifeng3051/article/ ...
- Netty(二):Netty为啥去掉支持AIO?
匠心零度 转载请注明原创出处,谢谢! 疑惑 我们都知道bio nio 以及nio2(也就是aio),如果不是特别熟悉可以看看我之前写的网络 I/O模型,那么netty为什么还经常看到类似下面的这段代码 ...
- 基于Netty的私有协议栈的开发
基于Netty的私有协议栈的开发 书是人类进步的阶梯,每读一本书都使自己得以提升,以前看书都是看了就看了,当时感觉受益匪浅,时间一长就又还回到书本了!所以说,好记性不如烂笔头,以后每次看完一本书都写一 ...
- Jmeter二次开发——基于Java请求
简述 这近几年,越来越多非http的协议需要进行性能测试,包括不仅限于各类rpc.mq.缓存等.对于这些协议,市面上可能没有现成的工具可以直接使用,这个时候,我们可以自己动手,通过编写相应的JavaS ...
- 最佳实践 | 联通数科基于 DolphinScheduler 的二次开发
点击上方 蓝字关注我们 ✎ 编 者 按 数据时代下,井喷的数据量为电信行业带来新的挑战.面对每日数百 TB 的新增数据,稳定可靠的调度系统必不可少. 中国联通旗下的联通数字科技有限公司(以下简称&qu ...
- Navisworks API 简单二次开发 (自定义工具条)
在Navisworks软件运行的时候界面右侧有个工具条.比较方便.但是在二次开发的时候我不知道在Api那里调用.如果有网友知道请告诉我.谢谢. 我用就自己设置一个工具.界面比较丑!没有美工. 代码: ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》- 12.二次开发及应用
目 录 第十二章 二次开发及应用... 2 12.1 项目配制... 3 12.2 引用相关组件... 4 12.3 构建主程序... 5 ...
- OBS-Studio二次开发记录
OBS-Studio 是一款跨平台的,开源的视频直播客户端软件. 公司需要对他进行二次开发,开发的目的是使用它的录屏功能. 开发的要求是:定制全新的界面,所见即所得,window系统兼容要好. 开发步 ...
- 小猪cms微信二次开发之怎样分页
$db=D('Classify'); $zid=$db->where(array('id'=>$this->_GET('fid'),'token'=>$this->tok ...
随机推荐
- 电脑不能上网win7 解决办法
情况一览: 电脑连的是WIFI 手机能上,局域网其他电脑能上 电脑浏览器打不开网页 问题解决: //win+R 快捷键进入cmd 1.ipconfig 看电脑有没有ip 2.ipconfig nslo ...
- CentOS6系统防火墙开启、关闭、查看状态(转载)
原文:https://blog.csdn.net/aaron_80726/article/details/79027760 注意:要进入到~目录 也就是家目录下才能查看防火墙 进入家目录:cd ~ 关 ...
- 00008 - crontab定时执行任务命令详解
linux 系统则是由 cron (crond) 这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的.另 外, 由于使用者自己也可以设置计划任务,所以, ...
- Mysql 之权限体系
1,MySQL权限体系 MySQL 的权限体系大致分为5个层级: 全局层级: 全局权限适用于一个给定服务器中的所有数据库.这些权限存储在mysql.user表中.GRANT ALL ON .和REVO ...
- 使用CacheCloud管理Redis实例
转载来源:http://www.ywnds.com/?p=10610 一.CacheCloud是什么? 最近在使用CacheCloud管理Redis,所以简单说一下,这里主要说一下我碰到的问题.Cac ...
- Redis进阶实践之十三 Redis的Redis-trib.rb脚本文件使用详解
转载来源:http://www.cnblogs.com/PatrickLiu/p/8484784.html 一.简介 事先说明一下,本篇文章不涉及对redis-trib.rb源代码的分析,只是从使用的 ...
- 关于Vue实例的生命周期created和mounted的区别
什么是生命周期 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载等一系列过程,我们称这是Vue的生命周期.通俗说就是Vue实例从创建到销毁的过 ...
- es6基础(2)--解构赋值
//解构赋值 { let a,b,rest; [a,b]=[1,2]; console.log(a,b); } //数组解构赋值 { let a,b,rest; [a,b,...rest]=[1,2, ...
- gulp安装,淘宝镜像
命令:express -e ./ express表示安装express -e表示使用ejs作为模板 ./表示当前目录中 (使用上面的命令之前我们应该使用npm安装express框架 sudo npm ...
- glob获取指定目录下的东西+更改工作目录
一:不更改工作目录 import glob path = 'image/imgs/*.jpg' # 正则匹配 指定路径 file_path = glob.glob(path) # 即可获取所有jpg的 ...