有很多场景需要服务端主动向客户端推送消息,比如小红点提醒,新消息提醒,群聊等场景,本文档记录服务端推送消息的方案以及简单实现。

方案设计

1、短轮询

前端不断间隔一段事件向服务器发送一个 HTTP 请求,如果有数据,就会在某次请求中返回。

image.png

适用场景:

  1. 扫码登录:短时间内频繁查询二维码状态
  2. 小OA系统:客户端使用量不大的情况下可以使用

缺点:

  1. 大量无效请求:大量的无效请求,浪费服务器资源
  2. 服务端请求压力大:万人群聊频繁访问,上万并发服务扛不住

2、长轮询

长轮询和短轮询相比,一个最大的改进之处在于:

  1. 短轮询模式下,服务端不管本轮有没有新消息产生,都会马上响应并返回。而长轮询模式当本次请求没有获取到新消息时,并不会马上结束返回,而是会在服务端“悬挂(hang)”,等待一段时间;
  2. 如果在等待的这段时间内有新消息产生,就能马上响应返回。

这也意味着 web 端的请求超时时长需要设置长一些。

image.png

相比短轮询模式,大幅降低短轮询模式中客户端高频无用的轮询导致的网络开销和功耗开销。

缺点:

  • 长轮询在超时时间内没有获取到消息时,会结束返回,因此仍然没有完全解决客户端“无效”请求的问题。
  • 服务端压力大:服务端悬挂(hang)住请求,只是降低了入口请求的 QPS,并没有减少对后端资源轮询的压力。假如有 1000 个请求在等待消息,可能意味着有 1000 个线程在不断轮询消息存储资源。(轮询转移到了后端)

3、WebSocket

长轮询和短轮询都是由客户端先发起的请求,服务端无法主动推送消息,因此诞生了 WebSocket。

实现原理: 客户端和服务器之间维持一个 TCP/IP 长连接,全双工通道

image.png

Netty 实现 WebSocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//  初始化 ChannelPipeline 

import com.abin.handler.HttpRequestHandler;
import com.abin.handler.TextWebSocketFrameHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class ChatServerInitializer extends ChannelInitializer<Channel> {
private final ChannelGroup group;

public ChatServerInitializer(ChannelGroup group) {
this.group = group;
}

@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
// http 数据在传输过程中是分段的
// HttpObjectAggregator可以把多个段聚合起来;
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new HttpRequestHandler("/ws"));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new TextWebSocketFrameHandler(group));
}
}

image.png

WebSocketServerProtocolHandler 处理了所有委托管理的 WebSocket 帧类型以及升级握手本身。如果握手成功,那么所需的 ChannelHandler 将会被添加到 ChannelPipeline 中,而那些不再需要的 ChannelHandler 则将会被移除。

image.png
image.png