深入浅出Netty通讯协议支持解析:第9篇教程

更新:11-18 民间故事 我要投稿 纠错 投诉

其实深入浅出Netty通讯协议支持解析:第9篇教程的问题并不复杂,但是又很多的朋友都不太了解,因此呢,今天小编就来为大家分享深入浅出Netty通讯协议支持解析:第9篇教程的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!

第一种方法使用自定义协议。传输消息时,消息的前几位(如2位)是自定义的(如AB)。解码器解析时,前两位是AB,表示协议类型。CD是一种协议类型。该方法没有使用protobuf,而是直接使用Netty的自定义协议来解决问题。第二种方法是使用protobuf实现的,它实际上规定了消息是如何定义的。因为netty本身,客户端和服务器建立TCP连接,一方必须确定另一方发送的对象类型。

Protocol Buffers实现netty的多种传输协议

我们知道要使用Protocol Buffers首先要定义一个.proto文件

定义最外层消息。最外面的消息(MyMessage) 包含所有已传递的消息类型。所有消息类型都嵌套在最外面的消息类型中。每次投递都会投递特定的消息类型(以最外层消息类型的枚举类型传递)

语法="proto2";

com.zhihao.miao.netty.sixtheexample 包;

选项optimize_for=SPEED;

option java_package="com.zhihao.miao.netty.seventhexample";

选项java_outer_classname="MyDataInfo";

消息我的消息{

枚举数据类型{

人员类型=1;

狗类型=2;

猫类型=3;

}

所需数据类型data_type=1;

//oneof表示:如果有多个可选字段,某一时刻只能设置一个值,这样可以节省内存空间。

其中一个数据体{

人人=2;

狗狗=3;

猫猫=4;

}

}

给人们留言{

可选字符串名称=1;

可选int32 年龄=2;

可选字符串地址=3;

}

消息狗{

可选字符串名称=1;

可选字符串年龄=2;

}

消息猫{

可选字符串名称=1;

可选字符串城市=2;

}使用编译器编译生成代码

protoc --java_out=src/main/java src/protobuf/People.proto 关于proto协议中Oneof的含义,如果有多个可选字段,某一时刻只能设置一个值,官方链接,生成MyDataInfo类,类代码太多,这里就不贴了。

服务器代码:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.bootstrap.ServerBootstrap;

导入io.netty.channel.ChannelFuture;

导入io.netty.channel.EventLoopGroup;

导入io.netty.channel.nio.NioEventLoopGroup;

导入io.netty.channel.socket.nio.NioServerSocketChannel;

导入io.netty.handler.logging.LogLevel;

导入io.netty.handler.logging.LoggingHandler;

公共类测试服务器{

公共静态无效主(字符串[] args)抛出异常{

EventLoopGroup bossGroup=new NioEventLoopGroup();

EventLoopGroupworkerGroup=new NioEventLoopGroup();

尝试{

ServerBootstrap serverBootstrap=new ServerBootstrap();

serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)

.handler(new LoggingHandler(LogLevel.INFO))

.childHandler(new TestServerInitializer());

ChannelFuturechannelFuture=serverBootstrap.bind(8888).sync();

ChannelFuture.channel().closeFuture().sync();

}最后{

bossGroup.shutdownGracely();

优雅地关闭();

}

}

}服务器初始化链接:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.channel.ChannelInitializer;

导入io.netty.channel.ChannelPipeline;

导入io.netty.channel.socket.SocketChannel;

导入io.netty.handler.codec.protobuf.ProtobufDecoder;

导入io.netty.handler.codec.protobuf.ProtobufEncoder;

导入io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;

导入io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

公共类TestServerInitializer 扩展ChannelInitializer{

@覆盖

protected void initChannel(SocketChannel ch) 抛出异常{

ChannelPipeline 管道=ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());

//使用最外层的消息实例

pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));

pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());

pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new TestServerHandler());

}

}其实实现的关键就在于此,使用MyDataInfo.MyMessage实数列(MyDataInfo.MyMessage是枚举类型),而我们定义的三个对象恰好是它的枚举对象。

自定义服务器端Handler根据通道中传入的数据的不同dataType值来解析具体类型:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.channel.ChannelHandlerContext;

导入io.netty.channel.SimpleChannelInboundHandler;

公共类TestServerHandler 扩展SimpleChannelInboundHandler{

@覆盖

protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) 抛出异常{

MyDataInfo.MyMessage.DataType dataType=msg.getDataType();

if(dataType==MyDataInfo.MyMessage.DataType.PeopleType){

MyDataInfo.People people=msg.getPeople();

System.out.println(people.getName());

System.out.println(people.getAge());

System.out.println(people.getAddress());

}否则if(dataType==MyDataInfo.MyMessage.DataType.DogType){

MyDataInfo.Dog 狗=msg.getDog();

System.out.println(dog.getName());

System.out.println(dog.getAge());

}否则if(dataType==MyDataInfo.MyMessage.DataType.CatType){

MyDataInfo.Cat cat=msg.getCat();

System.out.println(cat.getName());

System.out.println(cat.getCity());

}

}

}客户端代码:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.bootstrap.Bootstrap;

导入io.netty.channel.ChannelFuture;

导入io.netty.channel.EventLoopGroup;

导入io.netty.channel.nio.NioEventLoopGroup;

导入io.netty.channel.socket.nio.NioSocketChannel;

公共类测试客户端{

公共静态无效主(字符串[] args)抛出异常{

EventLoopGroup eventLoopGroup=new NioEventLoopGroup();

尝试{

Bootstrap bootstrap=new Bootstrap();

bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)

.handler(new TestClientInitializer());

ChannelFuturechannelFuture=bootstrap.connect("localhost",8888).sync();

ChannelFuture.channel().closeFuture().sync();

}最后{

eventLoopGroup.shutdownGraceively();

}

}

}客户端初始化链接:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.channel.ChannelInitializer;

导入io.netty.channel.ChannelPipeline;

导入io.netty.channel.socket.SocketChannel;

导入io.netty.handler.codec.protobuf.ProtobufDecoder;

导入io.netty.handler.codec.protobuf.ProtobufEncoder;

导入io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;

导入io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

公共类TestClientInitializer 扩展ChannelInitializer{

@覆盖

protected void initChannel(SocketChannel ch) 抛出异常{

ChannelPipeline 管道=ch.pipeline();

pipeline.addLast(new ProtobufVarint32FrameDecoder());

//使用最外层的消息实例

pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));

pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());

pipeline.addLast(new ProtobufEncoder());

pipeline.addLast(new TestClientHandler());

}

自定义处理器端的handler,随机发送不同协议的数据:

com.zhihao.miao.netty.seventheexample 包;

导入io.netty.channel.ChannelHandlerContext;

导入io.netty.channel.SimpleChannelInboundHandler;

导入java.util.Random;

公共类TestClientHandler 扩展SimpleChannelInboundHandler{

@覆盖

protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) 抛出异常{

}

//客户端向服务器发送数据

公共无效channelActive(ChannelHandlerContext ctx)抛出异常{

int randomInt=new Random().nextInt(3);

MyDataInfo.MyMessage myMessage=null;

if(0==randomInt){

myMessage=MyDataInfo.MyMessage.newBuilder()。

setDataType(MyDataInfo.MyMessage.DataType.PeopleType)。

setPeople(MyDataInfo.People.newBuilder().setName("张三").

setAddress("上海").setAge(26).build()).build();

}否则if(1==randomInt){

myMessage=MyDataInfo.MyMessage.newBuilder()。

setDataType(MyDataInfo.MyMessage.DataType.DogType)。

setDog(MyDataInfo.Dog.newBuilder().setName("旺财")

.setAge("2").build()).build();

}否则if(2==randomInt){

myMessage=MyDataInfo.MyMessage.newBuilder()。

setDataType(MyDataInfo.MyMessage.DataType.CatType)。

setCat(MyDataInfo.Cat.newBuilder().setName("汤姆")

.setCity("上海").build()).build();

}

ctx.channel().writeAndFlush(myMessage);

}

}启动服务器,然后启动客户端并执行几次。服务器控制台显示:

2017 年7 月5 日10:10:37 下午io.netty.handler.logging.LoggingHandler 通道读取

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] READ: [id:0x82a26e9f,L:/127。 0.0.1:8888-R:/127.0.0.1:51777]

2017 年7 月5 日10:10:37 下午io.netty.handler.logging.LoggingHandler channelReadComplete

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] 读取完成

汤姆

上海

2017 年7 月5 日10:11:38 下午io.netty.handler.logging.LoggingHandler 通道读取

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] READ: [id:0x128da3e7,L:/127.0 。 0.1:8888-R:/127.0.0.1:52049]

2017 年7 月5 日10:11:38 下午io.netty.handler.logging.LoggingHandler channelReadComplete

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] 读取完成

张三

26

上海

2017 年7 月5 日10:11:49 下午io.netty.handler.logging.LoggingHandler channelRead

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] READ: [id:0xa8220c73,L:/127。 0。 0.1:8888-R:/127.0.0.1:52097]

2017 年7 月5 日10:11:49 下午io.netty.handler.logging.LoggingHandler channelReadComplete

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] 读取完成

汤姆

上海

2017 年7 月5 日10:11:55 下午io.netty.handler.logging.LoggingHandler 通道读取

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] READ: [id:0x9ac52ec1,L:/127.0。 0.1:8888-R:/127.0.0.1:52125]

2017 年7 月5 日10:11:55 下午io.netty.handler.logging.LoggingHandler channelReadComplete

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] 读取完成

张三

26

上海

2017 年7 月5 日10:12:07 下午io.netty.handler.logging.LoggingHandler 通道读取

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] READ: [id:0x797d03b6,L:/127。 0.0.1:8888-R:/127.0.0.1:52178]

2017 年7 月5 日10:12:07 下午io.netty.handler.logging.LoggingHandler channelReadComplete

消息: [id:0xd5f957bd,L:/0:0:0:0:0:0:0:0:8888] 读取完成

繁荣

2

使用netty实现多种传输协议

类似官网的demo。我写了很久,在写这个demo之前也参考了官网。我对netty的理解又加深了:

三个协议实体类:

人员协议

包com.zhihao.miao.test.day10;

公共类人{

私有字符串用户名;

普里

vate int age; //get set方法 }Dog协议 package com.zhihao.miao.test.day10; public class Dog { private String name; private String age; //get set方法 }Cat协议 package com.zhihao.miao.test.day10; public class Cat { private String name; private String city; //get set方法 }服务端: import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class MultiServer { public static void main(String args[]) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); 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 ServerChannelInitializer()); // 绑定对应的端口号,并启动开始监听端口上的连接 Channel ch = serverBootstrap.bind(8899).sync().channel(); // 等待关闭,同步端口 ch.closeFuture().sync(); } }服务器端初始化lInitializer import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; public class ServerChannelInitializer extends ChannelInitializer{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //解析handler pipeline.addLast(new ServlerDecoder()); pipeline.addLast(new TestServerHandler()); } }服务端解码器Handler,如果解析的位置数据是0则按照 Person协议进行解码,如果传递的位置数据是1,则按照Dog协议进行解码,如果传递的位置数据是2,则按照Cat协议进行解码: public class ServlerDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, Listout) throws Exception { int flag = in.readInt(); if(flag == 0){ int usernamelength = in.readInt(); byte[] usernamebys = new byte[usernamelength]; in.readBytes(usernamebys); String username = new String(usernamebys); int age = in.readInt(); Person pserson = new Person(); pserson.setUsername(username); pserson.setAge(age); out.add(pserson); } if(flag ==1){ int namelength =in.readInt(); byte[] namebys = new byte[namelength]; in.readBytes(namebys); String name = new String(namebys); byte[] agebys = new byte[in.readableBytes()]; in.readBytes(agebys); String age = new String(agebys); Dog dog = new Dog(); dog.setName(name); dog.setAge(age); out.add(dog); } if(flag ==2){ int namelength = in.readInt(); byte[] nameByte = new byte[namelength]; in.readBytes(nameByte); String name = new String(nameByte); byte[] colorbys = new byte[in.readableBytes()]; in.readBytes(colorbys); String color = new String(colorbys); Cat cat = new Cat(); cat.setName(name); cat.setColor(color); out.add(cat); } }自定义服务器端Handler: import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class TestServerHandler extends SimpleChannelInboundHandler{ @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof Person){ System.out.println(((Person) msg).getUsername()); System.out.println(((Person) msg).getAge()); } if(msg instanceof Dog){ System.out.println(((Dog) msg).getName()); System.out.println(((Dog) msg).getAge()); } if(msg instanceof Cat){ System.out.println(((Cat) msg).getName());

System.out.println(((Cat) msg).getColor()); } } }客户端: import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; public class MultiClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class).handler(new ClientChannelInitializer()); // Start the connection attempt. Channel ch = b.connect("127.0.0.1", 8899).sync().channel(); ch.flush(); } }客户端初始化Initializer import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import java.util.Random; public class ClientChannelInitializer extends ChannelInitializer{ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); int randomInt = new Random().nextInt(3); /** * 编码动作,如果随机参数是1,则传输Person协议,如果随机参数是2,则传递Dog协议, * 如果随机参数是3,则传递Cat协议 * * Person协议就是传递一个标识位为0,然后将Person对象编码成二进制传输 * Dog协议传递一个标识位为1,然后将Dog对象编码成二进制进行传输 * Cat协议传递一个标识为2,然后将Cat对象编码成二进制进行传输 */ if(0 == randomInt){ pipeline.addLast(new PersonEncoder()); Person person = new Person(); person.setUsername("zhihao"); person.setAge(27); pipeline.addLast(new TestClientHandler(person)); } if(1 == randomInt){ pipeline.addLast(new DogEncoder()); Dog dog = new Dog(); dog.setName("wangcai"); dog.setAge("2"); pipeline.addLast(new TestClientHandler(dog)); } if(2 == randomInt){ pipeline.addLast(new CatEncoder()); Cat cat = new Cat(); cat.setName("maomi"); cat.setColor("yellow"); pipeline.addLast(new TestClientHandler(cat)); } } }三种自定义编码协议,与服务器端进行对应传输Person数据的时候,在Person数据之前加上标识位置数据0,在Dog数据之前加上标识位置数据1,在Cat数据之前加上标识位置数据2,然后将其与本身的数据一起编码成二进制进行传输。 import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class PersonEncoder extends MessageToByteEncoder{ @Override protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception { String username = msg.getUsername(); int usernamelength = username.length(); int age = msg.getAge(); out.writeInt(0); //标识位 out.writeInt(usernamelength); out.writeBytes(username.getBytes()); out.writeInt(age); } }Dog协议编码器 import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class DogEncoder extends MessageToByteEncoder{ @Override protected void encode(ChannelHandlerContext ctx, Dog msg, ByteBuf out) throws Exception { String name = msg.getName(); int namelength = name.length(); String age = msg.getAge(); out.writeInt(1); //标识位 out.writeInt(namelength); out.writeBytes(name.getBytes()); out.writeBytes(age.getBytes()); } }Cat协议编码器: import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class CatEncoder extends MessageToByteEncoder{ @Override protected void encode(ChannelHandlerContext ctx, Cat msg, ByteBuf out) throws Exception { String name = msg.getName(); int namelength = name.length(); String color = msg.getColor(); out.writeInt(2); //标识位 out.writeInt(namelength); out.writeBytes(name.getBytes()); out.writeBytes(color.getBytes()); } }自定义客户端处理器: import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class TestClientHandler extends ChannelInboundHandlerAdapter { private Person person; private Cat cat; private Dog dog; public TestClientHandler(Person person){ this.person = person; } public TestClientHandler(Dog dog){ this.dog = dog; } public TestClientHandler(Cat cat){ this.cat =cat; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { if(person != null){ ctx.channel().writeAndFlush(person); } if(dog != null){ ctx.channel().writeAndFlush(dog); } if(cat != null){ ctx.channel().writeAndFlush(cat); } } }启动服务端,再多次启动客户端,服务器控制台打印出不同协议传输的结果 maomi yellow 十月 15, 2017 4:33:43 下午 io.netty.handler.logging.LoggingHandler channelRead 信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0xf40f7b07, L:/127.0.0.1:8899 - R:/127.0.0.1:57879] 十月 15, 2017 4:33:43 下午 io.netty.handler.logging.LoggingHandler channelReadComplete 信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE wangcai 2 十月 15, 2017 4:33:48 下午 io.netty.handler.logging.LoggingHandler channelRead 信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0x3384f158, L:/127.0.0.1:8899 - R:/127.0.0.1:57914] 十月 15, 2017 4:33:48 下午 io.netty.handler.logging.LoggingHandler channelReadComplete 信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE zhihao

用户评论

?娘子汉

原来网ty可以支持好多种协议啊!这也太牛了吧!

    有20位网友表示赞同!

喜欢梅西

我一直以为netty只能支持TCP协议,看来我需要好好学习一下它的其他协议支持。

    有10位网友表示赞同!

苍白的笑〃

知道netty支持多种协议能扩展应用范围,很高兴看到这份笔记分享!

    有11位网友表示赞同!

致命伤

想清楚netty如何实现对不同协议的支持,这篇文章一定要看一遍。

    有12位网友表示赞同!

繁华若梦

学习netty是为了做分布式系统开发吧?多种协议支持确实重要。

    有17位网友表示赞同!

哭着哭着就萌了°

看了标题好奇这种协议支持具体都是哪些,笔记内容应该会介绍详细啊!

    有17位网友表示赞同!

冷嘲热讽i

多了解一些netty的特性,感觉未来的项目开发更容易了。

    有6位网友表示赞同!

西瓜贩子

学习netty好久了,感觉对协议支持这一块还不太了解,这篇笔记正好可以填补知识空缺。

    有9位网友表示赞同!

有些人,只适合好奇~

之前一直使用其他的框架,现在想尝试netty,这方面多协议支持真的很吸引人。

    有7位网友表示赞同!

秘密

网络通讯协议那么多,真是头大!希望笔记能介绍一些常见的协议和使用方法。

    有8位网友表示赞同!

人心叵测i

这篇笔记真棒,可以帮助我更好地理解netty的强大之处。

    有8位网友表示赞同!

々爱被冰凝固ゝ

在实际开发中,选择合适的协议非常重要,这篇文章应该会提供很多参考信息。

    有12位网友表示赞同!

短发

netty真的越来越厉害了!支持多种协议意味着更加灵活的应用场景。

    有7位网友表示赞同!

羁绊你

想了解更多关于netty和不同协议的应用案例,看一看这篇笔记也许会有答案。

    有5位网友表示赞同!

杰克

分享这份笔记真是太棒了,可以帮助很多新手更快地入门netty。

    有18位网友表示赞同!

久爱不厌

对高性能、多协议通信感兴趣的人,这篇文章一定不能错过!

    有17位网友表示赞同!

凉笙墨染

希望笔记能提供一些详细的代码示例,方便我更好地理解和学习。

    有20位网友表示赞同!

余笙南吟

netty的多协议支持真是个大亮点,可以帮助我们开发更强大的应用。

    有20位网友表示赞同!

疲倦了

期待这篇笔记能深入解析netty在不同协议上的优缺点,以便我做出更好的选择。

    有9位网友表示赞同!

【深入浅出Netty通讯协议支持解析:第9篇教程】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:探索网络世界的桥梁:深入解析链接奥秘 下一篇:探索BTOB领域的创新趋势与商业策略