Flutter:WebSocket封装-实现心跳、重连机制
前言
-
Flutter简介
Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 native扩展。同时 Flutter还使用 Native引擎渲染视图,这无疑能为用户提供良好的体验。
-
WebSocket简介
Http协议是无状态的,只能由客户端主动发起,服务端再被动响应,服务端无法向客户端主动推送内容,并且一旦服务器响应结束,链接就会断开(见注解部分),所以无法进行实时通信。WebSocket协议正是为解决客户端与服务端实时通信而产生的技术,现在已经被主流浏览器支持,所以对于Web开发者来说应该比较熟悉了,Flutter也提供了专门的包来支持WebSocket协议。
注意:Http协议中虽然可以通过keep-alive机制使服务器在响应结束后链接会保持一段时间,但最终还是会断开,keep-alive机制主要是用于避免在同一台服务器请求多个资源时频繁创建链接,它本质上是支持链接复用的技术,而并非用于实时通信,读者需要知道这两者的区别。
WebSocket协议本质上是一个基于tcp的协议,它是先通过HTTP协议发起一条特殊的http请求进行握手后,如果服务端支持WebSocket协议,则会进行协议升级。WebSocket会使用http协议握手后创建的tcp链接,和http协议不同的是,WebSocket的tcp链接是个长链接(不会断开),所以服务端与客户端就可以通过此TCP连接进行实时通信。有关WebSocket协议细节,读者可以看RFC文档,下面我们重点看看Flutter中如何使用WebSocket。
话不多说,直接撸代码
添加依赖:
web_socket_channel: ^1.1.0 # WebSocket
新建web_socket_utility.dart
工具类:
import 'dart:async';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
/// WebSocket地址
const String _SOCKET_URL = 'ws://121.40.165.18:8800';
/// WebSocket状态
enum SocketStatus {
SocketStatusConnected, // 已连接
SocketStatusFailed, // 失败
SocketStatusClosed, // 连接关闭
}
class WebSocketUtility {
/// 单例对象
static WebSocketUtility _socket;
/// 内部构造方法,可避免外部暴露构造函数,进行实例化
WebSocketUtility._();
/// 获取单例内部方法
factory WebSocketUtility() {
// 只能有一个实例
if (_socket == null) {
_socket = new WebSocketUtility._();
}
return _socket;
}
IOWebSocketChannel _webSocket; // WebSocket
SocketStatus _socketStatus; // socket状态
Timer _heartBeat; // 心跳定时器
num _heartTimes = 3000; // 心跳间隔(毫秒)
num _reconnectCount = 60; // 重连次数,默认60次
num _reconnectTimes = 0; // 重连计数器
Timer _reconnectTimer; // 重连定时器
Function onError; // 连接错误回调
Function onOpen; // 连接开启回调
Function onMessage; // 接收消息回调
/// 初始化WebSocket
void initWebSocket({Function onOpen, Function onMessage, Function onError}) {
this.onOpen = onOpen;
this.onMessage = onMessage;
this.onError = onError;
openSocket();
}
/// 开启WebSocket连接
void openSocket() {
closeSocket();
_webSocket = IOWebSocketChannel.connect(_SOCKET_URL);
print('WebSocket连接成功: $_SOCKET_URL');
// 连接成功,返回WebSocket实例
_socketStatus = SocketStatus.SocketStatusConnected;
// 连接成功,重置重连计数器
_reconnectTimes = 0;
if (_reconnectTimer != null) {
_reconnectTimer.cancel();
_reconnectTimer = null;
}
onOpen();
// 接收消息
_webSocket.stream.listen((data) => webSocketOnMessage(data),
onError: webSocketOnError, onDone: webSocketOnDone);
}
/// WebSocket接收消息回调
webSocketOnMessage(data) {
onMessage(data);
}
/// WebSocket关闭连接回调
webSocketOnDone() {
print('closed');
reconnect();
}
/// WebSocket连接错误回调
webSocketOnError(e) {
WebSocketChannelException ex = e;
_socketStatus = SocketStatus.SocketStatusFailed;
onError(ex.message);
closeSocket();
}
/// 初始化心跳
void initHeartBeat() {
destroyHeartBeat();
_heartBeat =
new Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
sentHeart();
});
}
/// 心跳
void sentHeart() {
sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
}
/// 销毁心跳
void destroyHeartBeat() {
if (_heartBeat != null) {
_heartBeat.cancel();
_heartBeat = null;
}
}
/// 关闭WebSocket
void closeSocket() {
if (_webSocket != null) {
print('WebSocket连接关闭');
_webSocket.sink.close();
destroyHeartBeat();
_socketStatus = SocketStatus.SocketStatusClosed;
}
}
/// 发送WebSocket消息
void sendMessage(message) {
if (_webSocket != null) {
switch (_socketStatus) {
case SocketStatus.SocketStatusConnected:
print('发送中:' + message);
_webSocket.sink.add(message);
break;
case SocketStatus.SocketStatusClosed:
print('连接已关闭');
break;
case SocketStatus.SocketStatusFailed:
print('发送失败');
break;
default:
break;
}
}
}
/// 重连机制
void reconnect() {
if (_reconnectTimes < _reconnectCount) {
_reconnectTimes++;
_reconnectTimer =
new Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
openSocket();
});
} else {
if (_reconnectTimer != null) {
print('重连次数超过最大次数');
_reconnectTimer.cancel();
_reconnectTimer = null;
}
return;
}
}
}
使用方法
import 'package:my_app/utils/web_socket_utility.dart';
WebSocketUtility().initWebSocket(onOpen: () {
WebSocketUtility().initHeartBeat();
}, onMessage: (data) {
print(data);
}, onError: (e) {
print(e);
});