uniapp解决多条socket连接冲突问题

适用平台-Android(IOS和小程序端请自行测试)
  • 因为公司暂时只规划了android端的开发,所以本开发者并没有对其他端没有进行尝试,如果是开发IOS和小程序的小伙伴请自行尝试,目测是没有太大问题。
前期准备
  • HBuilderx做uniapp开发想必这个工具都不陌生,就不多说了;
  • 环信IM小程序端的SDK-为什么选择这个SDK呢?嘿,等下我再说;
摸着石头过河

1、 当接到项目的时候我最先尝试的是原生的STOMP协议,在浏览器上完美的实现,当时感觉人生一片美好,Soeasy啊有木有!某一天心血来潮把手机插上看了一眼,看到控制台疯狂报错的一瞬间我慌了,然后就是各种的排查,和找解决的方法,并且使用官方提供的uni.connectSocket()方法来创建socket连接在APP端死活不给面子,然后无奈放弃。

  • 基于STOMP的小DOME,适用于web端
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js" charset="utf-8"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js" charset="utf-8"></script>
<script>

    var stompClient = null;

    //加载完浏览器后  调用connect(),打开双通道
    $(function(){
        //打开双通道
        connect()
    })

    //强制关闭浏览器  调用websocket.close(),进行正常关闭
    window.onunload = function() {
        disconnect()
    }

    var auth="Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInJvbGUiOjEsImlhdCI6MTU4OTM1NzA1MCwiZXhwIjoxNTkwNTY2NjUwfQ.0KeeyGwQmOs42Iv_iYBbK2BBvi9BcH47RW2OGTwOH-g";

    //打开双通道
    function connect(){
        // var socket = new WebSocket('ws:localhost:8080/admin/hello'); //连接SockJS的endpoint名称为"endpointAric"
        // stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
        var url = "ws:localhost:8080/admin/hello";
        stompClient = Stomp.client(url);
        //设置心跳
        // client will send heartbeats every 20000ms
        // stompClient.heartbeat.outgoing = 2000;
        // // client does not want to receive heartbeats from the server
        // stompClient.heartbeat.incoming = 2000;
        stompClient.connect({"Authorization":auth},function(frame){//连接WebSocket服务端

            console.log('Connected:' + frame);
            //广播接收信息
            stompTopic();
            //私聊天监听消息
            stompQueue();

        },function (error) {
            console.log('ConnectedError:' + error);
            //发生错误,可以在此处进行重连
        });



    }


    //关闭双通道
    function disconnect(){
        if(stompClient != null) {
            stompClient.disconnect();
        }
        console.log("Disconnected");
    }

    //广播(一对多)
    function stompTopic(){
        //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(广播接收信息)
        stompClient.subscribe('/topic/getResponse',function(response){
            // var message=JSON.parse(response.body);
            console.log(response)

        },{"Authorization":auth})

        //订阅错误
        stompClient.subscribe('/topic/errors',function(response){
            // var message=JSON.parse(response.body);
            console.log(response)

        },{"Authorization":auth})
        //订阅错误私发
        stompClient.subscribe('/user/' + 1 + '/errors',function(response){
            // var message=JSON.parse(response.body);
            console.log(response)

        },{"Authorization":auth})
    }

    //列队(一对一)
    function stompQueue(){
        //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(队列接收信息)
        stompClient.subscribe('/user/' + 1 + '/alone/getResponse',function(response){
            // var message=JSON.parse(response.body);
            //展示一对一的接收的内容接收
            console.log(response);
        },{"Authorization":"私发订阅"});
    }



    //群发
    function sendMassMessage(){
        var postValue={};
        postValue.name="1";
        postValue.chatValue="2";
        stompClient.send("/massRequest",{"Authorization":"群发"},JSON.stringify(postValue));
    }
    //单独发
    function sendAloneMessage(){
        var postValue={};
        stompClient.send("/aloneRequest",{"Authorization":"私发"},JSON.stringify(postValue));
    }

</script>

2、查看了腾讯IM的官方网站有小程序、webIM、Android IM、iOS IM但就是没有uniapp的,这个难受哦!试试webIM吧,然后试啊试,这次我学聪明了直接上手机端调试,然而最后并没有成功,这时候才发现web IM SDK并不支持uniapp编译到APP端,该怎么办呢?腾讯这种大公司提供的SDK都不支持我该肿么办呢?

3、在我抱怨之际让我发现了新的希望,GoEasy——一个简单到只要有手就会用的socket框架,当然使用简单的代价就是不够灵活,且功能也不够健全,并且最最致命的的是聊天信息不能进行保存全都在人家的服务器上,中间全部都是客户端与GoEasy之间的通讯跟公司服务器完全没关系了,不带后端完的结果就是大家都没得玩。

4、然后尝试了原生安卓,这次经历太过曲折就不再展开说了,总之是通过这次尝试虽然最终没有真正的成功的用到的项目中,但是却让我了解了更多的前端APP开发相关的知识,同时也想明白了做事还是要靠自己,怕麻烦只会更麻烦。

  • 对于开发APP框架的和实现的扩展
    1、webview 相当于一个网页然后加一个 App 的壳,性能稍微差了点意思
    2、原生渲染:最有代表性的就是weex和react native,性能和原生基本无异,并且还可以一端开发(web端)多端编译(android、iOS)真正的好东西啊
    3、Flutter:Flutter 是谷歌的移动 UI 框架,可以快速在 Android 和 iOS 上构建高质量的原生用户界面,性能比原生要更优,网上对与Flutter的褒贬不一,这里就不多介绍
    4、而uniapp里面融合webview和weex开发模式,uni-app是逻辑和渲染分离的。渲染层,在app端提供了两套排版引擎:小程序方式的webview渲染,和weex方式的原生渲染。两种渲染引擎可以自己根据需要选。vue文件走的webview渲染,nvue走的原生渲染。

5、经过上面的几番尝试,最终还是选择了**环信的小程序端IM的SDK**,到此终于建立连接成功了。你以为结束了?不不不,这只是开始。

6、当我把发送文字消息链路打通能够正常的接收发送文字的时候我以为这一切都结束了,但是当我拿出来展示的时候才发现自己写的有多lou。

  • 没有聊天历史;
  • 不能发送图片;
  • 不能发送语音;
  • 没有消息发送状态;
    哎!现在想想这哪是在做项目啊,这就是在应付自己,但是当时确实是没有想到这些,总以为聊天就是把消息发送给对方,对方能收到消息就算完事,多幼稚的想法啊!

7、 当我解决了IM,再向下做地图坐标推送的时候需要再创建一条新的socket连接,再次遇到了问题,自己创建的socket连接和环信的IM冲突了,看文档,搜帖,问大佬,又卡了很长很长的时间,然后被逼无奈之下查看了环信的SDK源码,在登录回调中找到了问题所在,因为是两个socket连接,环信的socket会误把uniapp的socket的连接状态当做自己的然后导致后面执行的代码出现问题(按照正常的逻辑两条链接是毫无关系的,ip端口号都是不想管的,但是不知道为什么造成了冲突),所以我将使用uniapp创建的socket连接放到了环信创建的连接成功的回调后面该问题得以解决,但是紧接着新的问题又出现了。

8、环信IM与自定义socket连接监听件发生冲突,使用uniapp官方提供的方法会全局监听搜有的socket连接,这个问题到没有困扰我很长时间,最后证实确实是官方的bug,且官方给出了解决的方案plus-websocket,终于通过plus-websocket解决环信和自定义的socket连接冲突的问题。
① 官方提供的创建socket连接的API问题分析:

  • 使用uni.connectSocket创建的socket连接回调监听会监听全局socket连接(该App下所有创建的socket连接的回调参数都可以监听的到)

② plus-websocket实现原理:

  • 我查看了他的源码,是使用ts写的,实现的原理是通过加载一个web页面在web页面中创建一个socket连接供uniapp调用从而实现socket通讯

9、使用plus-websocket创建两条socket连接发消息走同一条通道的问题,两个连接发送的消息都只会发送给其中某一个连接,具体的原因没有找到,但我通过修改源码中打开web页面的id来生成一个新的plus-websocket.js文件,相当于每一个连接单独引用了一个plus-websocket.js文件,从而导致打开的web页面不同最终导致的作用域不同,所以不会再互相影响。

  • Socket-websocket.js文件修改讲解
  • 先去GitHub地址上下载源码
  • 因为官方是用ts写的,且集成了websocket配置文件所以需要执行npm install下载依赖包
  • 找到SocketTask.ts文件修改const WEBVIEW_ID = ‘websocket’,__websocket__可以随意的起一个名字
  • 然后执行npm run build会自动打包输出out/index.js文件
  • 如果不想代码打包后丑化可以修改webpack配置,打开build/webpack.config.js文件注释loader 为 'html-loader’下的minimize: true即可

至此终于可以愉快的玩耍了!

最后附上自定义socket连接心跳检测的代码,小白可以做参考,有什么不合理的地方请大佬多指正:

// 保存聊天历史记录
import {storage, $ws, toast, skip02} from '@/common/forScript/config.js'
import {loginInfo} from '@/common/forScript/res.js'
import Sockets from './socket2.js'

/* 获取本地存储的token */
function getToken() { if(storage.getDataSync('token') != false) { return storage.getDataSync('token') }else {return false}}

/* 获取当前时间 */
function getNowTime() {return (new Date()).getTime()}

const s = new Sockets()
let heartbeat = null // 心跳定时器
let num = 0          // 计算心跳次数
let connectionStatus = false // 连接状态


let ExportFun = {
	// 初始化socket
	initSocket: function(that) {
		// 创建socket连接
		s.connectSocket({
			url: $ws+'admin/YsWebSocket?token=' + getToken() + '&type=2',
			// url: 'ws://echo.websocket.org', // 测试地址
			success(res) {
				console.log('uniapp - 聊天记录 -socket连接已经打开!')
				connectionStatus = true
				// 连接成功调起心跳任务
				ExportFun.onHeartBeat()
				
				let info = that.$store.state.loginInfo
				console.log(info)
				loginInfo(that, info).then((data)=>{
					console.log(data)
					login(data.res, data.res01)
				})
				
			},
			fail(err) {
				console.log(err)
			}
		})
		
		// 监听接收自定义socket,因为业务中没有用到所以注释了
		// s.onSocketMessage(function (res) {
		// 	console.log(res)
		// });	
		
		// 监听是否创建连接成功,没有走该方法所以成功后的逻辑代码都写到了init中
		s.onSocketOpen(function (res) {
			// console.log('uniapp - 聊天记录 -socket连接已经打开!')
			// // socketLocation.initSocket()
			// ExportFun.onHeartBeat()
		});
		
		// 监听连接是否关闭
		ExportFun.onSocketClose()
		
		s.onSocketError(function (res) {
			if(connectionStatus) {return}
			console.log('uniapp - 聊天记录 -WebSocket连接打开失败,请检查!');
			if(storage.getDataSync('token') != false) {
				console.log('uniapp - 聊天记录 -WebSocket连接失败,重新建立连接!');
				
				let rconnectSocket = setTimeout(()=>{
					// 重新创建连接
					ExportFun.reconnection()
					clearTimeout(rconnectSocket)
				}, 10000)
			}
		})
	},
	
	// 進入页面检测链接是否正常
	onShowHeartbeat: function() {
		console.log('进入页面启动心跳检测')
		if(heartbeat != null) {
			clearTimeout(heartbeat)
		}
		ExportFun.onHeartBeat()
	},
	
	// 发送数据
	sendSocketMessage: function(a) {
		s.sendSocketMessage({
			data: JSON.stringify(a),
			success(res) {
				num = 0
				ExportFun.onHeartBeat()
			},
			fail(err) {
				num++
				if(num == 5) {
					console.log('uniapp - 聊天记录 -心跳监测超时,主动断开连接!')
					num = 0
					// 重新创建连接
					ExportFun.reconnection()
				}
				ExportFun.onHeartBeat()
			}
		});
		/* 发送数据 结束 */
	},
	
	// 心跳检测
	onHeartBeat: function() {
		/**
		 * 第一次发消息成功如果定时器不存在则直接创建新的定时任务,
		 * 第二次发消息成功则先清除之前的定时任务再次发送新的定时任务
		 * */
		if(storage.getDataSync('token') != false) {
			if(heartbeat != null) {
				console.log('uniapp - 聊天记录 -发送心跳', num)
				clearTimeout(heartbeat)
				// 发送消息成功10秒后重新调起心跳连接
				heartbeat = setTimeout(()=>{
					ExportFun.sendSocketMessage(0) // 发送心跳数据
				}, 10000)
			} else {
				console.log('uniapp - 聊天记录 -发送心跳', num)
				// 发送消息成功10秒后重新调起心跳连接
				heartbeat = setTimeout(()=>{
					ExportFun.sendSocketMessage(0) // 发送心跳数据
				}, 10000)
			}
		} else {
			// 重新创建连接
			// ExportFun.reconnection()
		}
	},
	
	// 关闭socket连接
	closeSocket: function() {
		s.closeSocket({
			success: function(res) {
				console.log('连接已关闭!')
			},
			fail: function(err) {
				
			}
		})
	},
	
	// 监听连接断开
	onSocketClose: function() {
		s.onSocketClose(function (res) {
			console.log('uniapp - 聊天记录 -WebSocket 已关闭!')
			// 更改连接状态
			connectionStatus = false
			// 重新创建连接
			ExportFun.reconnection()
		})
	},
	
	// 重新创建连接
	reconnection: function() {
		// 如果处于连接状态则不再重新创建
		if(connectionStatus) {return}
		// 清除心跳定时器
		if(heartbeat != null) {clearTimeout(heartbeat)}
		// 主动调起关闭连接的方法
		ExportFun.closeSocket()
		
		if(storage.getDataSync('token') != false) {
			let a = setTimeout(()=>{
				ExportFun.initSocket()
				clearTimeout(a)
			}, 10000)
		}
	}
}

function login(res, res01) {
	/* 根据登陆人是否已经完善登陆信息决定跳转的页面 */
	if(res.data.step == 1) { // 补充个人信息
		
		if(res.data.roles == '3') { // 执行方 企业
			
			skip02('../reg/reg02?regType=3&step='+res.data.step)
			
		}
		if(res.data.roles == '4') { // 执行方 司机
			
			skip02('../reg/reg02?regType=4&step='+res.data.step)
			
		}
		
		return false
		
	} else if(res.data.step == 2) { // 补充企业信息
		
		if(res.data.roles == '3') { // 执行方 企业
			
			skip02('../reg/reg03')
			
		}
		if(res.data.roles == '4') { // 执行方 司机
			
			skip02('../reg/reg02?regType=4&step='+res.data.step)
			
		}
		return false
		
	}
	
	console.log(res.data.step)
	/* 判断登陆人的身份进行页面跳转 */
	if(!res.data.infoCheck && res.data.roles == 1) { // 需求方
		console.log(res.data.infoCheck)
		storage.setDataSync('infoCheck', '0') // 0=>false
		skip02('../../id1/informationComplete/informationComplete')
		return
		
	}
	
	skip02('../../home/home')
}

export default ExportFun

如果对您有帮助请点个赞呗!

你可能感兴趣的:(#,uniapp,javascript,android,前端)