小知識,大挑戰!本文正在參與「程式員必備小知識」創作活動
本文已參與「掘力星計劃」,贏取創作大禮包,挑戰創作激勵金,
前言:在這個假期,我完成了一個小Demo,Flutter 與 Springboot 進行websocket的通訊,為啥想要去做這個Demo呢,主要是在各大平臺以及google搜索后發現,沒有一個詳細的例子來教大家進行一對一、一對多的通訊,大多數都是教你怎么連接,卻沒有教你怎么去進行下一步的功能實作,于是我利用了五天的假期,踩了無數的坑,終于是完成了它,所以,點個贊吧,不容易啊,兄弟們😭
原始碼在文章最后,直接運行就完事,服務端我都幫兄弟們架包打好了,運行一下就行,運行方法在文末簡單敘述了😎
服務端分析:Springboot WebSocket 即時通訊
先上效果圖(我自己搜索這樣功能性的問題時,沒有效果圖基本上都是不想看的):


即時通訊最重要的功能是完成了(發送文字資訊)
閱讀本文的注意點:
1.需要一點WebSocket的原理知識
2.Flutter使用WebSocket的方式,本文使用 ‘dart:io’ ,大家也可以使用插件
正文:
1.WebSocket的簡單原理
很多同學在第一次碰到這個協議時會發現它與HTTP相似,于是就會問,我們已經有了 HTTP 協議,為什么還需要WebSocket?它有什么特殊的地方呢?
其實是因為 HTTP 協議有一個缺陷:通信只能由客戶端發起,

而WebSocket首先是一個持久化的協議,它的最大特點就是,服務器可以主動向客戶端推送資訊,客戶端也可以主動向服務器發送資訊,這個協議非常適合即時通訊或者訊息的推送,

2.Flutter中怎么使用WebSocket
有兩種方式:
1.Flutter自帶的 ‘dart:io’
-
連接WebSocket服務器
Future<WebSocket> webSocketFuture = WebSocket.connect('ws://192.168.13.32:9090'); //connect中放服務端地址 -
存放WebSocket.connect回傳的物件
static WebSocket _webSocket; -
發送訊息
_webSocket.add('發送訊息內容'); -
監聽接收訊息,呼叫listen方法
void onData(dynamic content) { print('收到訊息:'+content); } _webSocket.listen(onData, onDone: () { print('onDone'); }, one rror: () { print('onError'); }, cancelOnError: true); -
例子:
webSocketFuture.then((WebSocket ws) { _webSocket = ws; void onData(dynamic content) { print('收到新的訊息'); } // 呼叫add方法發送訊息 _webSocket.add('message'); // 監聽接收訊息,呼叫listen方法 _webSocket.listen(onData, onDone: () { print('onDone'); }, one rror: () { print('onError'); }, cancelOnError: true); }); -
關閉WebSocket連接
_webSocket.close();
2.第三方插件庫實作 WebSocket
基本使用步驟也都是:連接 WebSocket 服務器、發送訊息、接收訊息、關閉 WebSocket 連接,
- 在專案的 pubspec.yaml 里加入參考:
dependencies:
web_socket_channel: 官網最新版本
- 匯入包:
import 'package:web_socket_channel/io.dart';
- 連接 WebSocket 服務器:
var channel = IOWebSocketChannel.connect("ws://192.168.13.32:9090");
通過IOWebSocketChannel我們便可以進行各種操作
- 發送訊息:
channel.sink.add("connected!");
- 監聽接收訊息:
channel.stream.listen((message) { print('收到訊息:' + message); });
- 關閉 WebSocket 連接:
channel.sink.close();
以上就是 Flutter 通過第三方插件庫實作 WebSocket 通信功能的基本步驟,
3.聯系人界面以及對話界面的UI實作
我的小部件都進行了封裝,原始碼請看文章最后,分析部分只放重要代碼
-
對話框ui處理
頂部使用appbar,包含一個回傳按鈕,用戶資訊以及狀態,還有一個設定按鈕(沒有什么難點就不放代碼了)

- 雙方資訊處理

這里有個ui處理難點,就是分析資訊是誰發出的,是自己還是對方呢
這里我選擇在每條資訊Json格式的末尾加上一個判斷符:messageType,”receiver“代表對方發的資訊,”sender“代表是自己發的
ui處理:
ListView.builder( itemCount: UserMessage.messages.length, //總發送資訊的條數 shrinkWrap: true, padding: const EdgeInsets.only(top: 10, bottom: 10), physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return Container( padding: const EdgeInsets.only( left: 14, right: 14, top: 10, bottom: 10), child: Align( alignment: (UserMessage.messages[index].messageType == "receiver" ? Alignment.topLeft : Alignment.topRight), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: (UserMessage.messages[index].messageType == "receiver" ? Colors.grey.shade200 : Colors.blue[200]), ), padding: const EdgeInsets.all(16), child: Text( UserMessage.messages[index].messageContent, style: TextStyle(fontSize: 15), ))), ); },),
- 單個聯系人的ui

一行為一個聯系人模塊,其內包含用戶頭像,用戶姓名,即時通訊內容,以及上一次對話的時間,點擊每行跳轉到相對應的聊天框,
封裝處理:
class ConversationList extends StatefulWidget { String name; //用戶姓名 String messageText; //即時內容 String imageUrl; //用戶頭像 String time;//上一次對話時間 bool isMessageRead; //用于處理字體大小 ConversationList( {Key? key, required this.name, required this.messageText, required this.imageUrl, required this.time, required this.isMessageRead}) : super(key: key); @override _ConversationListState createState() => _ConversationListState();}
詳細布局:
return GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context){ return ChatDetailPage(name:widget.name,userImageUrl:widget.imageUrl); })); }, child: Container( padding: const EdgeInsets.only(left: 16, right: 16, top: 10, bottom: 10), child: Row( children: <Widget>[ Expanded( child: Row( children: <Widget>[ CircleAvatar( backgroundImage: AssetImage(widget.imageUrl), maxRadius: 30, ), const SizedBox( width: 16, ), Expanded( child: Container( color: Colors.transparent, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( widget.name, style: const TextStyle(fontSize: 16), ), const SizedBox( height: 6, ), Text( widget.messageText, style: TextStyle( fontSize: 13, color: Colors.grey.shade600, fontWeight: widget.isMessageRead ? FontWeight.bold : FontWeight.normal), ), ], ), ), ), ], ), ), Text( widget.time, style: TextStyle( fontSize: 12, fontWeight: widget.isMessageRead ? FontWeight.bold : FontWeight.normal), ), ], ), ), );}
-
串列實作:
這里簡單使用ListView.builder
ListView.builder( itemCount: chatUsers.length, shrinkWrap: true, padding: const EdgeInsets.only(top: 16), physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index){ return ConversationList( name: chatUsers[index].name, messageText: chatUsers[index].messageText, imageUrl: chatUsers[index].imageURL, time: chatUsers[index].time, isMessageRead: (index == 0 || index == 3)?true:false, ); },),
ui的難點部分在這里就分析完成了
4.Flutter WebSocket處理
因為是個Demo,封裝的資訊比較簡單
-
對發送的資訊進行封裝
class ChatMessage { String messageContent; String messageType; ChatMessage({required this.messageContent, required this.messageType});} -
對基本資訊,以及用戶資訊進行封裝,
class UserMessage{ static int userId = 0; static String socketUrl = "ws://192.168.10.104:9090/websocket/"; ///將對話暫存在這里 static List<ChatMessage> messages = [ ];} -
初始化時連接服務器并且對其進行監聽
當資料回傳時,保存到記憶體中
//連接wensocket并且監聽void connect(String userName) { Future<WebSocket> futureWebSocket = WebSocket.connect(UserMessage.socketUrl + "/$userName"); //socket地址 futureWebSocket.then((WebSocket ws) { _webSocket = ws; _webSocket.readyState; // 監聽事件 void onData(dynamic content) { print('收到訊息:' + content); setState(() { if (content.toString().substring(0, 1) == UserMessage.userId.toString()) { ///自己發送的訊息(服務端沒有完善) } else { UserMessage.messages.add(ChatMessage( messageContent: content.toString().substring( 2, ), messageType: "receiver")); } }); } _webSocket.listen(onData, one rror: (a) => print("error"), onDone: () => print("done")); });} -
發送資訊處理
對資料需要將json物件轉換為json字串
// 向服務器發送訊息void sendMessage(dynamic message) { print(convert.jsonEncode(message)); _webSocket.add(convert.jsonEncode(message);}onPressed: () { var toUser = "0"; //服務端沒有完善,這里固定用戶id了 if (UserMessage.userId == 0) { toUser = "1"; } else { toUser = "0"; } var message = { "msg": _controller.text, "toUser": toUser, "type": 1 }; ///傳遞資訊 sendMessage(message); //發送資訊 setState(() { //更新ui UserMessage.messages.add( ChatMessage( messageContent: _controller.text, messageType: "sender"), ); _controller.clear(); //清除輸入框內文字 });}, -
在退出頁面時,關閉WebSocket連接
@overridevoid dispose() { // TODO: implement dispose super.dispose(); closeSocket();}
5.該文章專案運行步驟
- 下載代碼壓縮包,解壓后的目錄是這樣的:

其中images是圖片,lib是源代碼,jar包是服務端
第一步:創建一個新專案,源代碼是使用了空安全的,也可以創建不是空安全的專案,改一下代碼即可
第二步:將images與lib復制進去
第三步:在pubspec.yaml中配置靜態圖片
assets: - images/
第四步:運行.jar包
切換到包存放的地址,服務端的埠為9090,如果與各位的埠沖突可以修改服務端代碼,或者停止你在使用9090的這個埠
java -jar 包名.jar
查找埠:
netstat -aon|findstr 9090
查看指定 PID 的行程
tasklist|findstr 10021
結束行程
強制(/F引數)殺死 pid 為 10021的所有行程包括子行程(/T引數):
taskkill /T /F /PID 9088
第五步:在cmd中查找自己的ip,然后在代碼的這里修改

第六步:運行后的操作
運行后會先進入登錄界面,這里第一只手機輸入0 ,第二只手機輸入1,因為服務端默認從0開始(給用戶分配id),因為沒有資料庫,

這樣進入就可以了,然后就可以像效果圖一樣開始交流
服務端有問題可以參考這篇文章:Springboot WebSocket 即時通訊
原始碼看評論區哈,這樣的小Demo就沒有建倉庫了😘
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/307208.html
標籤:其他
上一篇:Nignx優化與防盜鏈
下一篇:如何將命令列引數傳遞給Cake(Frosting),而這些引數已經被System.CommandLine收集了?
