socket.io 旨在不同版本的浏览器间实现实时通信,在多种传输方式中自动选择最有效方式。
通信,双工
发送事件:socket.emit ('eventName',{data})
响应事件:socket.on( 'enventName' ,{data} )
socket.io 服务端API总结:
//发送给所有的客户端包括自己
io.emit('hello', 'to all clients');
//发送给同一个房间的所有客户端包括自己
io.to('room42').emit('hello', "to all clients in 'room42' room");
io.on('connection', (socket) => {
//只发送给自己
socket.emit("hello","only sender");
//发送给所有客户端不包括自己
socket.broadcast.emit('hello', 'to all clients except sender');
//发送给同一个房间的所有客户端,不包括自己
socket.to('room42').emit('hello', "to all clients in 'room42' room except sender");
});
在以上的学习基础之上,建立了一个小的demo,来对socket.io有个深刻的理解。
需求:实现一个多人协同编辑一段文本
服务器端采用nodejs+socket.io:
首先配置package.json
{
"name": "socket2",
"version": "1.0.0",
"description": "socket io demo",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "daisy",
"license": "ISC",
"devDependencies": {},
"dependencies": {
"socket.io": "^2.0.4",
"socket.io-redis": "^5.2.0",
"node-windows": "^0.1.14"
}
}
执行命令安装相关的依赖:
npm install
接下来开始写服务端代码:建立一个js文件,名为app.js
const io = require('socket.io').listen(8124);
const redis = require('socket.io-redis');
const client = require('redis').createClient({ host: '192.168.103.52', port: 6379, detect_buffers: true });
const adapter = redis({ host: '192.168.103.52', port: 6379 });
const prefix = 'socket.io';
const namespace = 'mindLock';
adapter.pubClient.on('error', function () { });
adapter.subClient.on('error', function () { });
io.adapter(adapter);
function noop() { }
// 根据key获取redis信息
function getInfo(key, cb = noop) {
client.get(key, (err, reply) => {
let obj;
if (!reply) obj = {};
else obj = JSON.parse(reply.toString());
cb(obj);
});
}
io.on('connection', function (socket) {
let room = socket.handshake.query.id;
let userId = socket.handshake.query.userId;
if (!room) return socket.disconnect();
socket.join(room); // 加入房间
let channel = prefix + '#' + namespace + '#' + room + '#'; // redis中存储名
var value = getInfo(channel);
//初始化状态
socket.on("init", function () {
getInfo(channel, (obj) => {
if (!obj) { return; }
if (obj.isLock && obj.userId != userId) {
socket.emit("lock");
}
socket.emit("changeValue", { value: obj.inputValue });
})
});
socket.on('newValue', function (data) {
//发送内容,不包括自己
socket.broadcast.to(room).emit("lock");
socket.broadcast.to(room).emit("changeValue", { value: data.newValue });
//将最新的数据存到数据库
var obj = { inputValue: data.newValue, isLock: true, userId: userId };
client.set(channel, JSON.stringify(obj));
});
socket.on('unlock', function () {
//手动解锁,不包括自己
socket.to(room).emit("manuallyUnlock");
getInfo(channel, (obj) => {
obj["isLock"] = false;
client.set(channel, JSON.stringify(obj));
})
});
socket.on('disconnect', () => {
//断开连接,say goodbye ,包括自己
io.to(room).emit('leave', userId + ":left");
//如果是自己锁的,就解锁
getInfo(channel, (obj) => {
if (obj.userId == userId) {
obj["isLock"] = false;
socket.to(room).emit("manuallyUnlock");
client.set(channel, JSON.stringify(obj));
}
})
io.in(room).clients((err, clients) => {
if (!clients.length) client.del(channel);
});
});
});
io.of('/').adapter.clients((err, clients) => {
console.log(clients); // an array containing all connected socket ids
});
然后再编写客户端代码
新建一个html文件,名为index.html,
客户端使用vue+element-ui
代码如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>socket.io 测试</title>
<script src="./script/socket.io.js"></script>
<script src="./script/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<div id="title">
<p>{{title}}</p>
</div>
<el-input id="input" type="textarea" :rows="6" style="width: 50%" placeholder="请输入内容" v-model="inputText" v-bind:readonly="isLock">
</el-input>
<el-button v-bind:type="lockType" icon="el-icon-goods" circle>锁</el-button>
<el-button type="primary" round v-if="!isLock" v-on:click="clickLock">解锁</el-button>
</div>
</body>
</html>
<script>
var getParam = function (name) {
var search = document.location.search;
var pattern = new RegExp("[?&]" + name + "\=([^&]+)", "g");
var matcher = pattern.exec(search);
var items = null;
if (null != matcher) {
try {
items = decodeURIComponent(decodeURIComponent(matcher[1]));
} catch (e) {
try {
items = decodeURIComponent(matcher[1]);
} catch (e) {
items = matcher[1];
}
}
}
return items;
};
function randomId() {
var tmp = "";
var str = "0123456789qwertyuioplkjhgfdsazxcvbnm";
var length = str.length;
var timestamp = new Date().getTime();
for (var i = 0; i < 5; i++) {
tmp += str.charAt(Math.floor(Math.random() * length))
}
tmp += timestamp;
return tmp;
};
window.id = this.getParam("id");
window.userId = randomId();
var socket = io.connect('http://localhost:8124', {
query: {
id: window.id,
userId: window.userId
}
});
var vm = new Vue({
el: "#app",
data: {
title: "",
inputText: "",
isLock: false,
socket: socket
},
computed: {
lockType: function () {
return this.isLock ? 'danger' : 'success';
}
},
methods: {
clickLock: function () {
if (!this.isLock) {
this.socket.emit('unlock');
}
}
// changeInput: function (data) {
// this.inputText = data;
// },
// changeTitle: function (data) {
// this.title = data;
// }
},
watch: {
inputText: function () {
if (!this.isLock && typeof (this.inputText) != "undefined") {
debugger;
this.socket.emit('newValue', { newValue: this.inputText });
}
// else {
// this.isLock = false;
// }
}
}
});
socket.emit('init');
socket.on('changeValue', function (data) {
vm.inputText = data.value;
});
socket.on('lock', function (data) {
vm.isLock = true;
});
socket.on('leave', function (data) {
vm.$message(data);
});
socket.on('manuallyUnlock', function (data) {
vm.isLock = false;
});
</script>
这样一切就准备就绪,然后启动服务器,使用命令 node app.js 。
demo展示结果
用两个浏览器打开index.html,可以看到如下的效果,同时只有一个人可以锁定编辑,锁定编辑的人可以解锁。一旦开始编辑,就上锁。编辑内容实时同步。
以上就是demo的全部代码,希望能够帮助大家学习socket.io的执行原理。
下一篇文章介绍如何将nodejs服务安装成windows服务,使得开机启动。