client => http => server => ws/tcp => server2 收到 => server2 通过 unixsocket 投递消息到 work 处理完成后 => 通过 ws 写消息回去 => server 的 ws 收到消息
这个时候 如果正常处理 加工收到的消息, 写入消息到 client 的 fd,close 掉 client 连接 应该是可行的。
client => server(伪代码)=> return
<?php func add(){ $get= $_REQUEST_PARAMS; return success("code=>200,data=$get,msg=''"); }
都不需要返回了, server 收到 server2 结果后 fwrite($client_fd,$data); close($client_fd);
<?php func add(){ $get= $_REQUEST_PARAMS; (new Ws)->send($get); }
上面写好奇怪的样子。。 我想实现下面的写法有办法吗(代码直观看着舒服),比如
<?php func add(){ $get= $_REQUEST_PARAMS; $res = (new Ws)->send($get); return success("code=>200,data=$res,msg=''"); }
如果想实现上面这样子,需要在加一个队列(起到 while true 阻塞效果?)。
开辟 1 片内存 $arr[$client_fd] => $data; 数据回来后写入。然后 pop 消息。
这样子就好像成 select 了。。 无时无刻在遍历,有数据在返回,性能问题很大。。
然后好像为了解决问题又引入新的问题。
大佬们给点思路呢
]]>所以我想问问下一步优化性能的思路, 用什么软件模拟客户端测试合理 服务端没有明显的控制台报错的前提下,还有什么调查性能的方法和思路 下一步我应该如何进行,除了重新开发以外
第一次接触 websocket 被赶鸭子上架,问的问题不专业勿怪。
]]>springboot + websocket + vue
图文形式的聊天,有开启会话和结束会话,每次结束之后,把本次会话的聊天记录存起来(因为要推给第三方); 目前是纯原生的 websocket 实现;
1.图片的传输方式,了解两种方式 a.base64 ,b.客户端选择完图片之后,先上传到 OSS, 然后拿到返回的地址渲染 ; 保存会话记录的时候,消息里存图片的地址; 有没有更好的方式推荐; 2.这种存储聊天记录,如果使用 mysql 来存的话,表如何优雅设计; 或者换种方式存储,文本或者 es ; 3.研究了一下 socket.io ,使用 socket.io 重构的话 提升大吗?
]]>目前问题是 当 app 退出到后台 锁屏一段时间之后 app 被冻结了 再次打开 app 就会出现 Exception: java.net.SocketTimeoutException: timeout Exception: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed 类似的异常
想请问这种情况怎么处理
ps 提供思路并解决的老哥有红包感谢:D
]]>import datetime import random import string import asyncio import time import websockets from multiprocessing import Manager from concurrent import futures # 忽略警告 import warnings warnings.filterwarnings("ignore") strLen = 30 def putmsg(que, gvar): cc = 0 while True: cc += 1 if gvar['flag'] == True: break ranStr = '' for s in range(strLen): ranStr = ranStr + random.choice(string.ascii_letters + string.digits) # slTime = random.uniform(0.01,0.2) logStr = str(cc) + ' ' + "{:.2f}".format(slTime) + ' ' + str(datetime.datetime.now().replace(microsecOnd=0)) + ' ' + ranStr # print(logStr) # test # print(cc, '#', que.qsize()) que.put(logStr) # time.sleep(slTime) time.sleep(1.5) def wsock(queu, gvar): loop = asyncio.get_event_loop() async def stoploop(): loop.stop() # Maintain a list of connected clients connected_clients = set() async def register(websocket): # Add a new client to the list connected_clients.add(websocket) print('connected_clients.add(websocket)') async def unregister(websocket): # Remove a client from the list connected_clients.remove(websocket) print('connected_clients.remove(websocket)') async def broadcast(message): # Send a message to all connected clients if connected_clients: await asyncio.gather(*(client.send(message) for client in connected_clients)) async def echo(websocket, que=queu): await register(websocket) while True: if queu.qsize(): msgStr = queu.get() if connected_clients: try: await asyncio.gather(*(client.send(msgStr) for client in connected_clients)) except Exception as e: print(e) # echo:received 1001 (going away); then sent 1001 (going away) break else: asyncio.sleep(0.3) async for message in websocket: # Broadcast the received message to all clients if message == 'stop': gvar['flag'] = True await stoploop() await broadcast(message) await unregister(websocket) start_server = websockets.serve(echo, "172.17.0.2", 25299) asyncio.set_event_loop(loop) # loop.create_task(start_server) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() if __name__ == '__main__': # 队列 msgQue = Manager().Queue() # 全局变量 glovar = Manager().dict() # 启停开关 glovar['flag'] = False # 处理进程 proc = futures.ProcessPoolExecutor(max_workers=2) wsockRet = proc.submit(wsock, msgQue, glovar) putmsgRet = proc.submit(putmsg, msgQue, glovar)
问题是 websocket.serve 使用 echo 方法作为 handle ,
只有在 websocket 接口有事件的时候,才会调用 echo 进行处理,(被动式)
echo 的被动方法,队列里的日志越来越多,
想有一个永久循环,如果有 client(s),send 取出的队列内容,没有 client ,取出就 pass 了,
websocket.serve 被动调用不适合这个场合,看官方也没有更好的提示,
请教大家这里怎么换个方式实现呢?
proxy-read-timeout:'3600' proxy-send-timeout:'3600'
有没有大佬出现过这种情况? 错误如下:
java.io.EOFException: null at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1345) at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1255) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:75) at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183) at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:162) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:156) at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:829)
]]>我用 python 写了一个 websockets 例子, 服务端和客户端在一台服务器上就可以正常通信, 放在不同的服务器就会提示 error:websockets.exceptions.InvalidMessage: did not receive a valid HTTP response 。 是不是网络的问题,不过我服务器防火墙上的端口都打开了。 sever.py 服务端 import asyncio import websockets # 检测客户端权限,用户名密码通过才能退出循环 async def check_permit(websocket): while True: recv_str = await websocket.recv() cred_dict = recv_str.split(":") print("接受",cred_dict) if cred_dict[0] == "admin" and cred_dict[1] == "123456": response_str = "congratulation, you have connect with server\r\nnow, you can do something else" await websocket.send(response_str) return True else: response_str = "sorry, the username or password is wrong, please submit again" await websocket.send(response_str) # 接收客户端消息并处理,这里只是简单把客户端发来的返回回去 async def recv_msg(websocket): while True: recv_text = await websocket.recv() print("消息:",str(recv_text)) response_text = f"your submit context: {recv_text}" await websocket.send(response_text) # 服务器端主逻辑 # websocket 和 path 是该函数被回调时自动传过来的,不需要自己传 async def main_logic(websocket, path): await check_permit(websocket) await recv_msg(websocket) start_server = websockets.serve(main_logic, '0.0.0.0', 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() #### client.py 客户端 import asyncio import websockets # 向服务器端认证,用户名密码通过才能退出循环 async def auth_system(websocket): while True: cred_text = input("please enter your username and password: ") await websocket.send(cred_text) response_str = await websocket.recv() if "congratulation" in response_str: return True # 向服务器端发送认证后的消息 async def send_msg(websocket): while True: _text = input("please enter your context: ") if _text == "exit": print(f'you have enter "exit", goodbye') await websocket.close(reason="user exit") return False await websocket.send(_text) recv_text = await websocket.recv() print(f"{recv_text}") # 客户端主逻辑 async def main_logic(): async with websockets.connect('ws://(server 公网 ip):8765') as websocket: await auth_system(websocket) await send_msg(websocket) asyncio.get_event_loop().run_until_complete(main_logic())
]]>本文作者:星空无限
原文链接: https://liyangzone.com/2020/09/20/%E5%89%8D%E7%AB%AF/sync-player/
GoEasy 已获作者授权转载,GoEasy 转载时有改动,感谢作者的分享。
前段时间我有这样一个需求,想和一个异地的人一起看电影,先后在网上找了一些方案,不过那几个案都有一些缺点
coplay: 一个浏览器插件,只能播放各大视频网站的视频,视频资源有限,我想要看的视频没有,比如一些经典电影和美剧之类
微光 APP: 还是上面的问题,而且只有手机端
向日葵等远程桌面: 受限于网络问题,卡顿很严重,体验不好
作为一个对用户体验有追求的切图仔,我是一个下载党,看电影必须下载到本地看,基本不看视频网站上的玩意
那么有没有能实现同步播放本地文件的方案呢,答案是肯定的,经过我的一些摸索和研究,我实现了本地文件的同步播放,同时支持 PC 和手机端,而且还支持外挂字幕等高级功能,如何实现请往下看。
一个可以同步看视频的播放器,可用于异地同步观影、观剧,支持多人同时观看。 本项目有两个版本,web 版运行在浏览器上,可跨平台,不限操作系统、设备,功能简单适用于要求不高的用户。还有基于 SPlayer(射手影音)DIY 的客户端版本(windows 、MAC),播放 4K 高清文件、外挂字幕,统统没问题。
web 版同步效果 客户端与 web 版同步效果
基于 websocket 实现,与一些用 websocket 实现的聊天室类似,只不过这个聊天室里的消息换成了播放暂停的动作和时间信息,客户端收到消息后执行相应的动作:播放、暂停、快进,以达到同时播放的效果。
本项目的核心是 websocket,所以至少需要一台服务器提供 websocket 服务,websocket 服务可以自己部署,可以使用第三方平台 GoEasy 提供的 websocket 服务。
websocket 服务器可以是一台具有公网 IP 的云服务器,也可以是一台具有公网 IP 的普通 PC,没有公网 IP 也可以。你也可以使用 zerotier 或其他 VPN 工具将两台设备组成一个大局域网,让它们能互相通信。websocket 服务器操作系统不限,只要有 node.js 环境。
websocket 服务端部署方法:安装 node.js 环境,将 server 目录移动到服务器上,进入 server 目录,执行以下命令:
安装项目依赖包
# 安装项目依赖包 npm install # 启动 websocket 服务 node index.js
注册 GoEasy 开发者账号并创建一个应用,获得 appkey,复制到本项目相应位置即可。
GoEasy 官网:https://www.goeasy.io
无论是使用哪种 websocket 服务都可以,本项目写了两套代码,只需将不用的那套注释掉即可(默认 GoEasy)。
除了 websocket 服务器之外,还需要两个 http 服务端,一个是 web 服务端(提供 html 、css 、js 等文件的访问),一个是视频服务端(提供视频文件访问)。
你可以将 web 服务部端署到以下位置:
视频文件只需一个视频地址就行,也有以下几种选择:
使用场景 1:
云服务器带宽足够大(至少要大于播放视频的码率),云服务器既可以作为 websocket 服务端,也可以作为 http 服务端。上图中所有设备都访问云服务器的 ip 或域名。
使用场景 2:
云服务器的带宽很小,这时候它只能作为 websocket 服务端,这时可以用上图中的 PC1 和 PC2 作为 http 服务端,PC1 和 PHONE1 在一个内网访问 PC1 的内网 IP,PC2 和 PHONE2 在一个内网访问 PC2 的内网 IP,PC3 可作为自己的 http 服务端,PHONE3 若是有提供视频文件的服务端,也可以使用。 使用场景 3:
需要使用 zerotier 或其他 VPN 工具将异地设备组成一个大局域网,其中任意一台 PC 均可作为 websocket 服务端和 http 服务端(需要上传带宽足够大)。上图中各设备都访问那台 PC 的内网 ip 即可。
最简单的使用方法,下载 nginx 开启一个本地服务器,下载本项目 client 文件夹放到到 nginx 根目录里,视频文件也放到里面。注册 goeasy 开发者账号并创建一个应用,获得 appkey,并填入到 appkey 到代码(script/main.js)相应位置。然后浏览器打开 192.168.3.58/client/,填入你的视频地址 192.168.3.58/movie/xxx.mp4 或网络视频地址,对方也这样操作一番,即可实现同步播放视频。
web 版本的功能比较简单,而且受限于网络问题,快进之类的操作需要缓冲一段时间。如果你不满足 web 版功能,对用户体验有更高的要求,如支持更多文件格式、播放高清本地视频文件、外挂字幕等,我也找到了另一种方式来满足你的需求。
那就是 DIY 一个开源的播放器的源码:SPlayer(射手影音)。
射手影音官网: https://splayer.org
源码地址: https://github.com/chiflix/splayerx
在以 electron + 播放器为关键字一番搜索之后,我找到了这个基于 electron 实现的开源播放器,并下载了源码来研究。
经过一番研究之后,我找到了控制视频播放、暂停、快进的代码位置,并将控制同步的代码移植了进去,从而也实现了同步功能,并且与 web 版兼容。
具体方法请看:修改教程
本项目部分图标样式来源于此项目: coplay
本项目 github 地址:点击前往 ,欢迎⭐⭐⭐STAR⭐⭐⭐
GoEasy 是一个成熟稳定的企业级 websocket PAAS 服务平台,开发人员不需要考虑 websocket 服务端的搭建,只需要几行代码,就可以轻松实现客户端与客户端之间,服务器与客户端之间的的 websocket 通信。
GoEasy 作为国内领先的第三方 websocket 消息推送平台,具备极佳的兼容性。除了兼容所有常见的浏览器以外,同时也兼容 uni-app,各种小程序,以及 vue 、react-native 、cocos 、laya 、egret 等常见的前端框架。
同时 GoEasy 已经内置 websocket 中必备的心跳,断网重连,消息补发,历史消息和客户端上下线提醒等特性,开发人员也不需要自己搭建 websocket 服务处理集群高可用,安全和性能问题。GoEasy 已经稳定运行了 5 年,支持千万级并发,成功支撑过很多知名企业的重要活动,安全性和可靠性都是久经考验。
有兴趣自己搭建 websocket 的话,可以参考这篇技术分享《搭建 websocket 消息推送服务,必须要考虑的几个问题》
]]>今天小编就手把手的教你用 GoEasy 做一个聊天室,当然也可以用于直播间内的互动。全套源码已经开源,git 地址: https://gitee.com/goeasy-io/GoEasyDemo-Live-Chatroom.git
本教程主要目的是为大家介绍实现思路,为了确保本教程能帮助到使用不同前端技术的朋友,采用了 HTML + JQuery 的方式,后续还会推出 Uniapp(vue/nvue)和小程序版本,大家可以持续关注。
我们这次要实现的聊天室,有两个界面,分别是:
对于登录界面,我们期望:
实现步骤
登录界面的实现,不用多说,因为真的是 So Easy! 一个简单的界面,只包含三个简单的逻辑:
下边重点讲一下聊天室的实现。
当我们进入一个聊天室后,我们期望:
1. 初始化:
当用户选择了一个聊天室,显示聊天室界面之前,我们首先要进行以下初始化工作:
参考代码:service.js
//初始化聊天室 this.joinRoom = function(userId,nickName, avatar, roomID) { //初始化当前用户 this.currentUser = new User(userId, nickName, avatar); //初始化当前聊天室 id this.currentRoomId = roomID; //初始化 goeasy,建立长连接 this.goeasy = new GoEasy({ host: "hangzhou.goeasy.io", appkey: "您的 appkey", userId: this.currentUser.id, userData: '{"nickname":"' + this.currentUser.nickname + '","avatar":"' + this.currentUser.avatar + '"}', onConnected: function () { console.log( "GoEasy connect successfully.") }, onDisconnected: function () { console.log("GoEasy disconnected.") } }); //查询当前在线用户列表,初始化 onlineUsers 对象 this.initialOnlineUsers(); //监听用户上下线提醒,实时更新 onlineUsers 对象 this.subscriberPresence(); //监听和接收新消息 this.subscriberNewMessage(); };
2. 页面展示:
完成初始化之后,就跳转到直播间界面,在页面上显示以下数据:
参考代码:controller.js
//页面切换到聊天室界面 function showChatRoom() { //更新房间名 $("#chatRoom-header").find(".current-chatRoom-name").text(loginCommand.roomName); //加载聊天历史 var chatHistory = service.loadChatHistory(); chatHistory.forEach(function (item) { //展示发送的消息 var otherPerson = createCurrentChatRoomPerson(item.senderNickname + ":", item.content) $(".chatRoom-content-box").append($(otherPerson)); }); //隐藏登录界面 $(".chat-login-box").hide(); // //显示聊天界面 $(".chatRoom-box").show(); // //滑动到最后一行 scrollBottom(); }
至此,我们已经完成了 goeasy 长连接的初始化,和一个聊天室静态展示。接下来,我们一起来看看如何让这个聊天室能够动起来。
1. 实时更新在线用户数和头像列表
之前在 service.initialOnlineUsers 方法已经初始化 onlineUsers 对象,但聊天室随时都有用户进进出出,所以我们接下来还需要能够在有用户上线或下线的时候能够实时的更新 onlineUsers,并且实时显示在页面上。 当我们收到一个用户上线提醒,我们将新上线的用户的信息存入在线用户对象 onlineUsers 里,当有用户离开时,在本地在线用户列表里删除。
参考代码:service.js
//监听用户上下线时间,维护 onlineUsers 对象 this.subscriberPresence = function() { var self = this; this.goeasy.subscribePresence({ channel: this.currentRoomId, onPresence: function(presenceEvents) { presenceEvents.events.forEach(function(event) { var userId = event.userId; var count = presenceEvents.clientAmount; //更新 onlineUsers 在线用户数 self.onlineUsers.count = count; //如果有用户进入聊天室 if (event.action == "join" || event.action == "online") { var userData = JSON.parse(event.userData); var nickName = userData.nickname; var avatar = userData.avatar; var user = new User(userId, nickName, avatar); //将新用户加入 onlineUsers 列表 self.onlineUsers.users.push(user); //触发界面的更新 self.onJoinRoom(user.nickname, user.avatar); } else { for (var i = 0; i < self.onlineUsers.users.length; i++) { var leavingUser = self.onlineUsers.users[i]; if (leavingUser.id == userId) { var nickName = leavingUser.nickname; var avatar = leavingUser.avatar; //将离开的用户从 onlineUsers 中删掉 self.onlineUsers.users.splice(i, 1); //触发界面的更新 self.onLeaveRoom(nickName, avatar); } } } }); }, onSuccess : function () { console.log("监听成功") }, onFailed : function () { console.log("监听失败") } }); };
2. 发送消息
参考代码( service.js)
this.sendMessage = function(content) { var message = new ChatMessage(this.currentUser.id,this.currentUser.nickname, MessageType.CHAT, content); var self = this; this.goeasy.publish({ channel: self.currentRoomId, message: JSON.stringify(message), onSuccess: function() { console.log("消息发布成功。"); }, onFailed: function(error) { console.log("消息发送失败,错误编码:" + error.code + " 错误信息:" + error.content); } }); };
3. 接收和显示新消息 /道具
之前我们已经在初始化页面的时候执行了 service.subscriberNewMessage(),当我们收到一条消息时:
参考代码( service.js )
//监听消息或道具 this.subscriberNewMessage = function() { var self = this; this.goeasy.subscribe({ channel: this.currentRoomId, //替换为您自己的 channel onMessage: function(message) { var chatMessage = JSON.parse(message.content); //todo:事实上不推荐在前端收到时保存, 一个用户开多个窗口,会导致重复保存, 建议所有消息都是都在发送时在服务器端保存,这里只是为了演示 self.restapi.saveChatMessage(self.currentRoomId, chatMessage); //如果收到的是一个消息,就显示为消息 if (chatMessage.type == MessageType.CHAT) { var selfSent = chatMessage.senderUserId == self.currentUser.id; var cOntent= JSON.parse(message.content); self.onNewMessage(chatMessage.senderNickname, content, selfSent); } //如果收到的是一个道具,就播放道具动画 if (chatMessage.type == MessageType.PROP) { if (chatMessage.cOntent== Prop.ROCKET) { self.onNewRocket(chatMessage.senderNickname); } if (chatMessage.cOntent== Prop.HEART) { self.onNewHeart(chatMessage.senderNickname); } } } }); };
4. 发送和接收并展示道具
其实和发送消息的实现几乎是一样的,具体代码请参考 service.js 的 sendProp 方法,controller.js 的 onNewHeart()方法。动画的播放,使用了 TweenMax 这个库,主要是为了展示一个实现思路,小编也不知道这个库是否有很好的兼容性,以及是否能够用在 Uniapp 和小程序下,知道的朋友可以留言分享给大家。
this.sendProp = function(prop) { var self = this; var message = new ChatMessage(this.currentUser.id,this.currentUser.nickname, MessageType.PROP, prop); this.goeasy.publish({ channel: self.currentRoomId, message: JSON.stringify(message), onSuccess: function() { console.log("道具发布成功。"); }, onFailed: function(error) { console.log("道具发送失败,错误编码:" + error.code + " 错误信息:" + error.content); } }); };
至此,一个聊天室就搞定了,是不是很简单?
如果阅读本文或开发中有任何问题,也欢迎在 GoEasy 官网添加 GoEasy 为好友,来获得更多技术支持。
GoEasy 官网:https://www.goeasy.io/
]]>GoEasy 上架 DCloud Uniapp 插件市场已经有一个多月了,收到很多 Uniapp 开发人员的赞扬和好评,小编在此隔着屏幕向大家鞠躬,小编和 GoEasy 团队会继续努力,持续为 Uniapp 开发者提供最简单且优雅的 API,稳定高速可靠的即时通讯服务。
这段时间,也收到了很多朋友的一些问题?比如:
1 、GoEasy 和 Uniapp websocket API 有什么区别和优势?
Uniapp 官方的 websocket API 主要是用来与您的 websocket 服务通讯,所以使用 Uniapp websocket 的前提是,首先要搭建好您自己的 websocket 服务,然后与之通讯。这是一个纯技术的 API,在建立网络连接后,还有很多的工作需要自己来完成,比如:
需要自己实现心跳机制,来维护网络连接,来判断客户端的网络连接状态; 需要自己实现断网自动重连; 需要自己维护消息列表,确保遇到断网重连后,消息能够补发; 需要自己维护一个客户端列表; ... 等等很多细致而繁杂的工作,比如 websocket 的安全机制和性能优化; 除此之外服务端也有很多工作需要自己完成,有兴趣自己搭建 websocket 的话,可以参考这篇技术分享《搭建 websocket 消息推送服务,必须要考虑的几个问题》
而 GoEasy 是一个成熟稳定的 websocket 企业级 PAAS 服务平台,开发人员不需要考虑 websocket 服务端的搭建,只需要几行代码,就可以轻松实现客户端与客户端之间,服务器与客户端之间的的 websocket 通信,不需要考虑性能,安全,高可用集群的问题。只需要全力专注于开发自己的业务功能就好了。
GoEasy 已经内置 websocket 中必备的心跳,断网重连,消息补发,历史消息和客户端上下线提醒等特性,开发人员也不需要自己搭建 websocket 服务处理集群高可用,安全和性能问题。GoEasy 已经稳定运行了 5 年,支持千万级并发,成功支撑过很多知名企业的重要活动,安全性和可靠性都是久经考验。
2 、GoEasy 在 Uniapp 开发中主要用在哪些场景呢?
首先从技术上说,Uniapp 支持的所有技术 android, iOS 和小程序,GoEasy 都是完美支持的。
从应用场景上来说,所有需要 websocket 通信的场景,GoEasy 都可以完美支持:
聊天,IM,直播弹幕,用户上下线提醒, 在线用户列表 扫码点菜,扫码登录, 扫码支付, 扫码签到, 扫码打印 事件提醒,工单,订单实时提醒 在线拍卖, 在线点餐,在线选座 实时数据展示,实时监控大屏, 金融实时行情显示,设备监控系统 实时位置跟踪,外卖实时跟踪,物流实时跟踪 远程画板,远程医疗,游戏,远程在线授课 ...
3 、GoEasy 的文档为什么这么简单?简单到我都不知道如何使用
简单还不好吗? GoEasy 从研发的第一天,就把追求 API 的极简作为我们的工作重点。严格控制接口的数量,就是是为了降低开发人员的学习成本,其实就是为了让您爽啊!但这并不影响 GoEasy 完美支持所有的 websocket 即时通讯需求。
今天小编就手把手的教您用 GoEasy 在 Uniapp 下,最短的时间实现一个的 web 即时通讯 Demo 。
本 demo 已经通过小程序, iOS 和 Android 的真机测试,完整源代码已经上传 github,下载后,只需要将代码里的 appkey 换成自己的 common key,就可以体验了。下载地址:
https://github.com/GoEasySupport/goeasy-uniapp-helloworld
1 、获取 appkey
GoEasy 官网(https://www.goeasy.io/)上注册账号,创建一个应用,拿到您的 appkey
GoEasy 提供了两种类型的 appkey:
Common key: 即可以接收消息,也可以发送消息,与 Subscriber Key 最大的区别就是有写权限,可以发消息。适用于有消息发送需求的客户端和服务端开发。
Subscriber key: 只能接收消息,不可以发送消息,与 Common Key 最大的区别就是没有写权限,只能收消息。可以用于一些没有发送需求的客户端。
2 、获取 GoEasy SDK
两个方式:
DCloud Uniapp 的插件市场下载:https://ext.dcloud.net.cn/plugin?id=1334
也可以直接在 goeasy 官网下载:https://www.goeasy.io/cn/doc/client/get-goeasy-js.html
3 、初始化 GoEasy 对象
在 main.js 中将 goeasy 初始化为 Uniapp 的全局对象,方便所有页面都能方便的调用,同时也避免多个页面反复 new GoEasy 对象。
根据您在 GoEasy 后台创建应用时选择的区域,来传入不同的 Host,如果您创建 goeasy 应用时,选择了杭州,那么 host:"hangzhou.goeasy.io"。选择了新加坡,host:"singapore.goeasy.io"。
如果您的大部分用户都是在国内,创建应用时,记得选择杭州,以便获得更快的通讯速度。
// 在 main.js 中将 goeasy 初始化为全局对象,所有页面都能方便的调用,也避免多个页面反复 new GoEasy 对象 Vue.prototype.$goEasy = new GoEasy({ host: "hangzhou.goeasy.io", appkey: "my_appkey", //替换为您的应用 appkey onConnected: function() { console.log('连接成功!') }, onDisconnected: function() { console.log('连接断开!') }, onConnectFailed: function(error) { console.log('连接失败或错误!') } });
4 、uniapp 端接收消息
this.$goEasy.subscribe({ channel: "my_channel", //替换为您自己的 channel onMessage: function (message) { alert("Channel:" + message.channel + " content:" + message.content); } });
很多朋友会问 channel 从哪里来,如何创建,应该传入什么呢?
根据您的业务需求来设定,channel 可以为任意字符串,除了不能包含空格,和不建议使用中文外,没有任何限制,只需要和消息的发送端保持一致,就可以收到消息。channel 可以是您直播间的 uuid,也可以是一个用户的唯一表示符,可以任意定义,channel 不需要创建,可以随用随弃。
5 、uniapp 端发送消息
发送时,需要注意 channel 一定要和 subscriber 的 channel 完全一致,否则无法收到。
this.$goEasy.publish({ channel: "my_channel", //替换为您自己的 channel message: "Hello, GoEasy!" //替换为您想要发送的消息内容 });
本代码源码下载:
https://github.com/GoEasySupport/goeasy-uniapp-helloworld
小程序特别强调:
若您需要打包为微信小程序,需要在微信公众号平台配置 socket 合法域名,否则无法使用。 具体步骤:
访问 https://mp.weixin.qq.com ,进入微信公众平台|小程序 -> 设置 -> 开发设置 -> 服务器域名
socket 合法域名-> 添加 goeasy 的地址:wx-hangzhou.goeasy.io (记得 wx-开头)
若您创建 GoEasy 应用时选择了新加坡区域则添加地址:wx-singapore.goeasy.io
答疑时间:
1 、我的服务器端可以发送消息吗?都支持些哪些语言?
当然可以,任何语言都可以通过调用 goeasy 的 Rest API 发送消息,同时为了大家方便,GoEasy 的官方文档里,也准备了 Java, C#,NodeJS,PHP,Ruby 和 Python 等常见语言调用 REST API 的代码,这里获取更多详情:https://www.goeasy.io/cn/doc/server/publish.html
2 、GoEasy 可以发送图片,语音和视频吗?
当然可以,您可以通过推送文件路径的方式来实现文件的发送。
按照行业惯例,不论 MSN,微信,QQ 对于图片和视频,通常的做法都是,只推送文件路径,而不会推送文件本身。你如果有注意的话,当您接受图片和视频的时候,收到消息后,等一会儿才能看,就是因为发送的时候,只发送了路径。
]]>面对各种新场景对 websocket 功能和性能越来越高的需求,不同的团队有不同的选择,有的直接使用由专业团队开发的成熟稳定的第三方 websocket 服务,有些则选择自建 websocket 服务。
作为一个具有多年 websocket 开发经验的老程序猿,经历了 GoEasy 企业级 websocket 服务从无到有,从小到大的过程,此文是根据过去几年在 GoEasy 开发过程中踩过的坑,以及为众多开发团队提供 websocket 服务、与众多开发者交流中的总结的一些经验和体会。
这次主要从搭建 websocket 服务的基本功能和特性方面做一些分享,下次有机会再从构建一个高可用 websocket 时要面对的高并发,海量消息,集群容灾,横向扩展,以及自动化运维等方面进更多的分享。
以下几点是个人认为在构建 websocket 服务时必须要考虑的一些技术特性以及能显著提高用户体验的功能,供各位同学参考:
1.建立心跳机制
心跳机制几乎是所有网络编程的第一步,经常容易被新手忽略。因为在 websocket 长连接中,客户端和服务端并不会一直通信,如果双方长期没有沟通则都不清楚彼此当前状态,所以需要发送一段很小的报文告诉对方“我还活着”。另外还有两个目的:
服务端检测到某个客户端迟迟没有心跳过来可以主动关闭通道,让它下线; 客户端检测到某个服务端迟迟没有响应心跳也能重连获取一个新的连接。
2.建立具有良好兼容性的客户端 SDK
虽说现在主流浏览器都支持 websocket,但在编码中还是会遇到浏览器兼容性问题,而且通过 websocket 通信的客户端早已不仅限于各种 web 浏览器,还包括越来越多的 APP,小程序。因此就要求构建的 websocket 服务必须能够很友好的支持各种客户端。最好的方式就是构建一个能够兼容所有主流浏览器、小程序和 APP,以及 uni-app、vue、react-native 等目前常见的各种前端框架的客户端 SDK,这样不论公司的各个项目使用什么样的前端技术,都能够快速的集成 websocket 服务。
3.断网自动重连和消息补发机制
移动互联网时代,终端用户所处的网络环境多样且复杂,如用户进出电梯,出入地下室或地铁等网络不稳定的场所,或其他原因导致的网络不稳定都是很常见的场景。因此,一个可靠的 websocket 服务必须具备完善的断网自动重连机制。确保断网后,网络一旦恢复,能第一时间自动重新建立长连接,并且能够立即补发在网络不稳定期间发送的消息。
4.离线消息
基础的 Websocket 通讯从技术上来说,消息送达的前提条件就是建立起一个长连接,没有建立网络连接就来讨论通讯那是耍流氓。但是从使用者的角度上来说,随手关闭浏览器,或者将小程序、APP 进程直接杀掉而导致网络连接断开的情况是随时都在发生的。然后我们下意识的期待,就是我下次打开浏览器访问网页,或者打开 APP 时,能够收到用户离开系统期间的所有信息。从技术上这是一个跟 websocket 没有多大关系的需求,但实际上却是 websocket 服务不可或缺的基本特性,也是一个能够极大提升用户体验的功能。
5.上下线提醒,客户端在线列表
掌握当前系统有哪些用户在线,捕捉用户上下线事件,是搭建一个企业级 websocket 服务,必不可少的特性,尤其是开发 IM 和游戏类产品。
6.支持历史消息查询
websocket 服务,某种意义也是属于一个消息系统,对于历史消息的查询需求,是无法绕开的话题。比如 IM 系统中常见的历史消息,因此在 websocket 服务内部实现一个高速,可靠的消息队列机制来支持 websocket 服务实现历史消息的查询就是一个必须的工作。
7.消息的压缩机制
不论是为了保证消息通讯的速度和实时性,还是为了节约流量和带宽费用,或者是出于提高网卡的使用效率和增加系统的吞吐量,在通讯过程中对消息进行必要的压缩都是必不可少的。
除了需要考虑以上七点以外,笔者认为,还有几个问题也是很值得初学者积极关注的:
1.缓存和持久化
选择合适的消息缓存机制,是企业级 websocket 服务保证性能必须要考虑的问题。
2.异步调用
要支持大量消息通讯的高性能系统,必然推荐异步调用。若设计为同步调用,调用方就需要一直等待被调用方完成。如果一层一层的同步调用下去,所有的调用方需要相同的等待时间,调用方的资源会被大量的浪费。更糟糕的是一旦被调用方出问题,其他调用就会出现多米诺骨牌效应跟着出问题,导致故障蔓延。收到请求立即返回结果,然后再异步执行,不仅可以增加系统的吞吐量,最大的好处是让服务之间的解耦更为彻底。
3.独立于业务和标准化
尽管在一个 web 项目中可以同时存在常规 http 服务和 websocket 服务,尤其对性能要求不高的单应用 web 系统,这种方式更简单,更便于维护。但对于性能和可用性高的企业级系统或者互联网平台,更好的方式,是将 websocket 服务作为一个单独的微服务来进行设计,避免和常规的 http 服务抢占资源,导致系统性能不可控,同时也更便于横向扩展。
一个设计良好的企业级 websocket 服务应该是一个独立于业务系统、标准化的单独存在的技术性微服务,能够作为公司基础架构的一部分为公司的所有项目提供通讯服务。
4.幂等性和重复消息的过滤
所谓幂等性,就是一次和多次请求一个接口都应该具有同样的后果。为什么需要?对每个接口的调用都会有三种可能的结果:成功,失败和超时。对最后一种的原因很多可能是网络丢包,可能请求没有到达,也有可能返回没有收到。于是在对接口的调用时往往都会有重试机制,但重试机制很容易导致消息的重复发送,从用户层面这往往是不可接受的,因此在接口的设计时,我们就需要考虑接口的幂等性,确保同一条消息发送一次和十次都不回导致消息的重复到达。
5.支持 QoS 服务质量分级
其实对于上一点消息重复的问题,行业已经有了解决方案和标准规范,对于消息到达率和重复,常用的手段就是通过消息确认的方式来确保消息到达,要求越高,意味着确认机制越复杂,成本越高。为了在成本和到达率之间有很好的平衡,通常对消息系统的服务质量( QoS )分为以下三个级别 :
QoS 0(At most once):“最多发一次”,意味着发送就可以了,不需要确认机制,发送了即可,适用于要求不高的场景,可以接受一定的不到达率,成本最低。
QoS 1(At least once):“至少发一次”,意味着发送方必须明确收到接收方的确认信号,否则就会反复发,每条消息至少需要两次通信来确认到达,可以接受一些消息被重发,但成本不高。
QoS 2(Exactly once):“确保只发一次”,意味着每条消息只能到达一次,且不允许重复到达,为了达到这个目标就需要双方至少通讯三次,成本最高。
一个完善的 websocket 服务面对不同的应用场景,应该能够支持选择不同等级的 QoS,在成本和服务质量之间取得平衡。
最后
虽然 websocket 已经广泛的应用于各种系统和平台,但如果要搭建一个满足企业级或者大型互联网平台的可靠、安全稳定的 websocket 服务,对于没有经验的同学,在具体的技术实践过程依然是有不少的坑要踩。
对 websocket 服务有较高要求,选择成熟可靠的第三方 websocket 服务其实也是一个成本更低和高效的选择。GoEasy 作为国内领先的第三方 websocket 消息平台,已经稳定运行了 5 年时间,支持千万级消息并发,除了兼容所有常见的浏览器以外,同时也兼容 uni-app,各种小程序,以及 vue、react-native 等常见的前端框架。
希望本文能为初次搭建 websocket 服务的同学在思路上有所帮助和参考,也欢迎各位前辈多多批评指正,同时也希望未来有机会就更多的技术与大家进行交流。
GoEasy 官网: https://www.goeasy.io/
]]>//demo 进程启动向所有客户端连接发送数据 foreach ($room_users as $key => $clientId){ if ($currentSessionId != $clientId){ $this->send($clientId, $msg); }else{ $this->send($clientId, $msg_my); } }
]]>