目标:
实现服务器主动接收和发送前端的消息.
平常我们使用的ajax都是基于http的,但http的缺陷也很明显,就是必须由客户端发起,否则服务器无法主动向客户发送任何消息和数据。
如果我们非要实现服务器主动发送消息到前端,就得被迫使用“轮询”,简单的说就是隔一段时间就向服务器发送一次请求,看一下服务器有没有发送数据到前端.但这样做效率低,非常消耗资源。
解决方案,使用websocket
以下方案将借助express和express-ws来实现对话聊天功能
步骤
1、新建项目文件夹
新建一个空白项目 ws-study
,然后执行 npm init -y
进行初始化。
2、安装项目依赖
npm install express express-ws nodemon//保持js的持续运行
3、配置启动项
{
"scripts": {
"start": "nodemon server/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
}
配置后执行npm run start就可以打开服务器并热更新代码。
//注意:必须安装必须的依赖文件
4、新建public文件夹
index.html、userA.html、userB.html
5、express资源访问
项目根目录下创建service文件夹,其中创建server.js
const express = require("express")
const expressWs = require("express-ws")
//调用函数创建一个实例
const app = express();
const websocket = require("../websoket.js")
//端口号
const port = 3000;
//使用这个端口
expressWs(app)
//中间件
app.use(express.static('public'))//主动访问静态资源里面的文件
app.use('/index',express.static('public/index.html'))
app.use('/userA',express.static('public/userA.html'))
app.use('/userB',express.static('public/userB.html'))
// app.use
app.use('/ws', websocket)//要提前引用websocket框架
app.get("/*", (req,res)=>{})
// 监听端口
app.listen(port,()=>{
console.log(`监听到Server is running at http://localhost:${port}`);
})
6、比特虫生产icon图片
7、核心代码总结
userA.js中
//建立的实例
//userA是从服务器接到自己的数据,userA2从服务器接收到userB发过来的数据
const ws = new WebSocket("ws://localhost:3000/ws/userA")
const ws2 = new WebSocket("ws://localhost:3000/ws/userA2")
//ws是接收a自己发到服务器的数据,
//ws2是接收从服务器b发过来的数据
//从服务端收取数据的时候
ws.onmessage = function (res) {
//这个是自己的数据,将自己加到右侧
console.log("得到了自己发送到服务器的数据" + res);
msgbox.innerHTML += rightMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
//从服务器拿到数据的时候
ws2.onmessage = function (res) {
//这个是对方的数据,将加到左侧
console.log("得到了另一个发过来的数据" + res)
msgbox.innerHTML += leftMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
对应weboket.js中的代码
//userA发送过来的数据,将数据存到userAarr中,并将数据返回回去
router.ws("/userA",ws=>{
ws.on("message",(msg)=>{
//将userA中发过来的数据储存到userAarr这个数组中
userAarr.push(msg)
// 存了之后重新把拿到的数据返回给客户端,起验证作用
ws.send(msg)
})
})
//userA接收userB的数据,,/userA是接收自己的数据,userA2是接收从a发过来的数据
router.ws('/userA2',ws=>{
//清空定时器
let timer = null
//当断开连接的时候需要清空服务器
ws.on('close',()=>{
if(timer){
clearInterval(timer)
}
})
//定时去触发send,如果userBarr中的数据更新了的话,可以及时的发送到userA里面去
timer = setInterval(() => {
//如果userBarr有数据的话
if(userBarr.length>0){
//获取到userAarr的第一条数据
let msg = userBarr[0];
//获取到了就立即删除第一条数据,或者清空arr
userBarr.shift();
//将信息发送给userB
ws.send(msg)
}
}, 1000);
})
8、所有代码
userA.html
<div class="main">
<div class="msgbox" id="msgbox">
<div class="msg left" id="leftmsg">
<img src="./static/B.jpg" width="40" height="40" alt="">
<section>hello.你好</section>
</div>
<div class="msg right">
<section>HRLLOW.你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs </section>
<img src="./static/A.jpg" width="40" height="40" alt="">
</div>
</div>
<textarea name="" id="txt" placeholder="请输入内容"></textarea>
</div>
<div class="tips" id="tips">内容不能为空</div>
UserA<script>js
//建立的实例
//userA是从服务器接到自己的数据,userA2从服务器接收到userB发过来的数据
const ws = new WebSocket("ws://localhost:3000/ws/userA")
const ws2 = new WebSocket("ws://localhost:3000/ws/userA2")
//回调的时候
ws.onopen = function () {
//如果得到连接状态是1的话代表链接成功
console.log("如果状态码是1的话就代表链接成功");
console.log(ws.readyState);
}
//从服务端收取数据的时候
ws.onmessage = function (res) {
//这个是自己的数据,将自己加到右侧
console.log("得到了自己发送到服务器的数据" + res);
msgbox.innerHTML += rightMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
//从服务器拿到数据的时候
ws2.onmessage = function (res) {
//这个是对方的数据,将加到左侧
console.log("得到了另一个发过来的数据" + res)
msgbox.innerHTML += leftMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
//断开连接的时候
ws.onclose = function () {
console.log("已经断开了链接");
}
ws.onerror = function () {
console.log("链接错误");
}
//左边默认的数据
let leftinfo = "我是左边默认的一条消息"
leftmsg.innerHTML = (` <img src="./static/B.jpg" width="40" height="40" alt=""><section>${leftinfo}</section>`)
//生成右侧的数据,自己发送的
function rightMsgFn(value) {
var rightinfo = `<div class="msg right">
<section>${value}</section>
<img src="./static/A.jpg" width="40" height="40" alt="">
</div>`
return rightinfo
}
//生成左侧的数据,其他用户发过来的
function leftMsgFn(value) {
var rightinfo = `<div class="msg left">
<img src="./static/B.jpg" width="40" height="40" alt="">
<section>${value}</section>
</div>`
return rightinfo
}
//回车实践
txt.onkeyup = function (e) {
console.log(e.keyCode);
if (e.keyCode === 13) {
//非空判断
if (txt.value.trim() != '') {
let val = txt.value
//把val发送到服务器
ws.send(val)
//这是用于写静态验证用的,实际上是需要把从服务器发过来的信息inner到html里,实际上是加在onmessge里面的
// msgbox.innerHTML += rightMsgFn(val)
txt.value = ''
//到底部判断
var box = document.getElementById("msgbox")
//到底滑动
box.scroll({
top: box.scrollHeight,
behavior: "smooth"
})
} else {
//显示不能为空
tips.style.display = "block"
setTimeout(() => {
tips.style.display = ""
}, 1500);
}
}
}
base.css//公用css
body{
background: #efefef;
width: 100%;
height: 100vh;
box-sizing: border-box;
padding: 20px;
}
.main{
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.msgbox{
height: 70%;
width: 100%;
box-sizing: border-box;
border: 1px solid orange;
padding: 10px;
overflow: hidden;
}
textarea{
padding: 10px;
height: 20%;
width: 100%;
box-sizing: border-box;
border: 2px solid pink;
resize: none;
outline: none;
}
.msg{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.msg section{
flex: 1;
background: yellowgreen;
border-radius: 6px;
position: relative;
padding: 10px;
box-sizing: border-box;
word-break: break-all ;
margin-bottom: 10px;
}
.left section{
margin-left: 10px;
}
.left .content{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.right section{
background: wheat;
margin-right: 10px;
}
.msg section::after{
content: "";
width: 0px;height: 0px;
position: absolute;
left: -17px;top: 11px;
border: 9px solid transparent;
border-right-color: #9acd32;
}
.right section::after{
border-right-color: transparent;
border-left-color: wheat;
left: auto;
right: -17px;
}
.tips{
position: fixed;
top: 38%;
left: 38%;
width: 120px;
line-height: 35px;
text-align: center;
height: 35px;
background-color: rgba(0, 0, 0, 0.2);
display: none;
transition: all 1s;
}
userB
<body>
<div class="main">
<div class="msgbox" id="msgbox">
<div class="msg left" id="leftmsg">
<img src="./static/A.jpg" width="40" height="40" alt="">
<section>hello.你好</section>
</div>
<div class="msg right">
<section>HRLLOW.你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs你好dadsajfjsfjs </section>
<img src="./static/B.jpg" width="40" height="40" alt="">
</div>
</div>
<textarea name="" id="txt" placeholder="请输入内容"></textarea>
</div>
<div class="tips" id="tips">内容不能为空</div>
</body>
userB.js<script>
//建立的实例
//ws是从服务器接到自己的数据,ws2从服务器接收到userA发过来的数据
const ws = new WebSocket("ws://localhost:3000/ws/userB")
const ws2 = new WebSocket("ws://localhost:3000/ws/userB2")
//回调的时候
ws.onopen = function () {
//如果得到连接状态是1的话代表链接成功
console.log("如果状态码是1的话就代表链接成功");
console.log(ws.readyState);
}
//从服务端收取数据的时候
ws.onmessage = function (res) {
//这个是自己的数据,将自己加到右侧
console.log("得到了自己发送到服务器的数据" + res);
msgbox.innerHTML += rightMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
//从服务器拿到数据的时候
ws2.onmessage = function (res) {
//这个是对方的数据,将加到左侧
console.log("得到了另一个发过来的数据" + res)
msgbox.innerHTML += leftMsgFn(res.data)
// 添加盒子的滚动
msgbox.scrollTo({
top: msgbox.scrollHeight,
behavior: "smooth"
})
}
//断开连接的时候
ws.onclose = function () {
console.log("已经断开了链接");
}
ws.onerror = function () {
console.log("链接错误");
}
//左边默认的数据
let leftinfo = "我是左边默认的一条消息"
leftmsg.innerHTML = (` <img src="./static/A.jpg" width="40" height="40" alt=""><section>${leftinfo}</section>`)
//生成右侧的数据,自己发送的
function rightMsgFn(value) {
var rightinfo = `<div class="msg right">
<section>${value}</section>
<img src="./static/B.jpg" width="40" height="40" alt="">
</div>`
return rightinfo
}
//生成左侧的数据,其他用户发过来的
function leftMsgFn(value) {
var rightinfo = `<div class="msg left">
<img src="./static/A.jpg" width="40" height="40" alt="">
<section>${value}</section>
</div>`
return rightinfo
}
//回车实践
txt.onkeyup = function (e) {
console.log(e.keyCode);
if (e.keyCode === 13) {
//非空判断
if (txt.value.trim() != '') {
let val = txt.value
//把val发送到服务器
ws.send(val)
//这是用于写静态验证用的,实际上是需要把从服务器发过来的信息inner到html里,实际上是加在onmessge里面的
// msgbox.innerHTML += rightMsgFn(val)
txt.value = ''
//到底部判断
var box = document.getElementById("msgbox")
//到底滑动
box.scroll({
top: box.scrollHeight,
behavior: "smooth"
})
} else {
//显示不能为空
tips.style.display = "block"
setTimeout(() => {
tips.style.display = ""
}, 1500);
}
}
}
websoket.js
const express = require("express")
const expressWs = require("express-ws")
const router = express.Router();
expressWs(router);
//存放user1的数据
let userAarr = [];
//存放user2的数据
let userBarr = [];
//userA发送过来的数据,将数据存到userAarr中,并将数据返回回去
router.ws("/userA",ws=>{
ws.on("message",(msg)=>{
//将userA中发过来的数据储存到userAarr这个数组中
userAarr.push(msg)
// 存了之后重新把拿到的数据返回给客户端,起验证作用
ws.send(msg)
})
})
//userB接收userA的数据,,/userB是接收自己的数据,userB2是接收从a发过来的数据
router.ws('/userB2',ws=>{
//清空定时器
let timer = null
//当断开连接的时候需要清空服务器
ws.on('close',()=>{
if(timer){
clearInterval(timer)
}
})
//定时去触发send,如果userAarr中的数据更新了的话,可以及时的发送到userB里面去
timer = setInterval(() => {
//如果userAarr有数据的话
if(userAarr.length>0){
//获取到userAarr的第一条数据
let msg = userAarr[0];
//获取到了就立即删除第一条数据,或者清空arr
userAarr.shift();
//将信息发送给userB
ws.send(msg)
}
}, 1000);
})
//userB发送过来的数据,将数据存到userBarr中,并将数据返回回去
router.ws("/userB",ws=>{
ws.on("message",(msg)=>{
//将userB中发过来的数据储存到userBarr这个数组中
userBarr.push(msg)
// 存了之后重新把拿到的数据返回给客户端,起验证作用
ws.send(msg)
})
})
//userA接收userB的数据,,/userA是接收自己的数据,userA2是接收从a发过来的数据
router.ws('/userA2',ws=>{
//清空定时器
let timer = null
//当断开连接的时候需要清空服务器
ws.on('close',()=>{
if(timer){
clearInterval(timer)
}
})
//定时去触发send,如果userBarr中的数据更新了的话,可以及时的发送到userA里面去
timer = setInterval(() => {
//如果userBarr有数据的话
if(userBarr.length>0){
//获取到userAarr的第一条数据
let msg = userBarr[0];
//获取到了就立即删除第一条数据,或者清空arr
userBarr.shift();
//将信息发送给userB
ws.send(msg)
}
}, 1000);
})
//导出
module.exports = router;
效果展示
博客园https://www.cnblogs.com/jiali1010/articles
Comments | NOTHING