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.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</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<WebSocketServerHandler> SocketHandler=WebSocketServerHandler.class;
WebSocketServerHandler socketHandler=SocketHandler.newInstance();
//获取服务端的通道号
ChannelHandlerContext serverChannel=ServerCtx.getContext();
if(serverChannel !=null){
socketHandler.channelRead(serverCtx,msg);
}