国庆假期,整整七天,我使用Flutter终于做出了即时通信!!!😤

简介: 7天时间,踩了很多很多坑,终于完成了Flutter WebSocket即时通讯的功能,个人觉得蛮有学习意义的,来看看吧!哦对了,求个赞,哥哥们,国庆假期就贡献在这里啦~

前言:在这个假期,我完成了一个小Demo,Flutter 与 Springboot 进行websocket的通讯,为啥想要去做这个Demo呢,主要是在各大平台以及google搜索后发现,没有一个详细的例子来教大家进行一对一、一对多的通讯,大多数都是教你怎么连接,却没有教你怎么去进行下一步的功能实现,于是我利用了五天的假期,踩了无数的坑,终于是完成了它,所以,点个赞吧,不容易啊,兄弟们😭

git仓库还没有搭(可以看一下文末通知),服务端我都帮兄弟们架包打好了,运行一下就行,运行方法在文末简单叙述了😎

服务端分析:Springboot WebSocket 即时通讯

先上效果图(我自己搜索这样功能性的问题时,没有效果图基本上都是不想看的):

tt0.top-039531.gif

屏幕截图 2021-10-05 153152.jpg

即时通讯最重要的功能是完成了(发送文字信息)

阅读本文的注意点:

1.需要一点WebSocket的原理知识

2.Flutter使用WebSocket的方式,本文使用 'dart:io' ,大家也可以使用插件

正文:

1.WebSocket的简单原理

很多同学在第一次碰到这个协议时会发现它与HTTP相似,于是就会问,我们已经有了 HTTP 协议,为什么还需要WebSocket?它有什么特殊的地方呢?

其实是因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

img

而WebSocket首先是一个持久化的协议,它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,这个协议非常适合即时通讯或者消息的推送。

img

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');
          }, onError: () {
            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');
          }, onError: () {
            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,包含一个返回按钮,用户信息以及状态,还有一个设置按钮(没有什么难点就不放代码了)

1633651654(1).png

  • 双方信息处理

1633651683(1).png

这里有个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

1633605454(1).png

一行为一个联系人模块,其内包含用户头像,用户姓名,即时通讯内容,以及上一次对话的时间,点击每行跳转到相对应的聊天框。

封装处理:

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,        onError: (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.该文章项目运行步骤

  • 下载代码压缩包,解压后的目录是这样的:

1633654613(1).png

其中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,然后在代码的这里修改

1633656694(1).png

第六步:运行后的操作

运行后会先进入登录界面,这里第一只手机输入0 ,第二只手机输入1,因为服务端默认从0开始(给用户分配id),因为没有数据库。

1633656155(1).png

这样进入就可以了,然后就可以像效果图一样开始交流

服务端有问题可以参考这篇文章:Springboot WebSocket 即时通讯

通知:https://juejin.cn/pin/7034450183706378270

相关文章
阿云漫画 | 被KPI追杀的日子...
编者按: 上学时,有分数线来考核我们,工作后,有KPI来考核我们。那么,如果我们的人生也如KPI般,你的幸福指数又是多少呢?
127 0
|
监控 前端开发 jenkins
新来个技术总监,给团队引入了这款开发神器,同事直呼哇塞
带团队时间久了,就能发现整个 Team 都渐渐疲了。前两年老板还专门买了个系统搞 OKR,现在也不大提了;Scrum 我们也搞了,用起来也就那样;项目管理工具试了好几个,禅道、Worktile、现在用 Coding,反正有一个能用的就行;微服务化改造从去年开始在吭哧吭哧搞,我们自己搞得觉得很厉害,但业务部门那边就觉得没啥差别,搞不懂你们研发部门每天在弄些什么,赶紧做我们提的需求要紧。
新来个技术总监,给团队引入了这款开发神器,同事直呼哇塞
|
机器学习/深度学习 人工智能 自然语言处理
【paddlehubOCR项目】网课手酸酸,眼花花,救星来啦!
大家好这里是三岁,今天给大家带来的是在AiStudio项目平台的一个精选项目,虽然很短,但是效果拔群,使用到了最近特别火的paddleOCR~~~
271 0
【paddlehubOCR项目】网课手酸酸,眼花花,救星来啦!
|
存储 JSON 前端开发
国庆假期,整整七天,我使用SpringBoot终于做出了即时通信!!!😤
7天时间,踩了很多很多坑,终于完成了SpringBoot WebSocket即时通讯的功能,个人觉得蛮有学习意义的,来看看吧!哦对了,求个赞,哥哥们,国庆假期就贡献在这里啦~
|
监控 小程序 安全
热饭的测开成果盘点第十八期:微信小程序平台
本期介绍的是一款测试微信小程序的自动化平台,功能比较简单,算是我给我带着我小徒弟(txn) 练手的第一个学习用平台。
热饭的测开成果盘点第十八期:微信小程序平台
|
存储 移动开发 JSON
国庆节到了,实现一个生成国庆风头像小工具,详解实现过程!
明天就是国庆节了,最近看到好多好友换了国庆风的头像,感觉这个挺有意思,就找到了类似的源码研究了一番,并进行了改造(并非原创,只是进行了改造,只要想分享一下实现思路)。下面就来看看如何实现一键生成国庆风头像小工具。​
275 0
|
机器人
同事“带薪摸鱼”的窍门,全藏在这1个办公小技巧里了(RPA)
同事“带薪摸鱼”的窍门,全藏在这1个办公小技巧里了(RPA)
157 0
|
安全 定位技术 Android开发
跟《原神》学学怎么让用户上瘾
我刚玩一周,抽卡还不到20次的时候,就抽到了当期up的五星角色“胡桃”,一查发现这个角色很厉害,有人抽了上百次都没抽到,白板号都能卖到5、600元,觉得自己运气很好,赚到了的感觉,更加卖力地玩下去把角色养大。
1447 0
跟《原神》学学怎么让用户上瘾
|
移动开发 安全 关系型数据库
黑客马拉松经验谈:一个周末你能做出有趣、有用的服务吗?
“黑客马拉松”(Hackathon),是黑客 + 马拉松(Hack + Marathon)的组合字,大致上就是几个人聚在一起以马拉松的方式进行一段长时间的 Hack 活动,像是台湾 Yahoo! 办过 Open Hack Day、台湾微软办过 HTML5 或 IE 浏览器的黑客松活动,这类型的活动,实际进行的时间从半天、一天到两天一夜的长度都有。
539 0
黑客马拉松经验谈:一个周末你能做出有趣、有用的服务吗?
|
存储 编解码 UED
短视频平台开发时那些容易掉进去的“深坑”
互联网市场中之所以存在那么多优质的app,都是经过无数次的测试、优化和更新完成的。要想开发一款优质的app并没有那么容易。比如在短视频平台开发时,不仅需要考虑音视频是否同步、首屏打开速度等问题,还需要考虑界面的UI和功能等是否贴近用户需求。