flutter開發(fā)實戰(zhàn)-長鏈接WebSocket使用stomp協(xié)議stomp_dart_client
在app中經(jīng)常會使用長連接進(jìn)行消息通信,這里記錄一下基于websocket使用stomp協(xié)議的使用。
一、stomp:流文本定向消息協(xié)議
1.1 stomp介紹
stomp,Streaming Text Orientated Message Protocol,是流文本定向消息協(xié)議,是一種為MOM(Message Oriented Middleware,面向消息的中間件)設(shè)計的簡單文本協(xié)議。
它提供了一個可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進(jìn)行交互,類似于OpenWire(一種二進(jìn)制協(xié)議)。
1.2 協(xié)議支持
stomp 1.0
stomp 1.1 (including heart-beating)
1.3 stomp frame(幀)
stomp frame(幀)對象包括command、headers、body
command和headers屬性始終會被定義,若頭部信息時,headers參數(shù)可為{},body也可能為空
二、flutter上使用stomp
2.1 引入庫stomp_dart_client
flutter上使用stomp時,需要在pubspec.yaml引入庫如下
# stomp協(xié)議長鏈接
stomp_dart_client: ^0.4.4
stomp: ^0.8.0
2.2 實現(xiàn)websocketmanager封裝stomp
// 管理長鏈接socket, stomp協(xié)議
import 'package:stomp_dart_client/stomp.dart';
import 'package:stomp_dart_client/stomp_config.dart';
import 'package:stomp_dart_client/stomp_frame.dart';
// 接收到stomp協(xié)議的frame的callback
typedef OnFrameCallback = void Function(StompFrame);
enum StompState {
IDLE,
CREATED,
CONNECTING,
CONNECTED,
RECONNECTING,
DISCONNECTED,
ERROR,
}
class WebSocketStompManager {
//私有構(gòu)造函數(shù)
WebSocketStompManager._internal();
//保存單例
static WebSocketStompManager _singleton = WebSocketStompManager._internal();
//工廠構(gòu)造函數(shù)
factory WebSocketStompManager() => _singleton;
// 訂閱的Subscription
// 保存訂閱, id: dynamic
Map _subscriptions = Map<String, dynamic>();
// stomp的headers信息
Map<String, String>? _headers = Map<String, String>();
// 是否連接
StompState _stompState = StompState.IDLE;
// 當(dāng)前連接的Url
String _urlString = '';
// StompClient client
StompClient? _client;
// 創(chuàng)建連接
void createConnect(String urlString, Map<String, String> headers) {
_urlString = urlString;
_headers = _headers;
_client?.deactivate();
_client = null;
_client = StompClient(
config: StompConfig(
url: urlString,
// connectionTimeout: Duration(seconds: 10),
// stompConnectHeaders: {
// 'upgraded': 'websocket',
// },
// webSocketConnectHeaders: {
// 'upgraded': 'websocket',
// },
// 連接
beforeConnect: beforeConnectCallback,
onConnect: onConnectCallback,
onDisconnect: onDisconnectCallback,
onStompError: onStompErrorCallback,
onUnhandledFrame: onUnhandledFrameCallback,
onUnhandledMessage: onUnhandledMessageCallback,
onUnhandledReceipt: onUnhandledReceiptCallback,
onWebSocketError: onWebSocketErrorCallback,
onWebSocketDone: onWebSocketDoneCallback,
onDebugMessage: onDebugMessageCallback,
));
}
/// beforeConnect:未來 在建立連接之前將等待的異步函數(shù)。
Future<void> beforeConnectCallback() async {
// 在建立連接之前將等待的異步函數(shù)。
print("beforeConnectCallback 在建立連接之前將等待的異步函數(shù)。");
print('waiting to connect...');
// await Future.delayed(Duration(milliseconds: 200));
print('connecting...');
}
/// onClientNotCreateCallback, client未創(chuàng)建
void onClientNotCreateCallback() {
// client未創(chuàng)建
print("onClientNotCreateCallback client未創(chuàng)建");
}
/// onConnect:函數(shù)(StompFrame) 客戶端連接成功調(diào)用的函數(shù)
void onConnectCallback(StompFrame connectFrame) {
// client is connected and ready
// 如果連接成功
print(
"onConnectCallback 客戶端連接成功調(diào)用的函數(shù):"
"${connectFrame.toString()},"
"${connectFrame.command},"
"${connectFrame.headers},"
"${connectFrame.body}"
);
}
/// onDisconnect:函數(shù)(StompFrame) 客戶端預(yù)期斷開連接時調(diào)用的函數(shù)
void onDisconnectCallback(StompFrame p1) {
// 客戶端預(yù)期斷開連接時調(diào)用的函數(shù)
print("onDisconnectCallback 客戶端預(yù)期斷開連接時調(diào)用的函數(shù):${p1.toString()}");
}
/// onStompError:函數(shù)(StompFrame) 當(dāng) stomp 服務(wù)器發(fā)送錯誤幀時要調(diào)用的函數(shù)
void onStompErrorCallback(StompFrame p1) {
// 當(dāng) stomp 服務(wù)器發(fā)送錯誤幀時要調(diào)用的函數(shù)
print("onStompErrorCallback 當(dāng) stomp 服務(wù)器發(fā)送錯誤幀時要調(diào)用的函數(shù):${p1.toString()}");
}
/// onUnhandledFrame:函數(shù)(StompFrame) 服務(wù)器發(fā)送無法識別的幀時調(diào)用的函數(shù)
void onUnhandledFrameCallback(StompFrame p1) {
// 服務(wù)器發(fā)送無法識別的幀時調(diào)用的函數(shù)
print("onUnhandledFrameCallback 服務(wù)器發(fā)送無法識別的幀時調(diào)用的函數(shù):${p1.toString()}");
}
/// onUnhandledMessage:函數(shù)(StompFrame) 當(dāng)訂閱消息沒有處理程序時要調(diào)用的函數(shù)
void onUnhandledMessageCallback(StompFrame p1) {
// 當(dāng)訂閱消息沒有處理程序時要調(diào)用的函數(shù)
print("onUnhandledMessageCallback 當(dāng)訂閱消息沒有處理程序時要調(diào)用的函數(shù):${p1.toString()}");
}
/// onUnhandledReceipt:函數(shù)(StompFrame) 當(dāng)接收消息沒有注冊觀察者時調(diào)用的函數(shù)
void onUnhandledReceiptCallback(StompFrame p1) {
// 當(dāng)接收消息沒有注冊觀察者時調(diào)用的函數(shù)
print("onUnhandledReceiptCallback 當(dāng)接收消息沒有注冊觀察者時調(diào)用的函數(shù):${p1.toString()}");
}
/// onWebSocketError:函數(shù)(動態(tài)) 當(dāng)?shù)讓?WebSocket 拋出錯誤時要調(diào)用的函數(shù)
void onWebSocketErrorCallback(dynamic error) {
// 當(dāng)?shù)讓?WebSocket 拋出錯誤時要調(diào)用的函數(shù)
print(
"onWebSocketErrorCallback 當(dāng)?shù)讓?WebSocket 拋出錯誤時要調(diào)用的函數(shù):${error.toString()}");
}
/// onWebSocketDone:函數(shù)() 當(dāng)?shù)讓?WebSocket 完成/斷開連接時要調(diào)用的函數(shù)
void onWebSocketDoneCallback() {
// 當(dāng)?shù)讓?WebSocket 完成/斷開連接時要調(diào)用的函數(shù)
print("onWebSocketDoneCallback 當(dāng)?shù)讓?WebSocket 完成/斷開連接時要調(diào)用的函數(shù)");
}
/// onDebugMessage:函數(shù)(字符串) 為內(nèi)部消息處理程序生成的調(diào)試消息調(diào)用的函數(shù)
void onDebugMessageCallback(String p1) {
// 為內(nèi)部消息處理程序生成的調(diào)試消息調(diào)用的函數(shù)
print("onDebugMessageCallback 為內(nèi)部消息處理程序生成的調(diào)試消息調(diào)用的函數(shù):${p1}");
}
// 連接
void connect() {
// connect連接
if (_client != null) {
_client?.activate();
} else {
// 未創(chuàng)建client
onClientNotCreateCallback();
}
}
// Subscribe
void subscribe(String destination, OnFrameCallback? onFrameCallback) {
if (_client != null) {
dynamic unsubscribeFn = _client?.subscribe(
destination: destination,
headers: _headers,
callback: (frame) {
// Received a frame for this subscription
print(frame.body);
if (onFrameCallback != null) {
onFrameCallback(frame);
}
});
_subscriptions.putIfAbsent(destination, () => unsubscribeFn);
} else {
// 未創(chuàng)建client
onClientNotCreateCallback();
}
}
// client.subscribe(...) returns a function which can be called with an optional map of headers
void unsubscribe(String destination) {
if (_client != null) {
dynamic unsubscribeFn = _subscriptions[destination];
unsubscribeFn(unsubscribeHeaders: {});
} else {
// 未創(chuàng)建client
onClientNotCreateCallback();
}
}
// client.subscribe(...) returns a function which can be called with an optional map of headers
void unsubscribeAll() {
// 退訂所有
// 調(diào)用 Map 對象的 keys 成員 , 返回一個由 鍵 Key 組成的數(shù)組
for (var destination in _subscriptions.keys){
unsubscribe(destination);
}
}
void send(String destination, String? message) {
if (_client != null) {
_client?.send(destination: destination, body: message, headers: _headers);
} else {
// 未創(chuàng)建client
onClientNotCreateCallback();
}
}
void disconnect() {
if (_client != null) {
_client?.deactivate();
} else {
// 未創(chuàng)建client
onClientNotCreateCallback();
}
}
}
2.3 使用websocketmanager收發(fā)消息
創(chuàng)建頁面進(jìn)行消息收發(fā)
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
// TODO: implement initState
super.initState();
}
void dispose() {
// TODO: implement dispose
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter(model);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Wrap(
spacing: 8.0, // 主軸(水平)方向間距
runSpacing: 4.0, // 縱軸(垂直)方向間距
alignment: WrapAlignment.center, //沿主軸方向居中
children: [
TextButton(
onPressed: stompCreate,
child: Container(
color: Colors.black26,
child: Text(
'stomp創(chuàng)建',
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
TextButton(
onPressed: stompConnect,
child: Container(
color: Colors.black26,
child: Text(
'stomp連接',
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
TextButton(
onPressed: stompSubscribe,
child: Container(
color: Colors.black26,
child: Text(
'stomp訂閱',
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
TextButton(
onPressed: stompUnSubscribe,
child: Container(
color: Colors.black26,
child: Text(
'stomp退訂',
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
TextButton(
onPressed: stompSendMessage,
child: Container(
color: Colors.black26,
child: Text(
'stomp發(fā)送消息',
style: Theme.of(context).textTheme.bodyMedium,
),
),
)
],
),
],
),
),
);
}
// 測試stomp長鏈接
void stompCreate() {
// 創(chuàng)建stompClint
WebSocketStompManager().createConnect("ws://192.168.100.25:8080/test-endpoint/websocket", {});
}
void stompConnect() {
WebSocketStompManager().connect();
}
void stompSubscribe() {
WebSocketStompManager()
.subscribe("/topic/echo", (p0) {
print("stompSubscribe 1:$p0");
});
WebSocketStompManager()
.subscribe("/topic/echo", (p0) {
print("stompSubscribe 2:$p0");
});
}
void stompUnSubscribe() {
WebSocketStompManager().unsubscribeAll();
}
void stompSendMessage() {
WebSocketStompManager().send("/app/echo", "haha message from dart");
}
}
至此實現(xiàn)了flutter開發(fā)實戰(zhàn)-長鏈接WebSocket 使用stomp協(xié)議,進(jìn)行消息發(fā)送、消息接收。
2.4 注意事項
由于stomp_dart_client不支持https,如果使用WebSocketStompManager().createConnect(“ws://192.168.100.25:8080/test-endpoint/websocket”, {});
會報告錯誤“Not support Https shceme”,所以這里要使用ws或者wss。
三、小結(jié)
至此實現(xiàn)了flutter開發(fā)實戰(zhàn)-長鏈接WebSocket 使用stomp協(xié)議,進(jìn)行消息發(fā)送、消息接收。stomp實現(xiàn)的庫stomp_dart_client來實現(xiàn)該功能。文章來源:http://www.zghlxwxcb.cn/news/detail-544779.html
學(xué)習(xí)記錄,每天不停進(jìn)步。文章來源地址http://www.zghlxwxcb.cn/news/detail-544779.html
到了這里,關(guān)于flutter開發(fā)實戰(zhàn)-長鏈接WebSocket使用stomp協(xié)議stomp_dart_client的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!