ajax轮询是这个意思
// 客户端一发送小心给服务器,服务器接收信息,发送给客户端二,客户端二是每隔固定的时间去询问服务端有没有客户端 二的消息,这样很消耗性能
// ajax轮询不是实时的,而webSocket是建立长连接,一次连接实时更新信息,没有断连的情况,所以ajax轮询非常消耗性能
// http能不能实现聊天室的效果?
// http基于 请求 ---->响应
// 因为http请求给后端发送一个请求,然后会响应给发送请求者,
// 这个响应信息无法发送给另一个人所以http无法做到聊天室的功能
websocket介绍
// WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工通信
// 全双工通信的意思就是:允许客户端给服务器主动发送信息,也支持服务端给另一个客户端发送信息.
websocket属于长连接的一种持久协议
// websocket是一种持久协议,http是非持久协议.
// 现在很多网站都有实时推送的需求,比如聊天室,客服咨询等等
// 早期没有websocket的时候,一般通过ajax轮询,由于http请求,服务器无法给浏览器主动发送数据,
// 因此需要浏览器定时的给服务器发送请求(比如1s一次),服务器把最新的数据响应给浏览器.这种模式的缺点就是浪费性能和资源
WebSocket比较不好的地方就是纯原生实现聊天室
// WebSocket的缺点,没有一个广播的事件或者说方法
// 需要我们自己去封装一个,封装一个广播事件的方法broadcast函数,并且broadcast函数内部职能发送字符串类型
// 如果你发送别的对象数组等等服务端就会报错,所以我们还需要用到JSON.stringify去转换一下发给前端,并且前端还需要哦转
// WebSocket的事件非常的少,用来用去就那几个事件方法
// 所以我们更多的是用一个框架去开发聊天室类的网站
// 我们使用socket.io这个框架去开发
// 我们基于socket.io开发一个完整的聊天室
下面就是我在使用原生的websocket的demo
index.html文件代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>websocket</title>
</head>
<style>
div {
width: 200px;
height: 200px;
border: 1px solid #000;
}
</style>
<body>
<div>npm install nodejs-websocket</div>
<!-- 输入内容 -->
<input type="text" placeholder="请输入您的内容">
<!-- 发送请求的 -->
<button>发送消息</button>
<!-- 显示结果 -->
<div class="d1"></div>
</body>
<script>
var TYPE_ENTER = 0
var TYPE_LEAVE = 1
var TYPE_MSG = 2
var input = document.querySelector("input")
var button = document.querySelector("button")
var d1 = document.querySelector(".d1")
// 演示websocket在浏览器端如何使用
// H5已经直接提供了webSocket的API,所以我们可以直接去使用
// 创建websocket
// 1.参数一为websocket的服务地址,ws://localhost:6060
// 参数一不能写http://localhost:6060
var socket = new WebSocket("ws://localhost:6060")
// 2.open事件当websocket连接成功的时候触发
socket.addEventListener("open", function () {
d1.innerHTML = "连接服务已经成功了!"
})
// 3.主动的给websocket服务端发送消息
// socket.send(value)这个方法把value发送给服务器
// 在Network里面有个WS里面有headers和messages就可以看到请求的信息
// messages里面向上的绿色箭头表示我们前端发送给服务器的内容,向下的橙色箭头表示服务端响应给前端的内容
button.addEventListener("click", function () {
var value = input.value
// socket.send(value)这个方法把value发送给服务器
socket.send(value)
input.value = ""
})
// 4.接收websocket服务的数据
socket.addEventListener("message", function (e) {
console.log(e)
console.log("前端接收到后端响应回来的信息", e.data)
var dv = document.createElement("li")
dv.innerHTML = e.data
d1.appendChild(dv)
})
// 5.断开连接
socket.addEventListener("close", function () {
alert("与服务器断开连接!")
})
</script>
</html>
下面是文件夹位置
服务器的代码
fuwu.js
// 导入node-websocket包
var ws = require("nodejs-websocket")
var TYPE_ENTER=0
var TYPE_LEAVE=1
var TYPE_MSG=2
// 如何处理用户的请求
// 每次只要有用户连接,函数就会被执行,会给当前连接的用户创建一个connect对象
var count = 0
var server = ws.createServer(connect => {
console.log("有用户连接上来了!")
count++
connect.userName = `用户${count}`
// 有用户连接进来了,告诉所有用户,有人进来了聊天室
broadcast({
type:TYPE_ENTER,
msg:`${connect.userName}进入聊天室`,
time:new Date().toLocaleDateString()
})
// 监听用户发过来的数据使用connet.on事件
connect.on("text", data => {
console.log("接收到用户传给服务端的数据", data)
// // 给用户一个响应的数据
// connect.send(data.toUpperCase() + "!!!!!")
// 给所有的用户广播客户发送过来的消息
broadcast({
type:TYPE_MSG,
msg:data,
time:new Date().toLocaleDateString()
})
})
// 只要websocket连接断开,close事件就会触发
connect.on("close", () => {
count--
console.log("服务器断开连接!")
// 用户断连,告诉所有的用户,有人离开了聊天室
broadcast({
type:TYPE_LEAVE,
msg:`${connect.userName}离开了聊天室`,
time:new Date().toLocaleDateString()
})
})
// 注意一定要有error事件不然只有close事件就会报错dos
// 注册一个error,事件,处理用户的错误信息,用户发生异常!
connect.on("error", () => {
console.log('用户连接异常!')
})
})
// 广播,给所有的用户发送消息
function broadcast(msg) {
// server.connections:表示所有的用户
server.connections.forEach((item) => {
// 注意广播的时候不允许传一个对象只允许传一个JSON字符串
item.send(JSON.stringify(msg))
// 这儿是一个坑注意注意
})
}
server.listen(6060, () => {
console.log("服务器端口号6060已经成功开启!", "佛祖保佑无我写的代码一切安好无bug");
})
// WebSocket比较不好的地方就是纯原生实现聊天室
// WebSocket的缺点,没有一个广播的事件或者说方法
// 需要我们自己去封装一个,封装一个广播事件的方法broadcast函数,并且broadcast函数内部职能发送字符串类型
// 如果你发送别的对象数组等等服务端就会报错,所以我们还需要用到JSON.stringify去转换一下发给前端,并且前端还需要哦转
// WebSocket的事件非常的少,用来用去就那几个事件方法
// 所以我们更多的是用一个框架去开发聊天室类的网站
// 我们使用socket.io这个框架去开发
// 我们基于socket.io开发一个完整的聊天室
io.emit可以用来广播不需要封装函数对传输内容也没有限制
// 用户断开连接的功能
// 监听用户断开连接
// 每个用户都有连接,如果浏览器断联就会自动触发disconnect事件
// 前端不需要写emit disconnect事件
socket.on("disconnect", function (data) {
// 把当前用户的信息从users中删除
var idx = users.findIndex(item => item.username === socket.username)
// 删除掉断开连接的人
users.splice(idx, 1)
// 1.告诉所有人,有人离开了聊天室
io.emit("delUser",{
username:socket.username,
avatar:socket.avatar
})
// 2.告诉所有人,userList发生更新
io.emit("userList",users)
})
我们基于socket.io做出来的demo聊天室
登录页面的demo样例图片
聊天的页面
为了更加的简单便捷的去开发一个websocket项目我们使用到了socket.io去开发
我们基于socket.io开发的demo文件夹层级关系
server.js
var express = require("express")
var app = express()
var server = require("http").Server(app)
var io = require("socket.io")(server)
// 记录已经登录过的用户
var users = []
app.use(express.static("./public"))
server.listen(8083, () => {
console.log("服务器8083开启成功~!")
})
app.get("/", function (req, res) {
res.redirect("/index.html")
})
io.on("connection", function (socket) {
// 用户连接的功能
socket.on("login", function (data) {
// 判断,如果data在user中存在,说明该用户已经登录了不允许登录
// 如果data在users中不存在,说明该用户没有登录,允许用户登录
var user = users.find(item => item.username === data.username)
if (user) {
// 表示用户存在,登录失败!服务器需要给当前用户响应,告诉登录失败
socket.emit("loginError", {
msg: "登陆失败"
})
console.log("登录失败!")
} else {
// 表示用户不存在,登录成功!
users.push(data)
socket.emit("loginSuccess", data)
console.log("登录成功!")
// socket.io广播消息如下
// socket.emit是告诉单个人的事件
// io.emit是广播给所有用户的事件
// 告诉所有的用户.目前聊天室谁进入聊天室
io.emit("addUser", data)
// 告诉所有的用户.目前聊天室中有多少人
io.emit("userList", users)
// 把登录成功的用户名和头像存储起来
socket.username = data.username
socket.avatar = data.avatar
}
})
// 用户断开连接的功能
// 监听用户断开连接
// 每个用户都有连接,如果浏览器断联就会自动触发disconnect事件
// 前端不需要写emit disconnect事件
socket.on("disconnect", function (data) {
// 把当前用户的信息从users中删除
var idx = users.findIndex(item => item.username === socket.username)
// 删除掉断开连接的人
users.splice(idx, 1)
// 1.告诉所有人,有人离开了聊天室
io.emit("delUser",{
username:socket.username,
avatar:socket.avatar
})
// 2.告诉所有人,userList发生更新
io.emit("userList",users)
})
// 监听聊天的消息
socket.on("sendMessage",data=>{
console.log(data)
// 广播给所有的用户
io.emit("receiveMessage",data)
})
//接收图片信息
socket.on("sendImage",data=>{
console.log(data)
// 广播给所有的用户
io.emit("receiveImage",data)
})
})
index.js
/* global io */
/* global $ */
/* 聊天室的主要功能 */
// 1.连接socketio服务
var socket = io('http://localhost:8083')
var username, avatar
var toName = '群聊'
// 2.登录功能
$('#login_avatar li').on('click', function () {
$(this).addClass('now').siblings().removeClass('now')
})
// 点击按钮,登录
$('#loginBtn').on('click', function () {
// 获取用户名
var username = $('#username').val().trim()
if (!username) {
window.alert('请输入用户名')
return
}
if (username === '群聊') {
window.alert('用户名已存在')
return
}
// 获取选择的头像
var avatar = $('#login_avatar li.now img').attr('src')
console.log(username, avatar)
// 需要告诉socket io服务,登录
// 前端携带username和avatar去触发后端的login事件
socket.emit('login', {
username,
avatar
})
})
// 监听登录失败的请求
socket.on('loginError', data => {
window.alert('用户名已存在')
})
// 监听登录成功的请求
socket.on('loginSuccess', data => {
console.log('登录成功')
// 登录成功
// 隐藏登录窗口
// $('.login_box').fadeOut()
$('.login_box').hide()
// 显示聊天窗口
$('.container').fadeIn()
// 设置个人信息
$('.user-list .header img').attr('src', data.avatar)
$('.user-list .header .username').text(data.username)
username = data.username
avatar = data.avatar
})
// 监听添加用户的消息
socket.on('addUser', data => {
// 添加一条系统消息
$('.box-bd').append(`
<div class="system">
<p class="message_system">
<span class="content">${data.username} 加入群聊</span>
</p>
</div>
`)
scrollIntoView()
})
// 监听用户离开的消息
socket.on('delUser', data => {
// 添加一条系统消息
$('.box-bd').append(`
<div class="system leave">
<p class="message_system">
<span class="content">${data.username} 离开了群聊</span>
</p>
</div>
`)
scrollIntoView()
})
// 监听用户列表的消息
socket.on('userList', data => {
// 把userlist
$('.user-list ul').html('')
$('.user-list ul').append(`
<li class="user">
<div class="avatar"><img src="images/群聊.jpg" alt=""></div>
<div class="name">群聊</div>
</li>
`)
data.forEach(item => {
$('.user-list ul').append(`
<li class="user">
<div class="avatar"><img src="${item.avatar}" alt=""></div>
<div class="name">${item.username}</div>
</li>
`)
})
$('#userCount').text(data.length)
clickUser()
})
// 聊天功能
$('#btn-send').on('click', () => {
var content = $('#content').html().trim()
$('#content').html('')
if (!content) {
return window.alert('请输入内容')
}
// 发送消息给服务器
socket.emit('sendMessage', {
msg: content,
username,
avatar
})
})
// 监听接收聊天消息
socket.on('receiveMessage', data => {
console.log('收掉消息', data)
if (data.toName === '群聊') {
if (username === data.username) {
// 自己的消息
$('.box-bd').append(`
<div class="message-box">
<div class="my message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="content">
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
</div>
</div>
</div>
</div>
`)
} else {
// 别人的消息
$('.box-bd').append(`
<div class="message-box">
<div class="other message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="nickname">${data.username}</div>
<div class="content">
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
</div>
</div>
</div>
</div>
`)
}
} else {
if (username === data.username) {
// 自己的消息
$('.box-bd').append(`
<div class="message-box">
<div class="my message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="content">
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
<div class="bubble_toName">私聊</div>
</div>
</div>
</div>
</div>
`)
} else {
// 别人的消息
$('.box-bd').append(`
<div class="message-box">
<div class="other message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="nickname">${data.username}</div>
<div class="content">
<div class="bubble">
<div class="bubble_cont">${data.msg}</div>
<div class="bubble_toName">私聊</div>
</div>
</div>
</div>
</div>
`)
}
}
scrollIntoView()
})
// 当有消息时,将滑动到底部
function scrollIntoView () {
// 当前元素的底部滚动到可视区
$('.box-bd').children(':last').get(0).scrollIntoView(false)
}
// 发送图片功能
$('#file').on('change', function () {
var file = this.files[0]
// // 需要把这个图片发送到服务器,借助于H5新增的fileReader
var fr = new window.FileReader()
fr.readAsDataURL(file)
fr.onload = function () {
socket.emit('sendImage', {
username,
avatar,
img: fr.result,
toName
})
}
})
// 监听接收图片消息
socket.on('receiveImage', data => {
if (username === data.username) {
// 自己的消息
$('.box-bd').append(`
<div class="message-box">
<div class="my message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="content">
<div class="bubble">
<div class="bubble_cont">
<img src="${data.img}">
</div>
</div>
</div>
</div>
</div>
`)
} else {
// 别人的消息
$('.box-bd').append(`
<div class="message-box">
<div class="other message">
<img src="${data.avatar}" alt="" class="avatar">
<div class="nickname">${data.username}</div>
<div class="content">
<div class="bubble">
<div class="bubble_cont">
<img src="${data.img}">
</div>
</div>
</div>
</div>
</div>
`)
}
// 等待图片加载完成
$('.box-bd img:last').on('load', function () {
scrollIntoView()
})
})
// 初始化jquery-emoji插件
$('.face').on('click', function () {
// 点击.face的时候再去初始化
$('#content').emoji({
// 设置触发表情包的表情按钮
button: '.face',
// 表示只有一组标签的时候显不显示tab
showTab: true,
animation: 'slide',
position: 'topRight',
icons: [{
name: 'QQ表情',
path: 'lib/jquery-emoji/img/qq/',
maxNum: 91,
excludeNums: [41, 45, 54],
file: '.gif'
}]
})
})
// // 扩展:私聊功能
// $('#btn-send').on('click', () => {
// var content = $('#content').html().trim()
// console.log(content)
// if (!content) {
// return window.alert('请输入内容')
// }
// if (toName === '群聊') {
// // 发送消息给服务器
// socket.emit('sendMessage', {
// msg: content,
// username,
// avatar,
// toName
// })
// } else {
// // 发送私聊消息给服务器
// socket.emit('sendMessageToOne', {
// msg: content,
// username,
// avatar,
// toName: toName
// })
// }
// $('#content').html('')
// })
// 点击用户事件绑定
function clickUser () {
$('.user').on('click', function () {
$(this).addClass('active').siblings().removeClass('active')
var to = $(this).children('.name').text()
$('#chatName').text(to)
toName = to
})
}