chat

主要实现方式

在实现即时通讯(IM)系统的前后端交互时,长轮询(Long Polling)、定时轮询(Polling)、SSE(Server-Sent Events)和 WebSocket(WS) 是常见的通信模式,但它们各有适用场景和优缺点。

以下是对比分析及现代 IM 系统的典型技术选型:


1. 通信模式对比

| 技术 | 协议 | 方向性 | 实时性 | 适用场景 | 缺点 | |——————–|———–|————–|————–|———————————-|———————————–| | 定时轮询 | HTTP | 客户端主动拉取 | 低(延迟高) | 简单场景,兼容性要求高 | 资源浪费(频繁请求)、延迟不可控 | | 长轮询 | HTTP | 客户端半主动 | 中(依赖超时) | 兼容老旧设备,短时实时需求 | 连接频繁重建,服务器并发压力大 | | SSE | HTTP | 服务端单向推送 | 高(单向流) | 服务端主动通知(如新闻推送) | 仅支持单向通信,需浏览器兼容性 | | WebSocket | WS/WSS | 全双工双向通信 | 高(实时) | 即时聊天、实时协作、游戏 | 需协议升级,部分网络设备可能拦截 |

后端是 java springboot 技术栈,前段主要是 js vue 技术栈。除了上面的几个策略,还有其他实现方式吗?

在 Java Spring Boot 后端和 Vue.js 前端的技术栈下,除了传统的 WebSocket、长轮询、SSE 之外,还可以结合其他技术方案实现 IM 功能。

以下是针对该技术栈的扩展方案及实现细节:


1. gRPC + gRPC-Web(双向流通信)

gRPC + gRPC-Web(双向流通信)

gRPC 是 Google 开发的一种高性能、开源和通用的 RPC(Remote Procedure Call)框架,使用 Protocol Buffers(protobuf)作为接口定义语言(IDL),支持多种语言的客户端和服务器端实现。gRPC 提供了高效的通信协议,尤其适用于分布式系统中的微服务通信。

gRPC-Web 是 gRPC 的一个扩展,它使得可以在 Web 浏览器中直接与 gRPC 服务器进行通信。Web 客户端通过 HTTP/1.1 或 HTTP/2 协议与 gRPC 服务器通信。gRPC-Web 主要用于在现代 Web 应用中与 gRPC 服务进行交互,但浏览器本身不支持直接使用 gRPC,因此 gRPC-Web 充当了一个代理或中间层。

双向流通信 是 gRPC 中的一种通信模式,它允许客户端和服务器之间进行双向流数据传输。在此模式下,客户端和服务器都可以在保持连接的同时发送和接收消息。这种方式非常适合实时、低延迟的双向交互场景,如实时聊天、监控系统、在线游戏等。

gRPC + gRPC-Web 双向流通信的原理

gRPC 中有四种通信模型:

  1. 单向请求-响应(Unary RPC):客户端发送一个请求,服务器返回一个响应。
  2. 服务器流式响应(Server-streaming RPC):客户端发送一个请求,服务器返回多个响应。
  3. 客户端流式请求(Client-streaming RPC):客户端发送多个请求,服务器返回一个响应。
  4. 双向流式通信(Bidirectional-streaming RPC):客户端和服务器都可以发送多个消息,通过一个连接进行双向的流式通信。

在双向流式通信中,客户端和服务器通过流建立连接,双方可以随时发送消息,直到其中一方关闭连接。

双向流通信的步骤

  1. 客户端发送消息:客户端可以持续不断地向服务器发送消息。发送消息的顺序由客户端控制。
  2. 服务器接收并响应消息:服务器可以持续不断地接收客户端消息,并根据需要随时向客户端返回响应。
  3. 连接保持:与传统的请求-响应模型不同,在双向流模式下,连接保持开启,直到客户端或服务器显式关闭连接。

这种模式的一个常见应用是实时聊天系统,客户端和服务器保持一个长连接,实时交换消息。

gRPC 双向流通信示例

1. gRPC 服务定义

首先,我们定义一个简单的双向流式 RPC 服务。我们使用 Protocol Buffers 来描述服务接口。

service.proto 文件:

  [proto]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax = "proto3"; package chat; // ChatService 服务定义 service ChatService { // 双向流式 RPC rpc Chat (stream ChatMessage) returns (stream ChatMessage); } // 消息定义 message ChatMessage { string user = 1; // 用户名 string message = 2; // 消息内容 int64 timestamp = 3; // 时间戳 }

在这个定义中,我们创建了一个 ChatService 服务,它有一个 Chat 方法,支持双向流式通信。ChatMessage 消息定义了用户、消息和时间戳。

2. 生成服务代码

使用 protoc 命令来生成服务器端和客户端的代码。假设我们使用的是 Java 和 JavaScript(用于 gRPC-Web)客户端。

生成 Java 代码:

  [bash]
1
protoc --java_out=src/main/java --grpc-java_out=src/main/java --proto_path=./service service.proto

生成 JavaScript 代码(用于 gRPC-Web):

  [bash]
1
protoc --js_out=import_style=commonjs,binary:./src --grpc-web_out=import_style=typescript,mode=grpcwebtext:./src --proto_path=./service service.proto

3. 实现 gRPC 服务器

在 Java 后端实现双向流式 RPC:

  [java]
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
34
35
36
37
import io.grpc.stub.StreamObserver; public class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase { @Override public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) { return new StreamObserver<ChatMessage>() { @Override public void onNext(ChatMessage message) { // 打印收到的消息 System.out.println("Received message: " + message.getMessage()); // 发送响应 ChatMessage response = ChatMessage.newBuilder() .setUser("Server") .setMessage("Reply to: " + message.getMessage()) .setTimestamp(System.currentTimeMillis()) .build(); responseObserver.onNext(response); // 发送消息给客户端 } @Override public void onError(Throwable t) { // 错误处理 t.printStackTrace(); } @Override public void onCompleted() { // 连接关闭时 responseObserver.onCompleted(); } }; } }

在这个实现中,chat 方法返回一个 StreamObserver<ChatMessage> 对象,允许客户端和服务器之间进行双向通信。客户端发送消息后,服务器收到消息并回应。

4. 配置 gRPC-Web 代理

gRPC-Web 是为了支持 Web 浏览器与 gRPC 服务器通信。在浏览器中,客户端不能直接使用 WebSocket 或 HTTP/2 进行 gRPC 调用,所以需要通过一个代理(如 envoygrpc-web 代理)来桥接 gRPC 和 HTTP/1.1。

使用 gRPC-Web 代理

  • 安装 gRPC-Web 代理:

      [bash]
    1
    npm install -g grpc-web
  • 启动代理:

      [bash]
    1
    grpcwebproxy --backend_addr=localhost:50051 --allow_all_origins

    代理启动后,它会转发来自 Web 客户端的请求到实际的 gRPC 服务器。

5. 实现 Web 客户端(Vue.js + gRPC-Web)

前端客户端使用 gRPC-Web 进行双向流式通信。

  [javascript]
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
import { ChatServiceClient } from './generated/chat_grpc_web_pb'; import { ChatMessage } from './generated/service_pb'; const client = new ChatServiceClient('http://localhost:8080', null, null); const stream = client.chat((err, response) => { if (err) { console.error(err); } else { console.log('Received from server:', response.getMessage()); } }); // 发送消息 const message = new ChatMessage(); message.setUser('Client'); message.setMessage('Hello from client!'); message.setTimestamp(Date.now()); stream.write(message); // 监听输入并实时发送消息 document.querySelector('#sendButton').addEventListener('click', () => { const userMessage = document.querySelector('#messageInput').value; const msg = new ChatMessage(); msg.setUser('Client'); msg.setMessage(userMessage); msg.setTimestamp(Date.now()); stream.write(msg); });

在这个前端代码中,我们使用 ChatServiceClient 类来访问 gRPC 服务。通过 client.chat() 方法发起双向流请求,stream.write() 可以发送消息,stream.on('data') 可以接收从服务器返回的响应。

总结

  • gRPCgRPC-Web 提供了一种高效的双向流式通信方式,使客户端和服务器能够保持持久连接,进行实时数据交换。
  • 双向流通信 在许多场景下非常有用,比如即时消息、实时通知、在线游戏等。
  • gRPC-Web 通过代理允许 Web 客户端与 gRPC 服务器进行通信,使得 Web 应用能够使用 gRPC 的所有优势。

这种技术组合适合需要低延迟和高效实时通信的应用,是现代 Web 应用中的一种优秀解决方案。

方案概述

  • gRPC 是基于 HTTP/2 的高性能 RPC 框架,支持 双向流(Bidirectional Streaming),适合实时消息场景。
  • gRPC-Web 允许浏览器通过 HTTP/1.1 或 HTTP/2 与 gRPC 服务通信,解决浏览器原生不支持 gRPC 的问题。

实现步骤

  1. 后端(Spring Boot)
    • 添加依赖:grpc-spring-boot-starter
    • 定义 Protobuf 消息格式(.proto 文件):
        [protobuf]
      1
      2
      3
      4
      5
      service ChatService { rpc ChatStream(stream ClientMessage) returns (stream ServerMessage) {} } message ClientMessage { string content = 1; } message ServerMessage { string content = 1; }
    • 实现服务端流式处理:
        [java]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @GrpcService public class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase { @Override public StreamObserver<ClientMessage> chatStream(StreamObserver<ServerMessage> responseObserver) { return new StreamObserver<>() { @Override public void onNext(ClientMessage message) { // 处理消息并推送 responseObserver.onNext(ServerMessage.newBuilder().setContent("Echo: " + message.getContent()).build()); } @Override public void onError(Throwable t) { /* 错误处理 */ } @Override public void onCompleted() { responseObserver.onCompleted(); } }; } }
  2. 前端(Vue.js)
    • 使用 grpc-web 库:
        [bash]
      1
      npm install grpc-web
    • 生成客户端代码:
        [bash]
      1
      protoc --js_out=import_style=commonjs,binary:. --grpc-web_out=import_style=commonjs,mode=grpcwebtext:. chat.proto
    • 调用 gRPC 服务:
        [javascript]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { ChatServiceClient } from './chat_pb_service'; import { ClientMessage } from './chat_pb'; const client = new ChatServiceClient('http://localhost:8080'); const stream = client.chatStream(); stream.on('data', (response) => { console.log('Received:', response.getContent()); }); // 发送消息 const message = new ClientMessage(); message.setContent('Hello gRPC!'); stream.write(message);

优点

  • 高性能(二进制协议 Protobuf),支持多语言交互。
  • 天然支持双向流,适合复杂交互场景。

缺点

  • 浏览器端需通过 gRPC-Web 代理(如 Envoy)或框架封装。
  • 调试复杂度高于 REST API。

总结

参考资料