实战附源码

核心提示20大进阶架构专题每日送达作者:pjmike_pjjuejin.im/post/5bd584bc518825292865395d前言这一篇文章主要介绍如何用Springboot 整合 Netty,这里也是在网上搜寻了一些Netty例子学习后

每日发布的20大高级架构主题

作者:pj Mike _ pjjuejin.im/post/5bd584bc518825292865395d

序

本文主要介绍如何整合Netty和Springboot。这里也是在网上搜了一些Netty的例子,借鉴了别人的写作方法和经验后总结出来的。如有重复,请见谅。

我将在以下步骤中分析和讨论SpringBoot如何集成Netty:

构建Netty服务器构建Netty客户端使用protobuf定义消息格式Server 空空闲检测客户端发送心跳包,断开连接重新连接PS:为了简单起见,我把Netty服务器和客户端放在同一个SpringBoot项目中,当然也可以把客户端和服务器分开。

构建网络服务器

Netty服务器的代码其实挺简单的,代码如下:

@ component @ slf4jpublic类nettyserver {privateeventloopgroup boss = newnioeventloopgroup;privateeventloopgroup work = newnioeventloopgroup;@Valueprivate整数端口;@ post construct public Void Start Throws中断异常{ Server Bootstrap Bootstrap = New Server Bootstrap;指定bootstrap.group// Channel.channel//使用指定的端口来设置套接字地址。localAddress)//服务器端可连接队列的数量,设置backlog参数对应的TCP长连接。TCP/IP协议listen函数中的option//。一般情况下,如果两个小时内没有数据通信,TCP会自动发送活动探测数据消息。childOption//将小数据包打包成较大的帧进行传输,这样会增加网络的负载,即TCP延迟传输。child option . child handler;channel future future = bootstrap . bind . sync;if { log.info}}@PreDestroypublic void destory抛出interrupted exception { boss . shut down graceful . sync;work . shut down graceful . sync;log.info}}因为我们在springboot项目中使用Netty,所以我们将NettyServer的启动封装在一个start方法中,并使用@PostConstruct注释,将@PostConstruct注释添加到指定的方法中,以指示该方法是在Spring初始化Nettyserver类之后调用的。

考虑到心跳机制和其他操作的使用,关于ChannelHandler的逻辑处理链的部分将在后面描述。

构建网络客户端

Netty客户端代码类似于服务器代码,代码如下:

@ Component @ SLF 4j public class netty client { private EventLoopGroup group = new NioEventLoopGroup;@ Valueprivate int port@Valueprivate字符串主机;私有SocketChannel socketChannelpublic void send msg { socket channel . writeandflush;} @ post construct public void start { Bootstrap Bootstrap = new Bootstrap;bootstrap . group . channel . remote address . option . option . handler;channel future future = bootstrap . connect;//未来。AddListener future 1--> { if { log . info;} else { log.infofuture 1 . channel . event loop . schedule;}});socketChannel = future.channel}}以上还包含了客户端断开和重新连接的逻辑。更多细节将在下面描述。

使用protobuf构建通信协议

在整合Netty的过程中,我们使用Google的protobuf来定义消息格式。下面简单介绍一下protobuf。

protobuf简介

Google官方对protobuf的定义如下:

协议缓冲区是一种轻量高效的结构化数据存储格式,可用于结构化数据的序列化,非常适合数据存储或RPC数据交换格式。可用于通信协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

Netty中常用Protobuf作为序列化方案,当然protobuf也可以用来构建客户端和服务器端的通信协议。

为什么要用protobuf?

这里我们用protobuf作为我们的序列化手段,那么我们为什么用protobuf而不用其他的序列化方案,比如jdk自己的序列化,Thrift,fastjson等等。

首先,jdk本身的序列化手段有很多缺点,比如:

序列化的码流太大,性能太低,不能跨语言,而Google Protobuf是跨语言的,支持C++、java、python。然后,protobuf编码的消息更小,有利于存储和传输,性能也很高。与其他序列化框架相比,也是很有优势的。各种Java序列化框架的对比这里就不多说了。总之,Google Protobuf目前被广泛应用于各种项目中,它的诸多优点让我们选择使用它。

如何使用protobuf

对于Java,使用protobuf主要有以下步骤:

在中定义消息格式。原型文件。编译。使用protobuf编译器将proto文件转换成Java类。使用对应Java的protobuf API编写或读取消息。

定义protobuf协议格式

下面是我的演示中的message.proto文件作为示例,如下所示:

//protobuf语法包括proto2和proto3,其中指定了proto3syntax = " proto3//文件选项Java _ package = " com . pj Mike . server . protocol . proto buf ";option Java _ outer _ class name = " Messagebase ";//消息模型定义message message { string request id = 1;CommandType cmd = 2;字符串内容= 3;枚举命令类型{ NORMAL = 0;//通用服务消息heart beat _ REQUEST = 1;//客户端心跳消息heart beat _ RESPonSE = 2;//服务器心跳消息}}文件解释:

本文的第一行指定使用proto3语法。如果没有指定,编译器默认使用proto2语法。如今,proto3语法可能会在新项目中普遍使用。proto3支持更多的语言,但比proto2更简洁。如果你是第一次使用protobuf,你可以选择使用proto3来定义一个. proto文件。您可以标记一系列选项,其中一些是在文件级别,例如上面的第二行和第三行。java_package文件选项指示包,由。协议编译器编译的proto文件。java_outer_classname选项表示在您想要生成的java类的名称消息中定义了特定的消息格式。我在这里定义了三个字段,每个字段都有一个惟一的数字标识符。这些标识符用于标识消息的二进制格式中的每个字段。消息中添加了一个枚举类型,它包含CommandType类型中的所有值。每个枚举类型必须将其第一个类型映射到默认值0。

消息模型定义

关于消息格式,这里我只是非常非常简单地定义了几个字段。requestId代表消息Id,CommandType代表消息类型。这里简单分为心跳消息类型和业务消息类型,然后内容就是具体的消息内容。这里消息格式的定义非常简单。在实际项目中,关于自定义消息格式的要求很多,比较复杂。

以上简单介绍了protobuf的一些语法规则。有关protobuf语法的更多信息,请参考官方文档:

https://developers.google.com/protocol-buffers/docs/proto3

用。原型编译器

第一步是定义protobuf的消息格式,然后我们使用。proto文件将我们定义的消息格式编译成相应的Java类,这样我们就可以在项目中使用这个消息类。

关于protobuf编译器的安装,这里就不赘述了。详见官方文件:

https://developers.google.com/protocol-buffers/

安装编译器后,编译。使用以下命令创建proto文件:

协议-I =。/-Java _ out =。/./message.proto-I选项用于指定。找到要编译的原型消息定义文件。这个选项也可以写成-proto _ path-Java _ out选项表示生成Java代码后的存储位置。对于不同的语言,我们的选项可能会有所不同,比如-生成C++代码。

使用Java对应的protobuf API读写消息。

前面的Java类是根据。原型消息定义文件。我们这里的代码根据Message.proto生成Messagebase类,但是为了正常使用生成的Java类,我们还需要引入protobuf-java依赖关系:

com . Google . protobufprotobuf-Java 3 . 5 . 1每个用protobuf生成的Java类都会包含两个内部类:Msg和包含在Msg中的Builder。特别是,中定义的每条消息。proto将生成一个消息,每个消息对应一个构建器:

Buidler提供了building类,query类的APIMsg提供了查询、序列化和反序列化的API。比如我们用Builder构建Msg,例子如下:

公共类Messagebase test { public static void main { Messagebase。消息message = Messagebase。message . new builder . setrequestid . set content . build;system . out . println;}}这里就不介绍protobuf-java API的相关用法了。有关更多详细信息,请参考官方文档:

https://developers . Google . com/protocol-buffers/docs/reference/Java/

protobuf的编解码器

说了这么多,消息传输格式已经定义好了,但是在客户端和服务器端传输的过程中,我们还是需要对这个protobuf格式进行编解码。当然,我们可以定制消息的编码和解码。protobuf-java API提供了相关的序列化和反序列化方法。好消息是Netty为protobuf提供了编解码器来支持protobuf,如下表所示。

Netty实现心跳机制

让我们来看看客户端是如何实现心跳机制的:

@Slf4jpublic类HeartbeatHandler扩展ChannelInboundHandlerAdapter { @ override public void userEventTriggered抛出异常{ if { IdleStateEvent IdleStateEvent = evt;if { log.info//向服务器发送心跳包//这里使用protobuf定义的消息格式:messagebasemessage heart beat = new message base;消息;tobuildersetcmdsetrequestidsetcontent建造;//发送心跳消息,发送失败时关闭连接。CTX . writeandflush . addlistener;} } else { super . user event triggered;}}}我们在这里创建了一个ChannelHandler类,重写了userEventTriggered方法,其中实现了发送心跳包的逻辑,并在逻辑处理链中加入了IdleStateEvent类。

实际上,当connection 空空闲时间过长时,会触发一个IdleStateEvent事件,然后我们会调用userEventTriggered来处理IdleStateEvent事件。

启动客户端和服务器后,控制台会打印心跳消息,如下所示:

2018-10-28 16:30:46.825 INFO 42648-[ntLoopGroup-2-1]c . pjmike . server . client . heartbeat handler:它已经10s没有向服务器发送消息了,2018-10-28 16:30:47.176 INFO 42648-[ntLoopGroup-4-1]c . p . server . server . nettyserverhandler:收到了来自客户端的heart beat消息:请求ID:" 888

一般对于长连接,一种解决方案是双方都发送心跳消息,另一种是服务器是被动接收方,如果服务器在一段时间内没有收到心跳包,就直接断开连接。

这里我们采用第二种方案,只需要客户端发送心跳消息,然后服务器被动接收,再设置一段时间。在此期间,如果服务器没有收到任何消息,它会主动断开连接,这就是后面所说的空空闲检测。

Netty客户端已断开连接并重新连接

通常,在以下两种情况下,Netty客户端需要重新连接服务器:

Netty客户端启动时,服务器挂机,无法连接服务器。程序运行过程中,服务器突然挂机。第一种情况实现了ChannelFutureListener用于监控连接是否成功的机制,如果不成功,则断开连接并重试。代码如下:

@ Component @ SLF 4j public class netty client { private EventLoopGroup group = new NioEventLoopGroup;@ Valueprivate int port@Valueprivate字符串主机;私有SocketChannel socketChannelpublic void send msg { socket channel . writeandflush;} @ post construct public void start { Bootstrap Bootstrap = new Bootstrap;bootstrap . group . channel . remote address . handler;channel future future = bootstrap . connect;//未来。AddListener future 1--> { if { log . info;} else { log.infofuture 1 . channel . event loop . schedule;}});socketChannel = future.channel}}ChannelFuture添加侦听器。如果客户端无法连接到服务器,请调用channel.eventLoop.schedule方法来执行重试逻辑。

第二种情况是服务器在运行过程中突然挂机。这种情况在处理数据读写的处理程序中实现。代码如下:

@Slf4jpublic类HeartbeatHandler扩展ChannelInboundHandlerAdapter { @ Autowiredprivate netty client netty client;@ override public void userEventTriggered引发异常{ if { IdleStateEvent IdleStateEvent = evt;if { log.info//发送心跳包messagebasemessage heart beat = new message base;消息;tobuildersetcmdsetrequestidsetcontent构建到服务器;//发送心跳消息,发送失败时关闭连接。CTX . writeandflush . addlistener;} } else { super . user event triggered;} } @ Override Public Void Channel Inactive抛出异常{//如果服务器在运行过程中挂起,执行重新连接机制event loop event loop = CTX . Channel . event loop;eventLoop.schedulesuper.channelInactive}}这里我们直接在实现心跳机制的处理程序中重写channelInactive方法,然后在这个方法中执行重试逻辑。这里我们注入NettyClient类,从而调用NettyClient的start方法重新连接服务器。

ChannelInactive方法意味着如果当前通道没有连接到远程节点,将调用此方法。

Server 空空闲检测

空什么是空闲检测?实际上,空空闲检测就是检测每隔一段时间是否有数据读写。比如服务器检测是否在一段时间内收到了客户端发送的数据,如果没有,就及时释放资源,关闭连接。

对于空空闲检测,Netty特别提供了IdleStateHandler来实现这个功能。以下代码参考了掘金小册子《Netty与实战入门:模仿微信IM即时通讯系统》中空空闲检测部分的实现:

@ SLF 4 jpublic class serveridlestatehandler扩展idlestatehandler {private static final int reader _ idle _ time = 30;public serveridstatehandler { super;} @ override protected void channelIdle抛出异常{ log.infoCTX . channel . close;

控制器的方法测试

因为这是SpringBoot集成Netty的演示,所以我们创建一个控制器方法来测试Netty服务器和客户端之间的通信。控制器代码如下,非常简单:

@ RestControllerpublic class consumer controller { @ Autowiredprivate netty client netty client;@ getmapping public String send { Messagebase。消息message =新的消息库。message . to builder . set cmd . set content . setrequestid . build;netty client . send msg;返回“发送ok”;}}注入到NettyClient中,调用其sendMsg方法发送消息。结果如下:

c . p . Server . Server . nettyserverhandler:收到客户端发来的业务消息:请求ID:“ABA 74c 28-1B6E-42 B3-9 f27-889 e 7044 dcbf”内容:“您好服务器”

总结

以上详细介绍了如何将Netty与SpringBoot融合,其中借鉴了很多高层领导的例子和文章,是对如何使用Netty的初步了解。如果文中有错误,请指出来。

Github地址:

https://github.com/pjmike/springboot-netty

涉及

https://Jue Jin . im/book/5b 4 BC 28 BF 265 da 0 f 60130116 https://colobu . com/2015/08/14/netty-TCP-client-with-reconnect-handling/https://crossover Jie . top/2018/05/24/netty/NettyTCP-heart beat/https://segment fault . com/a/1190000006931568 https://https

之前给你发了三本Java面试书。这一次,我增加了一个新的。目前总共有四本采访书。我相信跳槽前一个月按照面试书准备基本没什么大问题。

java面试宝典5.0,350道Java面试题:收集自100+家公司,高级Java面试宝典-视频版和Java[BAT]面试必需品分别适用于初级、高级、高级工程师的面试复习。

包括java基础知识、javaweb、mysql性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring family bucket原理、微服务、Zookeeper、数据结构、限流熔丝退化等等。

看这里,证明点什么。

 
友情链接
鄂ICP备19019357号-22