- 前言:作为一个刚踏入职场的实习生,我很幸运参加了某个政府项目,并且在项目中负责一个核心模块功能的开发,而不是从头到尾对数据库的crud。虽然我一直心里抱怨我的工作范围根本就不是实习生干的活,因为没有前辈带我、入职就开始改bug。但在这样的环境下我却学到了很多东西,即便老板有压榨我的嫌疑,却给了我如此好的历练机会(但工资方面真的很亏QAQ),毕竟实习过后跳槽简历上还有亮点。
- 背景:在不同的网络环境、不同的开发语言组成的闭环系统中,多个模块之间要实时获取硬件设备的状态,并以xml报文的格式相互传递。在必要的时候解析这个报文获取其中的关键信息完成相应的动作,最后直观地把信息显示给前端用户。如此一来就是涉及到了跨终端通信,并且有的功能模块既要充当客户端分发指令,还要作为客户端从别处获取信息。很不巧的是,我负责的模块正是充当消息中转站的作用,为了解决这个需求我才有了了解这种机制的机会。
- 正文:由于涉及到不同模块之间的通信,其中又包含了web前端,为了保持通信协议的一致性项目采用了ws协议,即websocket。
http协议和ws协议:最直观的特点就是访问地址的写法上略有不同,两种访问分别为http://ip:port、ws://ip:port。http协议访问采用一问一答的方式——客户端发起请求到服务端,服务端收到请求后返回对应的信息,通信结束。虽然可以利用ajax发起异步的http请求,不过都是一次访问后连接就断开(ajax轮询可以解决此问题,对服务器资源消耗较大);ws协议建立于TCP协议之上,我们观察ws的头部信息会发现一件有意思的事情
HttpObjectAggregator$AggregatedFullHttpRequest(decodeResult: success, version: HTTP/1.1, content: CompositeByteBuf(ridx: 0, widx: 0, cap: 0, components=0))
GET / HTTP/1.1
Host: localhost:8888
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Sec-WebSocket-Key: RLnvly29Zl3sJQOfGFjO9w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
content-length: 0
websocket建立时是以http协议握手,当建立连接之后就不需要http之间双向通信。协议深层次的东西以我目前的能力是无法理解的,将来有机会我会再回过头来分析底层协议。那么接下来就进入我们的主题。
引入maven依赖(结合项目实际情况而定):
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>5.0.0.Alpha2version>
dependency>
我们的需求自己既是服务端向其他模块推送消息,又是客户端从别处获取消息后推送其他模块。那么我们要在自己项目中集成netty客户端和服务端,因为客户端无法给多个连接推送消息。这里我主要介绍
SimpleChannelInboundHandler类的三个方法:
1. channelActive
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端加入连接:" ctx.channel());
//UserChannelUtil封装了channelGroup
UserChannelUtil.getChannelGroup().add(ctx.channel());
}
这个方法会在有客户端连接上来时执行,具体作用很多,通常是获取客户端的通道号加入到ChannelGroup中,例如注意这个GlobalEventExecutor.INSTANCE是单例,那么就意味着我们操作的是同一个对象,我们将连接的通道加入后将来就可以实现批量推送。
2.channelInactive
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//断开连接
System.out.println("客户端断开连接:" ctx.channel());
UserChannelUtil.getChannelGroup().remove(ctx.channel());
}
它表示在客户端离开时会触发该事件,在这个方法内我们可以做一些收尾工作——把通道号去除,避免了通过列表的积累造成管理混乱。
3.channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端:" ctx.channel() "的消息:\n" msg.text())
}
很显然它就是在收到消息后会执行该事件,客户端和服务端的方法都是相同的。
重点:netty主要是通过连接时产生的通道channel进行消息的推送,只要拿到这个channe就能对任意的客户端推送消息,我们分析以下场景:当客户端连接服务端时会产生一个channel,观察channelRead()方法里面一个参数是channel,另一个则是接收的消息 。现在我们的任务就是获取服务端channelRead()的channel,在客户端的channelRead()中调用服务端的channelRead()方法,再把刚刚获取的服务端channel传进去这样通道就以及打通了,我们只需要把客户端传来的消息试试传入就完成了我们的需求。
首先建立通道号保存的对象
@Data
public class ServerChannel{
private static Channel serverChannel;
}
从服务端获取通道号
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ServerChannel.setServerChannel(ctx.channel());
System.out.println("收到客户端:" ctx.channel() "的消息:\n" msg.text())
}
在客户端调用服务端的方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到服务端:" ctx.channel() "的消息:\n" msg.text())
//通过反射创建服务端对象
Class SocketHandler=WebSocketServerHandler.class;
WebSocketServerHandler socketHandler=SocketHandler.newInstance();
//获取服务端的通道号
ChannelHandlerContext serverChannel=ServerCtx.getContext();
if(serverChannel !=null){
socketHandler.channelRead(serverCtx,msg);
}
- 结尾:说了一大堆做法就这么简单,分析问题的过程很漫长,理清思路就能高效解决问题。在这里给大家一个提示:netty握手协议内容的大小写敏感,很有可能会因为不同语言的websocket类库不同导致握手不成功,这时就需要重写netty的头部握手协议。如有不足之处请各位大佬们指正。
内容出处:,
声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.com/procedure/23324.html