为什么做
工作中经常看到别人使用和接触websocket
、但是自己的工作又用不上、于是便想着做一个个人项目来学习websocket
、恰巧看到了一个用websocket
打造的音乐聊天室项目、于是便从零开始开发了这样一个音乐聊天室大厅,想记录下一个大概的个人项目成型,也顺便分享与大家。
- 项目预览地址 音乐聊天室大厅
websocket 和 http 的区别
在我们日常的开发中,接触最多的就是http
协议了, http
协议是用在应用层的协议,他是基于tcp
协议的,http
协议建立链接也必须要有三次握手才能发送信息。在这种场景下,服务端是被动的,他不能去对客户端操作与通信,只能等待客户端主动发起,这种场景也很多件,在日常开发中,部分时候需要用到轮询便可凸显这种通信在某些场景的弊端,于是websocket
应运而生了。
WebSocket
他是为了解决客户端发起多个http
请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocke
t协议下客服端和浏览器可以同时发送信息。 建立了WebSocket
之后服务器不必在浏览器发送request
请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以发送信息到服务器。而且信息当中不必在带有head的部分信息了与http
的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
技术选型
常常我们在自己开发一个东西是,写代码的时间是很少的,真正更多的时间是在思考,项目怎么做,用什么技术,如何组装等等,只要思考好这些问题,真正开写的时候就会很快了。
个人的习惯一直是把前后端分开来写,所以首先的想法是把项目分为前后端两端两个项目,前端选择了目前公司在使用的技术栈vue
,后端则是选择了每一个前端都能快速上手的node。同时由于相对于websocket
,我选择了封装更为完善的socket-io
。两者区别不大。大致来讲socket-io
是对websocket
的封装,但也不完全正确,Socket.io
将Websocket
和轮询(Polling)
机制以及其它的实时通信方式封装成了通用的接口,其日常使用大同小异。
- 前端
- 使用前端框架
vue
进行基本前端开发 - 使用
socket-io-client
替代websocket
进行双工通信 - 使用套件
vue-socket.io-extended
,对socket-io
在vue
的一个封装集成,方便在开发中更为方便使用
- 使用前端框架
- 后端
- 使用
node
框架nestjs
进行后端开发(因为之前都是用express
,koa
等开发个人项目,公司项目用hapi
,个人感觉,不同框架确实有不同的感受,express
,koa
这类框架官方并没有帮你强行约束你的开发,没有统一的规范,会由于不同人的开发爱好不大相同,而nest的定义是一个渐进式的Node.js框架,用于构建高效,可靠和可扩展的服务器端应用程序;不要问我为什么要放图,据说放图可以提高访问量。,所以也是来尝试使用了nestjs
来进行本次开发) - 数据库使用了个人使用最多
mysql
- orm使用了
nestjs
配套的typeorm
。(个人感觉typeorm
没有之前自己使用的sequelize
好用),当然,作为前端,本身对后端可能不是特别了解,这也是全凭个人喜好而定了。 - 作为音乐聊天室,当然离不开曲库了,
歌曲来源是通过爬虫获取xx音乐网站实现的
- 使用
项目大致思路
要想打造一个音乐聊天室
,浅而已见,需要两个东西,音乐
,聊天功能
,要想实现这两个功能,我们分个顺序,先实现聊天,在聊天的基础上再去实现音乐。
- 要想实现聊天功能,前后端的大题流程是,前端发起一个连接请求,由后端来处理连接,记录连接用户,并保留住个人的基本信息,用于分发给其他进入聊天室的用户。
- 项目的权限验证依然使用的
jsonwebtoken
但是这个的思路和我们日常的验证稍有差异 - 当我们连接成功后就要开始播放音乐,而要想所有人听到的歌都是同步的,那么也就意味者控制歌曲的播放需要后端来做而不是前端了,那么如何后端控制音乐的播放呢也是一个问题
- 同时我们需要实现哪些功能呢,聊天发文字消息、发表情、发图片、复制粘贴发送图片、点歌、切歌、顶歌、等等功能,我们逐一来实现吧
实现功能
一、前后端权限校验
我们日常使用前后端交互的时候都是会在请求头携带token
,而这个操作呢,我们一般是通过axios
请求拦截进行全局操作,那么我们对socket
如何操作呢,首先,我们依然离不开token
我们的校验最终还是要用它进行校验,所以就离不开登录,我们登录后拿到token
携带在socket
请求中,在前端项目中,我们一般只会维护一个socket
实例,我们来看看,初始化的时候需要哪些东西吧。
socketio官网 初始化的参数基本就是这样,这里的套件有什么用呢?其实就是当我们使用这个套件后,首先$socket
就挂载在了Vue
的原型上,其次,我们就可以在组件使用的时候,定义一个和methods
,data
同级下的sockets
,我们就可以和methods
一样,在下面定义所有socket-io
来自服务端的事件了,如果接触过wensocket
,我们知道,服务端与客户端是通过事件交流的,双方会触发不同的事件去告诉对方需要做什么,在这里有个问题,我们的token加在哪里,官方文档也没有找到具体的方法,首先我们知道,这里的token
并不能在axios
统一全局加,那么就需要我们自己去处理了。
首先,当我们使用socket
的时候,我们就要抛弃传统的http
方法,开始一个新的概念,在这里,是初始化的时候就会创建的,我们在初始化的时候,用户还没登录,又怎么会有token
呢,所以很明显,token
的注入时机一定是连接前,并且登陆后,所以初始化这里我们就可以跳过了,来到连接阶段。
频繁的让服务端去验证会消耗不必要的性能,前端首先判断,没有token
强制用户登录,之后才能去连接,如何连接呢?
我们在连接前把token放入this.$socket.client.io.opts.query
,也可以放入到请求头中,看个人需求,这两点都可以,我们只是为了在请求连接的过程中,携带上token,这时我们就可以控制传入的参数,当然除了token
你也可以携带更多的参数,看看有无必要,然后服务端就可以在request
的query
中拿到token
进行验证了。这样,我们就可以自由控制连接时机和参数了,服务端的校验和普通http
无差别,校验不通过拒绝连接即可。
在socket
中,我们无需每次请求都携带token
,我们只需要在连接的这一次携带即可,后续连接通过后,会生成一个固定的连接id,cliend.id
,这是双方连接的唯一凭证信息,也是通过这个和你进行通信的,那么我们的权限就到这里就完成了。
二、聊天室消息通信交流
作为一个聊天室,最基本的功能就是聊天了,我们如何进行聊天呢,前面我们说到,socket
的通信实际就是响应各种事件,简单理解就是我们定义一些方法,会在双方发送事件的过程中触发,如何发送事件呢,客户端和服务端是稍有区别的
- 客户端
this.$socket.client.emit('CustomEvent', data)
这里便是像客户端发送一个事件,名字叫做CustomEvent
并且携带了data
数据,那么发送完这个请求后,服务端就会执行定义的CustomEvent
方法,同时接收到数据data。例如下图,接收到了一条消息
服务端呢大体相同却又不同,为什么这么说呢,因为,在客户端,我们是一对一,我们的目标只有服务端,所以我们只能对服务端发送事件,而在服务端则不同,有多个客户端连接他,那么他就是多对一,他要为多人符文,所以,他面向的事件场景分为三类,
代码语言:javascript复制1. 像当前连接的一个客户端发送事件通知
1. 像除开当前连接用户的其他所有人发送消息
1. 像包含当前连接客户端的所有用户发送消息
很好理解,我们不可能所有消息都要通知给每个人,我们是需要分发给不同人消息的,有了这样一个概念,你就能快速理解了
三、基本交互流程方式
一般而言呢,我们日常的聊天内容都会存在DB
项目的源码里面已经绑定了一个数据库了,这里我使用的是mysql
,这些都大同小异了,我们不需要过多关注,我们来大致分析一下一个用户发送消息后需要做哪些事情。
1. 当用户A连接进入房间的时候,首先我们要把房间的初始信息给与用户,一个基本的聊天室有哪些信息呢?
1. 历史用户聊天消息
2. 房间信息,房间公告,其他房间自定义设置
3. 当前所有在线用户列表,包含用户的一些基础信息,例如性别,签名等等
4. 歌曲信息,当前正在播放的歌曲,播放到多少秒了,从什么时候开始播放呢?【这些后面聊】
1. 用户进入房间后发送一条消息,服务端接收到消息,首先需要把消息存入`db`以便存储历史记录,然后把此条消息再通知给所有人,然后所有客户端会接收到有新消息来了的通知,就会吧新来的消息`push`进当前的消息列表,这样不论谁发消息,都只需要往数组加就好了。
1. 这就是一个聊天的基本流程
单纯的聊天当然不仅仅只有文字消息了,我们同样有表情,图片,或者微信中我们文字加小表情,如何实现呢,我们逐一分析
- 小表情,对于我们而言其实只是一个小的icon或者图片,所以我们选择的时候其实就是选择了一张图片,一般呈现形式如下
我们发现点击表情后变成了这种格式,其实如果你观察过以前的QQ
你会发现很多其实都是/大笑
类似这样的格式,他其实就是把图片的路径转为这样一个代键值,在发送出去的时候通过v-html
或者其他方式把他再编译为一个img
标签即可,这样就实现了这样的小表情和文字在一起的效果 ,发送出去如下
- 第二个我们需要发送表情包呢,那么同理,表情包对于我们而言也是一个图片,一个图片地址罢了,不同的是,表情包我们不需要他停留在消息输入栏,这样更简单了,直接点击表情包,就发送一个图片链接即可,而要对消息区分,只需要给消息再加上一个类型,例如
text文字类型
,img图片类型
,notice消息通知类型
,这样就言简意赅了,通过不同的类型渲染不同样式就轻松区分了,例如下图
- 第三个便是,如果用户要发送自己的图片消息怎么做呢,我们知道,图片要发送都是粘贴过来或者发送文件的,本质呢都是发送一个文件,怎么操作呢?
input
中有一个事件就是粘贴事件@paste
,在这其中e.target.file
就可以拿到粘贴的图片信息呢,然后通过文件上传接口把文件上传到远端,通过接口拿到一个返回的图片地址,把这个图片地址发给服务端就完成了自定义的图片信息费发送,那么视频呢,当然也是同理了,这样是不是清晰了呢。
四、音乐功能
作为音乐聊天室,除了聊天,第二点当然是音乐功能了,对于h5
而言,h5
就是一个video
或者audio
,这点大家都知道就不用不说了,我们只需要src
引入资源地址就可以播放了,非常简单,但是我们当然不只是为了简单听歌了,我们想要实现一个人,所有人一起点歌,然后一起按顺序播放的功能,大家进入聊天室听到的都是一个相同时间的歌曲,对于这个功能,客户端只需要两件事情,
- 前端思路
- 知道现在在播放什么歌曲,歌曲的资源地址是什么。
- 当前播放歌曲到哪一秒了,用户进入就要从当前大家一起的这个时间开始播放,同步播放
- 大致流程是,进入房间或者房间信息状态,当前歌曲,歌曲开始播放时间,加载歌曲,跳到当前播放时间开始播放
- 需要注意的是,目前由于浏览器限制,是不能自动播放音乐的
autopalay
也不会生效,需要和用户有交互才能播放,所以在进入房间前,有弹窗让用户确认,实现进入房间就会播放的功能。
- 后端思路
- 后端首先呢,需要歌曲资源了,我们需要用爬虫,在初始化阶段就拿到一部分歌曲作为,没人点歌的时候随机播放的音乐,这部分在源码的初始化阶段有详细注释,看个人爱好愿意初始化加载多少音乐。
- 播放歌曲的时间是有服务端控制的什么时候自动切换歌曲也是,所以服务端需要知道歌曲什么时间该切换,同时保证歌曲一直有,那么我们需要的是,项目启动的时候就开始播放音乐,如何操作呢,其实就是随机从数据库拿到一首歌曲,然后开始记录,记录当前的歌曲,然后当前歌曲的时间,当前歌曲的资源地址等等,用户进入房间就推送给用户,但是用户进入房间的时候怎么知道当前是多少秒呢,所以,我们从数据库拿到歌曲的时候需要记录一个时间戳
timespace
,用户进入房间之后,拿进入时间减去记录的时间戳就是歌曲播放时间,歌曲从这个时间播放就好了 - 那么什么时间切歌呢,自动的切歌当然需要歌曲播放完毕就切换啊,歌曲什么时候播放完毕呢?就是一首歌的时间呢,在拿到歌曲信息的时候也知道了歌曲时间,只要设置一个定时器,在歌曲时间这么多秒后执行切歌的方法就好了呀,同时,在切换的时候再次更新时间戳,我们就实现了一个自动切歌的功能了
- 当然,我们还需要用户点歌操作,如何实现呢,歌曲的搜索同样需要使用爬虫,进行搜索歌曲,搜索到歌曲之后,用户点歌会把当前歌曲id和发送给服务端,服务端会记录谁点了什么歌,当然,我们也需要有先来后到之分,所以我们需要维护一个
队列
,会按照顺序依次加入点歌用户的歌曲,这个时候,自动切歌就不会去数据库读取了,大致流程是,查看队列
有没有用户点的歌曲,没有在数据库随机
获取一个,有的话拿到队列第一首歌曲,然后切歌,再移除掉队列的歌曲,就实现了点歌自动播放了。 - 那么有人点的歌很难听怎么办,或者用户自己想移除自己点的歌呢,都很简单了,拿到歌曲id和用户信息,从队列里去掉就好了,大致思路就是如此呢,具体细节呢还有很多,可优化的点也有很多,期待大家提
issues
指出来吧
五、总结
抱着学习的态度去学习也许很慢、抱着兴趣的态度去学习就会事半功倍、把学习当做娱乐、打怪升级就会很有意思了、一个小小的个人项目分享给大家、不足的地方欢迎大家提出来、一起互相学习进步吧。