WebSocket是什么?
- WebSocket是一个基于TCP的全双工通讯协议,归属于IETF
- WebSocket API 是一个 Web API,归属于W3C
- 两个规范是独立发布的
WebSocket与HTTP有什么关系?
- WebSocket和Http并没有关系他只是HTTP协议上的一个补充,为什么说是HTTP协议的补充呢,来看下面的两张图。
- WebSocket会借用Http协议来完成一部分握手,首先浏览器发送HTTP的get请求,紧接着服务端会返回101状态码,101是Switching Protocols,当客户端通过在请求里使用Upgrade报头,以通知服务器它想改用除HTTP协议之外的其他协议时,客户端将获得此响应代码,通常HTTP客户端会在收到服务器发来的101响应后关闭与服务器的TCP连接,101响应代码意味着该客户端不再是一个HTTP客户端,而将成为另一种客户端,在发送这个响应后,将http升级到webSocket。
- Connection:必须设置Upgrade
- Upgrade:字段必须设置Websocket
- Sec-WebSocket-Key:是一个Base64 encode的值,这个是浏览器随机生成的字符串,用于验证。
- Origin:字段可选,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。
WebSocket优点?
- WebSocket是一个持久化的协议,而HTTP是非持久的无状态协议
- HTTP是被动的,一个Request对应一个Response,Response不能主动发起
- Ajax轮询的原理是让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息,需要服务器有很快的处理速度和资源
- long pool的原理跟ajax轮询差不多,都是采用轮询的方式,不过采取的是阻塞模型,客户端发起连接后,如果没消息,就一直不返回Response给客户端,直到有消息才返回,返回完之后,客户端再次建立连接,不断循环,需要服务器有很高的并发
WebSocket API
ws.readyState |
描述 |
0 |
正在连接 |
1 |
表示连接成功,可以通信 |
2 |
连接正在关闭 |
3 |
表示连接已关闭或打开连接失败 |
bufferedAmount |
描述 |
|
只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数 |
事件 |
事件处理程 |
描述 |
open |
ws.onopen |
连接建立时触发 |
message |
ws.onmessage |
接收数据时触发 |
error |
ws.onerror |
通信错误时触发 |
close |
ws.onclose |
连接关闭时触发 |
方法 |
描述 |
ws.send() |
使用连接发送数据 |
ws.close() |
关闭链接 |
WebSocket API 兼容性?
仍是存在不兼容的浏览器
WebSocket常用实现
为了解决兼容问题,websocket常用实现通常是通过socket.io。
vue+koa实战
chat.vue
<template>
<div class="chat">
<!-- 导航 -->
<van-nav-bar
:title="navTitle"
left-text=""
left-arrow
@click-left="onClickLeft"
/>
<!-- 聊天区域 -->
<div class="box">
<div
class="chat-list"
v-for="(item,index) in chatmsgs"
:key="item.id">
<div v-if="item.from == myUserInfo.userId" class="chat-left">
<img class="user-img" :src='item.avatar'/>
<div>{{item.content}}</div>
</div>
<div v-else class="chat-right">
<div>{{item.content}}</div>
<img class="user-img" :src='item.avatar'/>
</div>
</div>
</div>
<!-- 输入框 -->
<div class="chat-foot">
<input id="input" type="text" placeholder="请输入..." v-model="inputText"/>
<span @click="send">发送</span>
</div>
</div>
</template>
<script>
import axios from 'axios';
import url from '@/service.config.js';
import { mapState } from 'vuex';
import io from 'socket.io-client'
export default {
name: 'chat',
components: {
},
data() {
return {
myUserInfo: {},
navTitle: '',
userId: '',
inputText: '',
socket: '',
msgsList: [],
avatar: require('../assets/default_img.jpeg'),
flag: false,
socketData: {},
chatmsgs:[]
};
},
computed: {
...mapState(['userInfo']),
},
mounted() {
this.islogin = this.userInfo.isLogin;
this.myUserInfo = this.userInfo;
this.userId = this.$route.query.userid;
this.socket = io('ws://localhost:3000');
this.socket.on('recvmsg', data=>{
console.log(data,"监听全局消息");
this.flag = true;
this.socketData = data;
this.userInto(data.from);
})
setTimeout(()=>{
this.userInto(this.userId);
this.getMsgList();
document.addEventListener("keydown", this.keyDownEvent);
});
console.log("我的信息=======", this.myUserInfo)
},
methods: {
onClickLeft() {
this.flag = false;
window.history.go(-1);
},
userInto(user) {
axios({
url: url.userInto,
method: 'post',
data: {
userid: user,
}
}).then(res=>{
if(res.data.code == 200) {
const data = res.data.data;
if(!this.flag) {
this.navTitle = data.userName;
} else {
this.avatar = res.data.data.userHead == ''
? require('../assets/default_img.jpeg')
: res.data.data.userHead;
this.$set(this.socketData,'avatar',this.avatar);
this.msgsList=[...this.msgsList,this.socketData];
const chatid = [this.userId,this.myUserInfo.userId].sort().join('_');
this.chatmsgs = this.msgsList.filter(v=>v.chatid == chatid);
console.log("this.msgsList,this.chatmsgs=============", this.msgsList, this.chatmsgs);
}
}
}).catch(err=>{
console.log(err);
});
},
getMsgList() {
axios({
url: url.getMsgList,
method: 'post',
data: {
from: this.myUserInfo.userId,
}
}).then(res=>{
if(res.data.code == 200) {
this.msgsList = res.data.data.msgs;
this.msgsList.forEach((item,index) => {
let avatar = res.data.data.users[item.from].avatar;
let userHead = avatar=='' ? require('../assets/default_img.jpeg') :avatar
this.$set(item,'avatar',userHead);
});
const chatid = [this.userId,this.myUserInfo.userId].sort().join('_');
this.chatmsgs = this.msgsList.filter(v=>v.chatid == chatid);
}
})
},
send() {
let text = document.getElementById("input").value
const from = this.myUserInfo.userId;
const to = this.userId;
const msg = text;
this.socket.emit('sendmsg',{from,to,msg})
this.inputText='';
},
keyDownEvent(event) {
if (event.keyCode === 13) this.send();
},
}
}
</script>
koa index.js
const Koa = require('koa');
const app = new Koa();
const server = require('http').Server(app.callback());
const io = require('socket.io')(server);
const chatModel = require('./model/Chat');
const Chat = chatModel.getModel('Chat');
io.on('connection',function(socket){
console.log('socket connection');
socket.on('sendmsg', function(data){
const {from,to,msg} = data;
const chatid = [from,to].sort().join('_')
Chat.create({chatid,from,to,content:msg},function(err,doc){
console.log(doc,"创建消息");
io.emit('recvmsg',Object.assign({},doc._doc))
})
})
})
const cors = require('koa2-cors');
app.use(cors({
origin: ['xxx:8080'],
credentials: true
}));
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
const Router = require('koa-router');
let chat = require('./controller/chat.js');
let router = new Router();
router.use('/chat', chat.routes());
app.use(router.routes());
app.use(router.allowedMethods());
app.use(async ctx => {
ctx.body = 'hello word';
});
server.listen(3000, ()=>{
console.log('Server is running at port 3000...');
});
koa model/Chat.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const chatSchema = new Schema({
chatid:{'type':String,'require':true},
from: {'type':String, 'require':true},
to: {'type':String, 'require':true},
content: {'type':String, 'require':true, default:''});
mongoose.model('Chat', chatSchema);
module.exports = {
getModel:function(name){
return mongoose.model(name)
}
}
koa controller/chat.js
const Router = require('koa-router');
let router = new Router();
const mongoose = require('mongoose');
router.post('/getMsgList', async ctx => {
const Chat = mongoose.model('Chat');
let newChat = new Chat(ctx.request.body);
const user = newChat.from;
const User = mongoose.model('User');
let users = {}
await User.find({}).exec().then(async(res)=>{
res.forEach(v=>{
users[v._id] = {name:v.userName,avatar:v.userHead}
})
await Chat.find({'$or':[{from:user},{to:user}]}).exec().then((res)=>{
ctx.body = {
code: 200,
message: '成功',
data: {
msgs:res,
users:users
}
};
}).catch(err=>{
ctx.body = {
code: 500,
message: err
};
});
})
});
module.exports = router;