在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接。由于微服务往对方发送信息的时候,所有的请求都是使用的同一个连接,这样就会产生粘包和拆包的问题。本文首先会对粘包和拆包问题进行描述,然后介绍其常用的解决方案,最后会对Netty提供的几种解决方案进行讲解。这里说明一下,由于oschina将“jie ma qi”认定为敏感文字,因而本文统一使用“解码一器”表示该含义
粘包和拆包 产生粘包和拆包问题的主要原因是,操作系统在发送TCP数据的时候,底层会有一个缓冲区,例如1024个字节大小,如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题;如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。如下图展示了粘包和拆包的一个示意图: 上图中演示了粘包和拆包的三种情况:
A和B两个包都刚好满足TCP缓冲区的大小,或者说其等待时间已经达到TCP等待时长,从而还是使用两个独立的包进行发送; A和B两次请求间隔时间内较短,并且数据包较小,因而合并为同一个包发送给服务端; B包比较大,因而将其拆分为两个包B_1和B_2进行发送,而这里由于拆分后的B_2比较小,其又与A包合并在一起发送。
常见解决方案 对于粘包和拆包问题,常见的解决方案有四种:
客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度; 客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包; 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息; 通过自定义协议进行粘包和拆包的处理。
Netty提供的粘包拆包解决方案 3.1 FixedLengthFrameDecoder 对于使用固定长度的粘包和拆包场景,可以使用FixedLengthFrameDecoder,该解码一器会每次读取固定长度的消息,如果当前读取到的消息不足指定长度,那么就会等待下一个消息到达后进行补足。其使用也比较简单,只需要在构造函数中指定每个消息的长度即可。这里需要注意的是,FixedLengthFrameDecoder只是一个解码一器,Netty也只提供了一个解码一器,这是因为对于解码是需要等待下一个包的进行补全的,代码相对复杂,而对于编码器,用户可以自行编写,因为编码时只需要将不足指定长度的部分进行补全即可。下面的示例中展示了如何使用FixedLengthFrameDecoder来进行粘包和拆包处理:
public class EchoServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 这里将FixedLengthFrameDecoder添加到pipeline中,指定长度为20
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
// 将前一步解码得到的数据转码为字符串
ch.pipeline().addLast(new StringDecoder());
// 这里FixedLengthFrameEncoder是我们自定义的,用于将长度不足20的消息进行补全空格
ch.pipeline().addLast(new FixedLengthFrameEncoder(20));
// 最终的数据处理
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer().bind(8080);
}
}
上面的pipeline中,对于入栈数据,这里主要添加了FixedLengthFrameDecoder和StringDecoder,前面一个用于处理固定长度的消息的粘包和拆包问题,第二个则是将处理之后的消息转换为字符串。最后由EchoServerHandler处理最终得到的数据,处理完成后,将处理得到的数据交由FixedLengthFrameEncoder处理,该编码器是我们自定义的实现,主要作用是将长度不足20的消息进行空格补全。下面是FixedLengthFrameEncoder的实现代码:
public class FixedLengthFrameEncoder extends MessageToByteEncoder {
private int length;
public FixedLengthFrameEncoder(int length) {
this.length = length;
}
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out)
throws Exception {
// 对于超过指定长度的消息,这里直接抛出异常
if (msg.length() > length) {
throw new UnsupportedOperationException(
"message length is too large, it's limited " + length);
}
// 如果长度不足,则进行补全
if (msg.length() < length) {
msg = addSpace(msg);
}
ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.getBytes()));
}
// 进行空格补全
private String addSpace(String msg) {
StringBuilder builder = new StringBuilder(msg);
for (int i = 0; i < length - msg.length(); i++) {
builder.append(" ");
}
return builder.toString();
}
}
这里FixedLengthFrameEncoder实现了decode()方法,在该方法中,主要是将消息长度不足20的消息进行空格补全。EchoServerHandler的作用主要是打印接收到的消息,然后发送响应给客户端:
public class EchoServerHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("server receives message: " + msg.trim());
ctx.writeAndFlush("hello client!");
}
}
对于客户端,其实现方式基本与服务端的使用方式类似,只是在最后进行消息发送的时候与服务端的处理方式不同。如下是客户端EchoClient的代码:
public class EchoClient {
public void connect(String host, int port) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 对服务端发送的消息进行粘包和拆包处理,由于服务端发送的消息已经进行了空格补全,
// 并且长度为20,因而这里指定的长度也为20
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
// 将粘包和拆包处理得到的消息转换为字符串
ch.pipeline().addLast(new StringDecoder());
// 对客户端发送的消息进行空格补全,保证其长度为20
ch.pipeline().addLast(new FixedLengthFrameEncoder(20));
// 客户端发送消息给服务端,并且处理服务端响应的消息
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoClient().connect("127.0.0.1", 8080);
}
}
对于客户端而言,其消息的处理流程其实与服务端是相似的,对于入站消息,需要对其进行粘包和拆包处理,然后将其转码为字符串,对于出站消息,则需要将长度不足20的消息进行空格补全。客户端与服务端处理的主要区别在于最后的消息处理handler不一样,也即这里的EchoClientHandler,如下是该handler的源码:
public class EchoClientHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("client receives message: " + msg.trim());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("hello server!");
}
}
这里客户端的处理主要是重写了channelActive()和channelRead0()两个方法,这两个方法的主要作用在于,channelActive()会在客户端连接上服务器时执行,也就是说,其连上服务器之后就会往服务器发送消息。而channelRead0()主要是在服务器发送响应给客户端时执行,这里主要是打印服务器的响应消息。对于服务端而言,前面我们我们可以看到,EchoServerHandler只重写了channelRead0()方法,这是因为服务器只需要等待客户端发送消息过来,然后在该方法中进行处理,处理完成后直接将响应发送给客户端。如下是分别启动服务端和客户端之后控制台打印的数据:
// server
server receives message: hello server!
// client
client receives message: hello client!
3.2 LineBasedFrameDecoder与DelimiterBasedFrameDecoder 对于通过分隔符进行粘包和拆包问题的处理,Netty提供了两个编解码的类,LineBasedFrameDecoder和DelimiterBasedFrameDecoder。这里LineBasedFrameDecoder的作用主要是通过换行符,即\n或者\r\n对数据进行处理;而DelimiterBasedFrameDecoder的作用则是通过用户指定的分隔符对数据进行粘包和拆包处理。同样的,这两个类都是解码一器类,而对于数据的编码,也即在每个数据包最后添加换行符或者指定分割符的部分需要用户自行进行处理。这里以DelimiterBasedFrameDecoder为例进行讲解,如下是EchoServer中使用该类的代码片段,其余部分与前面的例子中的完全一致:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
String delimiter = "_$";
// 将delimiter设置到DelimiterBasedFrameDecoder中,经过该解码一器进行处理之后,源数据将会
// 被按照_$进行分隔,这里1024指的是分隔的最大长度,即当读取到1024个字节的数据之后,若还是未
// 读取到分隔符,则舍弃当前数据段,因为其很有可能是由于码流紊乱造成的
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,
Unpooled.wrappedBuffer(delimiter.getBytes())));
// 将分隔之后的字节数据转换为字符串数据
ch.pipeline().addLast(new StringDecoder());
// 这是我们自定义的一个编码器,主要作用是在返回的响应数据最后添加分隔符
ch.pipeline().addLast(new DelimiterBasedFrameEncoder(delimiter));
// 最终处理数据并且返回响应的handler
ch.pipeline().addLast(new EchoServerHandler());
}
上面pipeline的设置中,添加的解码一器主要有DelimiterBasedFrameDecoder和StringDecoder,经过这两个处理器处理之后,接收到的字节流就会被分隔,并且转换为字符串数据,最终交由EchoServerHandler处理。这里DelimiterBasedFrameEncoder是我们自定义的编码器,其主要作用是在返回的响应数据之后添加分隔符。如下是该编码器的源码:
public class DelimiterBasedFrameEncoder extends MessageToByteEncoder {
private String delimiter;
public DelimiterBasedFrameEncoder(String delimiter) {
this.delimiter = delimiter;
}
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out)
throws Exception {
// 在响应的数据后面添加分隔符
ctx.writeAndFlush(Unpooled.wrappedBuffer((msg + delimiter).getBytes()));
}
}
对于客户端而言,这里的处理方式与服务端类似,其pipeline的添加方式如下:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
String delimiter = "_$";
// 对服务端返回的消息通过_$进行分隔,并且每次查找的最大大小为1024字节
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,
Unpooled.wrappedBuffer(delimiter.getBytes())));
// 将分隔之后的字节数据转换为字符串
ch.pipeline().addLast(new StringDecoder());
// 对客户端发送的数据进行编码,这里主要是在客户端发送的数据最后添加分隔符
ch.pipeline().addLast(new DelimiterBasedFrameEncoder(delimiter));
// 客户端发送数据给服务端,并且处理从服务端响应的数据
ch.pipeline().addLast(new EchoClientHandler());
}
这里客户端的处理方式与服务端基本一致,关于这里没展示的代码,其与示例一中的代码完全一致,这里则不予展示。
3.3 LengthFieldBasedFrameDecoder与LengthFieldPrepender 这里LengthFieldBasedFrameDecoder与LengthFieldPrepender需要配合起来使用,其实本质上来讲,这两者一个是解码,一个是编码的关系。它们处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。LengthFieldBasedFrameDecoder会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据;而LengthFieldPrepender则会在响应的数据前面添加指定的字节数据,这个字节数据中保存了当前消息体的整体字节数据长度。LengthFieldBasedFrameDecoder的解码过程如下图所示: 关于LengthFieldBasedFrameDecoder,这里需要对其构造函数参数进行介绍:
maxFrameLength:指定了每个包所能传递的最大数据包大小; lengthFieldOffset:指定了长度字段在字节码中的偏移量; lengthFieldLength:指定了长度字段所占用的字节长度; lengthAdjustment:对一些不仅包含有消息头和消息体的数据进行消息头的长度的调整,这样就可以只得到消息体的数据,这里的lengthAdjustment指定的就是消息头的长度; initialBytesToStrip:对于长度字段在消息头中间的情况,可以通过initialBytesToStrip忽略掉消息头以及长度字段占用的字节。 这里我们以json序列化为例对LengthFieldBasedFrameDecoder和LengthFieldPrepender的使用方式进行讲解。如下是EchoServer的源码:
public class EchoServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 这里将LengthFieldBasedFrameDecoder添加到pipeline的首位,因为其需要对接收到的数据
// 进行长度字段解码,这里也会对数据进行粘包和拆包处理
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
// LengthFieldPrepender是一个编码器,主要是在响应字节数据前面添加字节长度字段
ch.pipeline().addLast(new LengthFieldPrepender(2));
// 对经过粘包和拆包处理之后的数据进行json反序列化,从而得到User对象
ch.pipeline().addLast(new JsonDecoder());
// 对响应数据进行编码,主要是将User对象序列化为json
ch.pipeline().addLast(new JsonEncoder());
// 处理客户端的请求的数据,并且进行响应
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer().bind(8080);
}
}
这里EchoServer主要是在pipeline中添加了两个编码器和两个解码一器,编码器主要是负责将响应的User对象序列化为json对象,然后在其字节数组前面添加一个长度字段的字节数组;解码一器主要是对接收到的数据进行长度字段的解码,然后将其反序列化为一个User对象。下面是JsonDecoder的源码:
public class JsonDecoder extends MessageToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out)
throws Exception {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
User user = JSON.parseObject(new String(bytes, CharsetUtil.UTF_8), User.class);
out.add(user);
}
}
JsonDecoder首先从接收到的数据流中读取字节数组,然后将其反序列化为一个User对象。下面我们看看JsonEncoder的源码:
public class JsonEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, User user, ByteBuf buf)
throws Exception {
String json = JSON.toJSONString(user);
ctx.writeAndFlush(Unpooled.wrappedBuffer(json.getBytes()));
}
}
JsonEncoder将响应得到的User对象转换为一个json对象,然后写入响应中。对于EchoServerHandler,其主要作用就是接收客户端数据,并且进行响应,如下是其源码:
public class EchoServerHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, User user) throws Exception {
System.out.println("receive from client: " + user);
ctx.write(user);
}
}
对于客户端,其主要逻辑与服务端的基本类似,这里主要展示其pipeline的添加方式,以及最后发送请求,并且对服务器响应进行处理的过程:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new JsonDecoder());
ch.pipeline().addLast(new JsonEncoder());
ch.pipeline().addLast(new EchoClientHandler());
}
public class EchoClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.write(getUser());
}
private User getUser() {
User user = new User();
user.setAge(27);
user.setName("zhangxufeng");
return user;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, User user) throws Exception {
System.out.println("receive message from server: " + user);
}
}
这里客户端首先会在连接上服务器时,往服务器发送一个User对象数据,然后在接收到服务器响应之后,会打印服务器响应的数据。
3.4 自定义粘包与拆包器 对于粘包与拆包问题,其实前面三种基本上已经能够满足大多数情形了,但是对于一些更加复杂的协议,可能有一些定制化的需求。对于这些场景,其实本质上,我们也不需要手动从头开始写一份粘包与拆包处理器,而是通过继承LengthFieldBasedFrameDecoder和LengthFieldPrepender来实现粘包和拆包的处理。
如果用户确实需要不通过继承的方式实现自己的粘包和拆包处理器,这里可以通过实现MessageToByteEncoder和ByteToMessageDecoder来实现。这里MessageToByteEncoder的作用是将响应数据编码为一个ByteBuf对象,而ByteToMessageDecoder则是将接收到的ByteBuf数据转换为某个对象数据。通过实现这两个抽象类,用户就可以达到实现自定义粘包和拆包处理的目的。如下是这两个类及其抽象方法的声明:
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List out)
throws Exception;
}
public abstract class MessageToByteEncoder extends ChannelOutboundHandlerAdapter {
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out)
throws Exception;
}
小结 本文首先对粘包和拆包的问题原理进行描述,帮助读者理解粘包和拆包问题所在。然后对处理粘包和拆包的几种常用解决方案进行讲解。接着通过辅以示例的方式对Netty提供的几种解决粘包和拆包问题的解决方案进行了详细讲解。
觉得不错请点赞支持,欢迎留言或进我的个人群855801563领取【架构资料专题目合集90期】、【BATJTMD大厂JAVA面试真题1000+】,本群专用于学习交流技术、分享面试机会,拒绝广告,我也会在群内不定期答题、探讨。
转载于:https://blog.51cto.com/13981400/2366891
你可能感兴趣的:(Netty解决粘包和拆包问题的四种方案)
PostgreSQL 生产环境升级指南:pg_upgrade 快速完成版本升级!
pitt1997
数据库 学习笔记 PostgreSQL pg_upgrade 生产环境升级 PostgreSQL大版本升级
前言PostgreSQL的版本号由主要版本号和次要版本号组成。例如,在10.1中,10是主要版本,1是次要版本。关于更多版本的规划,请参考PostgreSQL版本路线图。版本号规则:PostgreSQL10及以后:版本号采用X.Y形式(如10.1,11.2),其中X为主要版本,Y为次要版本。PostgreSQL10之前:版本号采用X.Y.Z形式(如9.5.3),其中X.Y为主要版本(如9.5),Z
Java高频面试之集合-03
牛马baby
java 面试 开发语言
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝面试官:说说ArrayList和LinkedList的区别ArrayList与LinkedList的详细对比一、底层数据结构特性ArrayListLinkedList存储结构基于动态数组基于双向链表内存分配连续内存块非连续内存,节点分散存储元素访问通过索引直接寻址(时间复杂度O(1))需要遍历链表(时间复杂度O(n))插入/删除
C++ 学生成绩管理系统
非德77
c++ 算法 开发语言
一、项目背景与核心需求成绩管理系统是高校教学管理的重要工具,本系统采用C++面向对象编程实现,主要功能模块包括:学生信息管理(学号/姓名/3门课程成绩)成绩增删改查(CRUD)操作数据持久化存储统计分析与报表生成用户友好交互界面二、系统架构设计1.类结构设计采用经典的MVC分层思想:示意图如下:┌──────────────┐┌──────────────┐│Student││ScoreSyste
B/S架构和C/S架构概述与优缺点
此方konata
架构 php 开发语言
B/S架构(Browser/Server架构)定义B/S架构是一种通过浏览器访问服务器的架构模式。客户端(通常是浏览器)与服务器之间通过HTTP/HTTPS协议进行通信。优点易于部署和维护:客户端只需要安装浏览器,无需单独安装客户端软件。服务器端的更新和维护只需在一个地方进行,客户端自动生效。跨平台性:只要支持HTTP协议的浏览器,就可以访问应用程序,不受操作系统限制。提高了应用程序的可用性和灵活
介绍一下Qt中的动态属性
已是上好佳
qt 数据库 开发语言 c++
在Qt中,动态属性是一种强大且灵活的特性,它允许你在运行时为对象添加、修改和查询属性,而不需要在类的定义中预先声明这些属性。下面为你详细介绍Qt动态属性的相关内容:1.动态属性的基本概念在传统的C++类中,属性通常是在类的定义里通过成员变量来表示的,并且在编译时就已经确定。而Qt的动态属性打破了这种限制,它可以在程序运行期间为任何继承自QObject的对象添加额外的属性,这些属性以键值对的形式存储
HashMap源码解读
十五001
基础 哈希算法 散列表 算法
1.HashMap概述HashMap是基于哈希表的Map接口实现,允许空键和空值。它继承自AbstractMap,实现了Map、Cloneable和Serializable接口。2.底层数据结构在JDK1.8中,HashMap的底层数据结构由数组+链表+红黑树构成:数组:存储哈希表的节点(Node)。链表:解决哈希冲突,当多个键的哈希值相同或相近时,它们会被存储在同一个数组槽位的链表中。红黑树:当
Vim常用命令备忘
assaper
vim 编辑器 linux
文章目录一、Vim支持模式二、Vim常用命令1.光标移动2.文本操作3.查找置换4.保存退出5.多文件编辑6.多窗口编辑7.多标签编辑8.目录操作9.运行命令10.可视化操作11.其他命令一、Vim支持模式普通模式:打开文件时的默认模式,在其他模式下按Esc键都可返回到该模式。插入模式:在普通模式下按i/a/o键进入该模式,进行文本编辑操作。命令行模式:在普通模式下输入:后会进入该模式,在该模式下
168. Excel表列名称(JS实现)
PAT-python-zjw
剑指offer
1题目给定一个正整数,返回它在Excel表中相对应的列名称。例如,1->A2->B3->C…26->Z27->AA28->AB…示例1:输入:1输出:“A”示例2:输入:28输出:“AB”示例3:输入:701输出:“ZY”链接:https://leetcode-cn.com/problems/excel-sheet-column-title2思路这道题看起来挺简单,但实际做的时候要好好考虑一下索引
地址解析协议(ARP):深入理解网络的“地址翻译官”
leo·li
路由交换技术笔记 ARP 网络通信 IP与MAC 局域网 ARP欺骗 网络排错 协议原理
地址解析协议(ARP,AddressResolutionProtocol)是网络通信中的“幕后翻译”,负责在局域网中将IP地址转换为MAC地址。作为TCP/IP协议栈的基础组件,ARP在数据帧传输中起着关键作用。本文将从零开始,详细剖析ARP的原理、过程及应用,通过丰富的示例带你彻底掌握这一“地址翻译官”的工作奥秘。一、ARP的基本概念:IP与MAC的“桥梁”在局域网中,设备通信靠的是二层地址(M
IPsec+预共享密钥的IKE野蛮模式
leo·li
IPSec VPN H3C 路由交换 网络 路由器 网络协议
目标配置IPsec+预共享密钥的IKE野蛮模式步骤一、配置各接口IP地址步骤二、配置默认路由[RTB]iproute-static0.0.0.002.2.2.2步骤三:配置公网连接在SWA上配置DHCPServer。设置RTA从SWA动态获得IP地址和默认路由。[SWA]dhcpenable[SWA]dhcpserverip-pool1[SWA-dhcp-pool-1]network1.1.1.0
bootstrap row 之间的竖直方向的距离要怎么调整?
yzy85
问:我有两个rowa和rowb,我发现这两个row之间的竖直方向的距离靠的有点近,我想调整a和b之间的距离,row之前的水平距离可以通过col-md-offset*调整,那竖直方向上的距离要怎么调整,谢谢!答:1、目前我是通过直接加一个空的h1标签,来拉开距离,感觉有点不太rails.大家有更合适的方法吗?谢谢!2、可以自己在css中给top-offset的class写个margin,然后把这个c
vue让table表格滚动的功能代码
yzy85
vue.js javascript 前端
方法1:vue中在固定高度的容器中,表格内容需要滚动显示,代码如下:/***自动滚动*@param{divData}dom*@param{time}多久滚动一次默认50ms*/exportfunctioninfinitScroll(divData,time=50){divData.onmouseover=function(){clearInterval(t);//鼠标移入,停止滚动};divDat
Android wpa_supplicant源码分析--conf配置文件
水木无痕
http://blog.csdn.net/cuijiyue/article/details/514288351配置文件conf文件作为wpa_supplicant的配置文件,一般叫做wpa_supplicant.conf。其中存储着wpa_supplicant的运行参数和以保存的网络列表。conf文件的路径,通过启动wpa_supplicant时的–c参数传入,初始化过程中赋值到wpa_s->co
svn 通过127.0.01能访问 但通过公网IP不能访问,这是什么原因?
行思理
运维 Linux svn linux 防火墙
连接失败的提示如下1、SVN的启动方法方法一:svnserve-d-r/mnt/svn方法二:svnserve-d--listen-port3690-r/mnt/svn方法三:svnserve-d-r/mnt/svn--listen-host0.0.0.02、首先检查svn服务器是否启动方法一:netstat-tunlp|grepsvn演示如下如上状态,说明已启动方法二:svnserve--ver
SVN学习笔记
颜洛滨
开发工具 SVN 开发工具 版本管理
SVN学习笔记SVN背景知识SVN,全称Subversion,是一个开放源码的集中式版本控制系统,这里需要注意的一个点就是集中式,所谓的集中式,就是说,SVN管理的所有仓库都位于一个集中的服务器上,如下图所示SVN官网:SVN官网SVN安装:SVN支持多个平台,包括Windows,Mac,Linux等,官网上提供了详细的安装指南,这里我使用的是Centos6,对应的安装步骤如下首先使用svn--v
MySQL存储结构
胖虎是只mao
MySQL 数据库 mysql 哈希表 数组 二叉树
背景:为什么数据库存储使用b+树而不是二叉树,因为二叉树树高过高,每次查询都需要访问过多节点,即访问数据块过多,而从磁盘随机读取数据块过于耗时。1.表存储结构单位:表>段>区>页>行在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说存储空间的基本单位是页。一个页就是一棵树B+树的节点,数据库I/O操作的最小单位是页,与数据库相关的内容都会存储在页的结构里。2.B+树索引结构
简单分享下Python数据可视化
小软件大世界
信息可视化 python 开发语言
在数据科学的广阔天地里,数据可视化是不可或缺的一环,它让复杂的数据变得易于理解。对于Python初学者而言,掌握Matplotlib和Seaborn这两个强大的库,无疑能让你的分析报告更加生动有趣。本文专为渴望提升数据可视化技能的你设计,通过15个实用技巧,带你从基础走向高级,探索数据背后的精彩故事。1.基础条形图-简单入手Matplotlib示例:import matplotlib.pyplot
168. Excel表列名称 题解
睡的饱
c++ leetcode 开发语言
168.Excel表列名称题解给你一个整数columnNumber,返回它在Excel表中相对应的列名称。例如:A->1B->2C->3...Z->26AA->27AB->28...示例1:输入:columnNumber=1输出:"A"示例2:输入:columnNumber=28输出:"AB"示例3:输入:columnNumber=701输出:"ZY"示例4:输入:columnNumber=214
网络安全主动防御技术与应用
坚持可信
信息安全 web安全 php 安全
入侵阻断技术(IntrusionPreventionTechnologies)是指通过检测并阻止网络和系统中的恶意活动,防止攻击者利用系统漏洞或其他手段进行破坏或未经授权访问的技术和方法。这些技术通常结合入侵检测(IntrusionDetection)功能,通过实时监控和响应机制,有效防御各种类型的网络攻击。以下是入侵阻断技术的详细介绍及其应用。一、入侵阻断技术入侵检测和防御系统(IDS/IPS)
168. Excel表列名称——【Leetcode每日一题】
零點零壹
LeetCode excel leetcode 算法
168.Excel表列名称给你一个整数columnNumber,返回它在Excel表中相对应的列名称。例如:A->1B->2C->3…Z->26AA->27AB->28…示例1:输入:columnNumber=1输出:“A”示例2:输入:columnNumber=28输出:“AB”示例3:输入:columnNumber=701输出:“ZY”示例4:输入:columnNumber=214748364
matlab中logm函数的应用,matlab 各种 对数函数 用法以及实例是什么
刘惠昌
在MATLAB中输入对数函数主要分为以下两种类型:一、直接型以e、2或者是10为底的对数的话,直接输入:y=log(x),y=log2(x),y=log10(x)。例如,a1=log(2.7183);a2=log2(2);a3=log10(10),其结果如下图:二、转换性如果需要求的对数函在MATLAB运算当中,我们常常需要求对数,在编写M文件的过程中,我们也需要表示对数,下面我就通过一些示例介绍
虚拟机 centos svn服务器搭建,centos下搭建svn+apache服务器
weixin_39757893
虚拟机 centos svn服务器搭建
安装软件包#yuminstallhttpd#yuminstallmod_dav_svn#yuminstallsubversion2.验证安装#httpd-version结果显示:前往/etc/httpd/modules/下,检查是否包含mod_dav_svn.so和mod_authz_svn.so,如果有,mod_dav_svn安装成功。#svnserve--version结果显示:安装svn成功
轻量级python编辑器 内存_vscode-轻量级实用编辑器
weixin_39557402
轻量级python编辑器 内存
前言:经网友推荐,下载vscode,发现速度确实快,度娘看了下,是微软抽调的一小波人做的。这样就不担心windows平台插件支持了。js,python都支持高亮,本身自带插件也都实用。自带控制台,终端,emmet格式插件,图标插件,小地图插件。占用内存少,推荐!先整理部分觉得不错的插件,体验一段时间再修改一、常用插件1.vscode-icon让vscode的文件夹目录添加上对应的图标注:安装好如果
linux svn创建资源库,CentOS 7搭建svn服务
你的麦克疯
linux svn创建资源库
一、背景自己平时有记笔记的习惯,回到宿舍笔记就同步不了。打算入手下很火的笔记软件,用着觉得不顺手,目录一多查找不方便,没有英文首字母定位快,想想决定用svn同步,整理出来分享给大家。二、搭建svn服务1、安装subversionyum-yinstallsubversion2、创建版本库目录,为创建版本库提供存放位置mkdir-p/home/svn/svnrepos3、创建svn版本库,mynote
Java面试专业技能怎么写_Java面试——专业技能
靳天羽
Java面试专业技能怎么写
目录一、简单讲下Java的跨平台原理二、装箱与拆箱三、实现一个拷贝文件的工具类使用字节流还是字符流四、介绍下线程池五、JSP和Servlet有哪些相同点和不同点六、简单介绍一下关系数据库三范式七、Mysql数据库的默认的最大连接数八、说一下Mysql和Oracle的分页九、简单讲一下数据库的触发器的使用场景十、简单讲一下数据库的存储过程的使用场景十一、简单介绍一下Activiti十二、编写一个Se
基于统信UOS的Kivy移动应用打包
Botiway
移动APP python Kivy
将Kivy应用打包为移动应用(Android或iOS)是发布应用的关键步骤。Kivy提供了多种工具来简化打包过程,其中最常用的是Buildozer(用于Android)和Kivy-iOS(用于iOS)。以下是详细的打包指南。1.打包为Android应用使用Buildozer可以将Kivy应用打包为AndroidAPK文件。1.1安装Buildozer首先,确保已安装Buildozer:pip3in
linux | Vim 命令快捷操作
斐夷所非
Linux linux Vim
注:本文为过去的“vim使用笔记”。跳转命令跳转命令#:向前查找光标当前所在单词,并跳转到该单词的上一个出现位置。*:向后查找光标当前所在单词,并跳转到该单词的下一个出现位置。行内跳转0:跳转到当前行的行首。[Home]$:跳转到当前行的行尾。[End]^:跳转到当前行的第一个非空字符处。g_:跳转到行尾最后一个非空白字符。|n:跳转到当前行的第n列(例如:|10跳转到第10列)。文件内跳转gg:
高斯溅射融合之路(一)- webgl渲染3d gaussian splatting
山海鲸可视化
数字孪生 GIS系统 webgl 数字孪生 GIS 高斯泼溅 AI重构
大家好,我是山海鲸的技术负责人。之前已经写了一个GIS融合系列。其实CesiumJS的整合有相当的难度,同时也有很多方面的工作,很难在几篇文章内写完,整个山海鲸团队也是投入了接近两年的时间,才把周边整套工具链进行了完善,后续有新的内容也会持续分享,隔壁系列传送门:GIS融合之路一坑未平,一坑又起。去年年末,我们的AI合作伙伴突然给山海鲸技术团队丢来了一个新技术-3DGaussianSplattin
如何打造多种风格的三维地图?2月27日直播讲解演示
山海鲸可视化
数字孪生 GIS系统 数字孪生 数据可视化 可视化大屏 GIS 三维地图
欢迎扫码图片中的二维码订阅直播,也可以在Bilibili关注“山海鲸可视化”,我们将于同一时间进行直播。
171. Excel 表列序号
冱洇
力扣刷题记录 excel leetcode 算法
Excel表列序号题目描述尝试做法推荐做法题目描述给你一个字符串columnTitle,表示Excel表格中的列名称。返回该列名称对应的列序号。例如:A->1B->2C->3…Z->26AA->27AB->28…示例1:输入:columnTitle=“A”输出:1示例2:输入:columnTitle=“AB”输出:28示例3:输入:columnTitle=“ZY”输出:701提示:1=0;i--)
戴尔笔记本win8系统改装win7系统
sophia天雪
win7 戴尔 改装系统 win8
戴尔win8 系统改装win7 系统详述
第一步:使用U盘制作虚拟光驱:
1)下载安装UltraISO:注册码可以在网上搜索。
2)启动UltraISO,点击“文件”—》“打开”按钮,打开已经准备好的ISO镜像文
BeanUtils.copyProperties使用笔记
bylijinnan
java
BeanUtils.copyProperties VS PropertyUtils.copyProperties
两者最大的区别是:
BeanUtils.copyProperties会进行类型转换,而PropertyUtils.copyProperties不会。
既然进行了类型转换,那BeanUtils.copyProperties的速度比不上PropertyUtils.copyProp
MyEclipse中文乱码问题
0624chenhong
MyEclipse
一、设置新建常见文件的默认编码格式,也就是文件保存的格式。
在不对MyEclipse进行设置的时候,默认保存文件的编码,一般跟简体中文操作系统(如windows2000,windowsXP)的编码一致,即GBK。
在简体中文系统下,ANSI 编码代表 GBK编码;在日文操作系统下,ANSI 编码代表 JIS 编码。
Window-->Preferences-->General -
发送邮件
不懂事的小屁孩
send email
import org.apache.commons.mail.EmailAttachment;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.commons.mail.MultiPartEmail;
动画合集
换个号韩国红果果
html css
动画 指一种样式变为另一种样式 keyframes应当始终定义0 100 过程
1 transition 制作鼠标滑过图片时的放大效果
css
.wrap{
width: 340px;height: 340px;
position: absolute;
top: 30%;
left: 20%;
overflow: hidden;
bor
网络最常见的攻击方式竟然是SQL注入
蓝儿唯美
sql注入
NTT研究表明,尽管SQL注入(SQLi)型攻击记录详尽且为人熟知,但目前网络应用程序仍然是SQLi攻击的重灾区。
信息安全和风险管理公司NTTCom Security发布的《2015全球智能威胁风险报告》表明,目前黑客攻击网络应用程序方式中最流行的,要数SQLi攻击。报告对去年发生的60亿攻击 行为进行分析,指出SQLi攻击是最常见的网络应用程序攻击方式。全球网络应用程序攻击中,SQLi攻击占
java笔记2
a-john
java
类的封装:
1,java中,对象就是一个封装体。封装是把对象的属性和服务结合成一个独立的的单位。并尽可能隐藏对象的内部细节(尤其是私有数据)
2,目的:使对象以外的部分不能随意存取对象的内部数据(如属性),从而使软件错误能够局部化,减少差错和排错的难度。
3,简单来说,“隐藏属性、方法或实现细节的过程”称为——封装。
4,封装的特性:
4.1设置
[Andengine]Error:can't creat bitmap form path “gfx/xxx.xxx”
aijuans
学习Android遇到的错误
最开始遇到这个错误是很早以前了,以前也没注意,只当是一个不理解的bug,因为所有的texture,textureregion都没有问题,但是就是提示错误。
昨天和美工要图片,本来是要背景透明的png格式,可是她却给了我一个jpg的。说明了之后她说没法改,因为没有png这个保存选项。
我就看了一下,和她要了psd的文件,还好我有一点
自己写的一个繁体到简体的转换程序
asialee
java 转换 繁体 filter 简体
今天调研一个任务,基于java的filter实现繁体到简体的转换,于是写了一个demo,给各位博友奉上,欢迎批评指正。
实现的思路是重载request的调取参数的几个方法,然后做下转换。
android意图和意图监听器技术
百合不是茶
android 显示意图 隐式意图 意图监听器
Intent是在activity之间传递数据;Intent的传递分为显示传递和隐式传递
显式意图:调用Intent.setComponent() 或 Intent.setClassName() 或 Intent.setClass()方法明确指定了组件名的Intent为显式意图,显式意图明确指定了Intent应该传递给哪个组件。
隐式意图;不指明调用的名称,根据设
spring3中新增的@value注解
bijian1013
java spring @Value
在spring 3.0中,可以通过使用@value,对一些如xxx.properties文件中的文件,进行键值对的注入,例子如下:
1.首先在applicationContext.xml中加入:
<beans xmlns="http://www.springframework.
Jboss启用CXF日志
sunjing
log jboss CXF
1. 在standalone.xml配置文件中添加system-properties:
<system-properties> <property name="org.apache.cxf.logging.enabled" value=&
【Hadoop三】Centos7_x86_64部署Hadoop集群之编译Hadoop源代码
bit1129
centos
编译必需的软件
Firebugs3.0.0
Maven3.2.3
Ant
JDK1.7.0_67
protobuf-2.5.0
Hadoop 2.5.2源码包
Firebugs3.0.0
http://sourceforge.jp/projects/sfnet_findbug
struts2验证框架的使用和扩展
白糖_
框架 xml bean struts 正则表达式
struts2能够对前台提交的表单数据进行输入有效性校验,通常有两种方式:
1、在Action类中通过validatexx方法验证,这种方式很简单,在此不再赘述;
2、通过编写xx-validation.xml文件执行表单验证,当用户提交表单请求后,struts会优先执行xml文件,如果校验不通过是不会让请求访问指定action的。
本文介绍一下struts2通过xml文件进行校验的方法并说
记录-感悟
braveCS
感悟
再翻翻以前写的感悟,有时会发现自己很幼稚,也会让自己找回初心。
2015-1-11 1. 能在工作之余学习感兴趣的东西已经很幸福了;
2. 要改变自己,不能这样一直在原来区域,要突破安全区舒适区,才能提高自己,往好的方面发展;
3. 多反省多思考;要会用工具,而不是变成工具的奴隶;
4. 一天内集中一个定长时间段看最新资讯和偏流式博
编程之美-数组中最长递增子序列
bylijinnan
编程之美
import java.util.Arrays;
import java.util.Random;
public class LongestAccendingSubSequence {
/**
* 编程之美 数组中最长递增子序列
* 书上的解法容易理解
* 另一方法书上没有提到的是,可以将数组排序(由小到大)得到新的数组,
* 然后求排序后的数组与原数
读书笔记5
chengxuyuancsdn
重复提交 struts2的token验证
1、重复提交
2、struts2的token验证
3、用response返回xml时的注意
1、重复提交
(1)应用场景
(1-1)点击提交按钮两次。
(1-2)使用浏览器后退按钮重复之前的操作,导致重复提交表单。
(1-3)刷新页面
(1-4)使用浏览器历史记录重复提交表单。
(1-5)浏览器重复的 HTTP 请求。
(2)解决方法
(2-1)禁掉提交按钮
(2-2)
[时空与探索]全球联合进行第二次费城实验的可能性
comsci
二次世界大战前后,由爱因斯坦参加的一次在海军舰艇上进行的物理学实验 -费城实验
至今给我们大家留下很多迷团.....
关于费城实验的详细过程,大家可以在网络上搜索一下,我这里就不详细描述了
在这里,我的意思是,现在
easy connect 之 ORA-12154: TNS: 无法解析指定的连接标识符
daizj
oracle ORA-12154
用easy connect连接出现“tns无法解析指定的连接标示符”的错误,如下:
C:\Users\Administrator>sqlplus username/pwd@192.168.0.5:1521/orcl
SQL*Plus: Release 10.2.0.1.0 – Production on 星期一 5月 21 18:16:20 2012
Copyright (c) 198
简单排序:归并排序
dieslrae
归并排序
public void mergeSort(int[] array){
int temp = array.length/2;
if(temp == 0){
return;
}
int[] a = new int[temp];
int
C语言中字符串的\0和空格
dcj3sjt126com
c
\0 为字符串结束符,比如说:
abcd (空格)cdefg;
存入数组时,空格作为一个字符占有一个字节的空间,我们
解决Composer国内速度慢的办法
dcj3sjt126com
Composer
用法:
有两种方式启用本镜像服务:
1 将以下配置信息添加到 Composer 的配置文件 config.json 中(系统全局配置)。见“例1”
2 将以下配置信息添加到你的项目的 composer.json 文件中(针对单个项目配置)。见“例2”
为了避免安装包的时候都要执行两次查询,切记要添加禁用 packagist 的设置,如下 1 2 3 4 5
高效可伸缩的结果缓存
shuizhaosi888
高效可伸缩的结果缓存
/**
* 要执行的算法,返回结果v
*/
public interface Computable<A, V> {
public V comput(final A arg);
}
/**
* 用于缓存数据
*/
public class Memoizer<A, V> implements Computable<A,
三点定位的算法
haoningabc
c 算法
三点定位,
已知a,b,c三个顶点的x,y坐标
和三个点都z坐标的距离,la,lb,lc
求z点的坐标
原理就是围绕a,b,c 三个点画圆,三个圆焦点的部分就是所求
但是,由于三个点的距离可能不准,不一定会有结果,
所以是三个圆环的焦点,环的宽度开始为0,没有取到则加1
运行
gcc -lm test.c
test.c代码如下
#include "stdi
epoll使用详解
jimmee
c linux 服务端编程 epoll
epoll - I/O event notification facility在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linu
Hibernate对Enum的映射的基本使用方法
linzx0212
enum Hibernate
枚举
/**
* 性别枚举
*/
public enum Gender {
MALE(0), FEMALE(1), OTHER(2);
private Gender(int i) {
this.i = i;
}
private int i;
public int getI
第10章 高级事件(下)
onestopweb
事件
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/
孙子兵法
roadrunners
孙子 兵法
始计第一
孙子曰:
兵者,国之大事,死生之地,存亡之道,不可不察也。
故经之以五事,校之以计,而索其情:一曰道,二曰天,三曰地,四曰将,五
曰法。道者,令民于上同意,可与之死,可与之生,而不危也;天者,阴阳、寒暑
、时制也;地者,远近、险易、广狭、死生也;将者,智、信、仁、勇、严也;法
者,曲制、官道、主用也。凡此五者,将莫不闻,知之者胜,不知之者不胜。故校
之以计,而索其情,曰
MySQL双向复制
tomcat_oracle
mysql
本文包括:
主机配置
从机配置
建立主-从复制
建立双向复制
背景
按照以下简单的步骤:
参考一下:
在机器A配置主机(192.168.1.30)
在机器B配置从机(192.168.1.29)
我们可以使用下面的步骤来实现这一点
步骤1:机器A设置主机
在主机中打开配置文件 ,
zoj 3822 Domination(dp)
阿尔萨斯
Mina
题目链接:zoj 3822 Domination
题目大意:给定一个N∗M的棋盘,每次任选一个位置放置一枚棋子,直到每行每列上都至少有一枚棋子,问放置棋子个数的期望。
解题思路:大白书上概率那一张有一道类似的题目,但是因为时间比较久了,还是稍微想了一下。dp[i][j][k]表示i行j列上均有至少一枚棋子,并且消耗k步的概率(k≤i∗j),因为放置在i+1~n上等价与放在i+1行上,同理