本文首发于公众号:移动开发那些事 Flutter 开发者必备:WebSocket 实用指南
1 简介
Websocket
是网络通信(应用层)协议,是客户端应用中解决实时通信的解决方案之一。本文将从 Websocket
的基本概念出发,结合其实现的原理详细解析其在 Flutter
开发中要如何使用该协议来实现业务需求,帮助开发者全面掌握这一关键技术。
2 Websocket
介绍
Websocket
是一种基于TCP
的全双工通信协议 ,由HTML5
标准定义,旨在解决HTTP
协议在实时通信场景中的局限性。与传统的 HTTP
协议相比,Websocket
最大的特点是建立连接后保持持久连接状态 ,允许客户端与服务器之间进行双向的数据传输,而无需传统HTTP
那样每次通信都重新建立连接。
在 HTTP
协议中,通信只能由客户端发起,服务器无法主动向客户端推送数据。如果需要实现类似实时更新的功能,只能通过轮询(Polling
)或长轮询(Long Polling
)等方式模拟,这些方式不仅会消耗大量的网络带宽和服务器资源,还会导致数据传输延迟。而Websocket
通过一次握手建立连接后,客户端和服务器可以随时向对方发送数据,实现了真正意义上的实时双向通信。
看到这里,很多朋友肯定会有疑问,现在HTTP 2.0
的协议也是支持服务器推送的,为啥说要实现类似的推送的功能要通过轮询来实现呢?因为HTTP 2.0
的推送是有条件的
- 2.0的推送的时机是在客户端请求某个资源时,能把相关的资源推送到客户端缓存;(推送的资源只能是浏览器能直接使用的缓存资源)
2.1 Websocket
推送与HTTP 2.0
的区别
对比维度 | WebSocket 推送 | HTTP/2 服务器推送(Server Push) |
---|---|---|
协议本质 | 独立的全双工通信协议,需单独建立连接 | 基于 HTTP/2 协议的扩展功能,复用 HTTP 连接 |
推送方向 | 全双工:客户端与服务器可双向主动推送数据 | 单向:仅服务器可主动向客户端推送关联资源 |
连接方式 | 需通过 HTTP 握手升级为 WebSocket 连接,连接长期保持 | 基于已建立的 HTTP/2 连接,无需额外握手 |
推送触发条件 | 无依赖,服务器可随时主动推送任意数据 | 需基于客户端的请求触发,推送与请求相关的资源 |
数据格式 | 支持文本、二进制等任意格式,协议本身不限制 | 主要推送 HTTP 资源(如 CSS、JS、图片等) |
适用场景 | 实时通信场景(如聊天、弹幕、实时数据监控) | 网页资源预加载(如提前推送页面依赖的 CSS/JS) |
连接生命周期 | 连接长期存在,除非主动关闭 | 随 HTTP/2 连接生命周期结束而终止(通常较短) |
状态管理 | 保持连接状态,适合持续交互 | 无状态,推送资源后不维护长期交互状态 |
兼容性 | 需浏览器和服务器同时支持 WebSocket 协议 | 依赖 HTTP/2 协议支持,需客户端和服务器均兼容 |
资源消耗 | 长期保持连接,服务器资源占用较高 | 复用现有连接,资源消耗相对较低 |
2.2 协议格式
Websocket
协议的 URL
格式与 HTTP
有所区别,使用ws://
(非加密)或wss://
(加密,基于 TLS)作为协议前缀,例如ws://``example.com/realtime
或wss://``secure.example.com/chat
。这种协议设计使得Websocket
能够与 HTTP
协议共用端口(80 和 443),从而避免了防火墙对非标准端口的限制。
2.3 Websocket
特点
2.3.1 实现即时双向通信
Websocket
最核心的作用是打破传统 HTTP
的单向请求限制,让客户端与服务器之间能够实时互发数据 。例如在即时聊天应用中,用户发送消息后,服务器可以立即将消息推送给接收方的客户端,无需客户端频繁发起请求;在多人协作工具中,某一用户的操作变更能通过 Websocket
实时同步到其他用户的设备上,确保所有参与者看到的数据保持一致。
2.3.2 降低资源消耗与延迟
相比轮询等模拟实时通信的方式,Websocket 通过持久连接减少了重复建立连接的开销。在移动应用中,这意味着更低的网络流量消耗和更长的设备续航时间,尤其适合移动场景。同时,数据传输的实时性也得到显著提升,延迟可降低至毫秒级,这对于在线游戏、实时语音交互的应用至关重要;
3 Websocket
核心流程
3.1 建立连接:基于 HTTP 的握手过程
Websocket
连接的建立采用 "握手" 机制,本质上是通过 HTTP
请求发起,然后升级为 Websocket
协议
-
客户端向服务器发送一个特殊的 HTTP GET 请求,请求头中包含
Upgrade: websocket
和Connection: Upgrade
字段,表明希望将连接升级为Websocket
协议; -
服务器收到请求后,若支持
Websocket
协议,会返回101
状态码(Switching Protocols
),并在响应头中包含Sec-WebSocket-Accept
等字段,完成握手; -
握手成功后,
HTTP
连接正式升级为Websocket
连接,客户端与服务器进入全双工通信状态,此时可发送业务数据;
3.2 传输数据:帧格式与消息处理
Websocket
协议采用帧(Frame
)作为数据传输的基本单位,所有数据都被分割为帧进行传输,并且支持文本消息和二进制消息两种类型。文本消息通常用于传输 JSON、字符串等文本数据,二进制消息则适用于图片、音频等二进制数据。
4 Flutter 中 Websocket
的使用
Flutter
应用一般通过官方提供的web_socket_channel
包实现对 Websocket
的支持,该库封装了原生平台的WebSocket实现(Android使用OkHttp,iOS使用NSURLSession),并提供统一的Dart接口。其核心流程可分为连接建立、数据传输、连接管理三个阶段,每个阶段都涉及特定的技术细节和处理逻辑。 以下代码示例展示了 Flutter 中建立 Websocket 连接的基本方式:
4.1 建立连接
go
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
// 1 建立与服务器的Websocket连接
// 步骤1:连接到WebSocket服务器(假设服务器地址为ws://echo.websocket.events)
final _channel = WebSocketChannel.connect('ws://echo.websocket.events');
// 步骤2:监听连接是否建立,
_channel.ready.then((_) {
// 更新已连接的标记位
print('Websocket连接已建立');
}).catchError((error) {
// 这里处理连接失败的一些情况
print('连接建立失败: \$error');
});
4.2 数据传输
-
消息监听:通过
_channel.stream.listen()
订阅消息流,onData
回调会在收到服务器消息时触发。 -
消息发送:
_channel.sink.add()
将消息写入流的接收端(StreamSink
),底层自动封装为WebSocket文本帧并发送。 -
连接关闭:
_channel.sink.close()
发送关闭帧,通知服务器终止连接;onDone
回调会在连接完全关闭后触发。
less
import 'dart:convert';
// 发送文本消息
_channel.sink.add('Hello, Websocket!');
// 发送JSON数据
_channel_channel.sink.add(json.encode({'type': 'message', 'content': 'Flutter Websocket'}));
// 接收消息
// 监听服务器消息(Stream订阅)
_channel.stream.listen(
(message) {
// 收到消息后更新UI
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('收到消息: $message')),
);
},
onError: (error) {
// 处理连接或通信错误(如网络中断)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('错误: $error')),
);
},
onDone: () {
// 连接关闭时触发(如主动调用close()或服务器断开)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('连接已关闭')),
);
},
);
在底层实现中,web_socket_channel
会将发送的消息拆分为符合 Websocket 帧格式的数据包(包含 opcode、掩码、数据长度等信息),并在接收时将帧重组为完整的消息。这种帧机制确保了数据传输的高效性和可靠性,同时支持消息的分片传输 ------ 对于大型消息,可分割为多个帧逐步发送,避免单次传输的数据量过大。
4.3 连接管理
由于网络波动、服务器维护等原因,Websocket
连接可能会意外断开。为保证通信的稳定性,移动应用需要实现完善的连接管理机制,核心包括心跳检测和自动重连。
心跳检测的原理是客户端与服务器定期互发心跳包(通常是一段特定的字符串或空消息),若在规定时间内未收到对方的心跳响应,则判定连接异常。在 Flutter 中可通过定时任务实现:
dart
import 'dart:async';
// 启动心跳检测
Timer.periodic(Duration(seconds: 30), (timer) {
if (channel.closeCode == null) { // 连接未关闭
channel.sink.add('heartbeat'); // 发送心跳包
} else {
timer.cancel(); // 连接已关闭,取消定时任务
}
});
自动重连 则是在连接断开后,通过定时重试的方式重新建立连接。可结合onDone
回调实现:
scss
void _connect() {
final _channel = WebSocketChannel.connect('ws://echo.websocket.events');
_channel.stream.listen(
(message) => print('收到消息: \$message'),
onDone: () {
print('连接断开,尝试重连...');
Future.delayed(Duration(seconds: 5), _connect); // 5秒后重试
);
}
在实际应用中,重连机制还需考虑重连次数限制、网络状态检测(如通过connectivity_plus
包判断设备是否联网)等因素,避免无效的重连尝试消耗过多资源。
5 总结
本文主要从Websocket
的特点出发,介绍了其建立连接,传输数据和管理连接方法的一些使用及基础原理,同时也介绍了如何在Flutter
中使用这一技术。主要是想帮助开发者了解Websocket
,并在有实际业务需求的场景下能正确使用该技术。 当然在实际开发中,还需结合具体业务场景优化通信策略,如合理设计消息格式、控制数据传输频率等,以在实时性和资源消耗之间取得最佳平衡。