WebSocket
随着 web 技术的发展,使用场景和需求也越来越复杂,客户端不再满足于简单的请求得到状态的需求
实时通讯越来越多应用于各个领域
HTTP 是最常用的客户端与服务端通信技术,但 HTTP 通信只能由客户端发起,无法及时获取服务端的数据改变
只能依靠定期轮询来获取最新的状态。时效性无法保证,同时更多的请求也会增加服务器的负担
WebSocket 技术应运而生
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
Socket.IO
Socket.IO 是基于 WebSocket 的 Client-Server 实时通信库
Socket.IO 可以在客户端和服务器之间实现低延迟、双向和基于事件的通信
它建立在 WebSocket 协议之上,并提供额外的保证,例如回退到 HTTP 长轮询或自动重新连接
注意: Socket.IO 不是 WebSocket 实现。
也就是说我们不能通过 new WebSocket(URL) 的方式来连接服务端
必须使用其提供的客户端的 socket.io-client 来链接 socket.io 创建的服务
Socket.IO 屏蔽了所有底层细节,让顶层调用非常简单
当 Socket.IO 检测到当前环境不支持 WebSocket 时,能够自动地选择最佳的方式来实现网络的实时通信
Socket.IO 由两部分组成:
- 一个服务端用于集成 (或挂载) 到 Node.JS HTTP 服务器:
socket.io - 一个加载到浏览器中的客户端:
socket.io-client
Socket.IO 的核心理念就是允许发送、接收任意事件和任意数据
任意能被编码为 JSON 的对象都可以用于传输,二进制数据也是支持的
Socket.IO 底层是基于 engine.io 这个库
engine.io 为 socket.io 提供跨浏览器 / 跨设备的双向通信的底层库
engine.io 使用了 Websocket 和 XHR 方式封装了一套 socket 协议
在低版本的浏览器中,不支持 Websocket ,为了兼容使用长轮询 (polling ) 替代
Socket.IO使用
在使用上,Socket 客户端首先创建一个 socket 对象
io() 的第一个参数是链接服务器的 URL ,默认情况下是 window.location
Socket 的客户端和服务端都有两个函数 on() 、emit() 实现客户端与服务端的双向通信:
Emit :触发一个事件,第一个参数是事件名称,第二个参数是要发送到另一端的数据on :注册一个事件,用来监听 emit 触发的事件
broadcast 默认是向所有的 socket 连接进行广播,但是不包括发送者自身:
io.emit(foo); 会触发所有用户的foo事件socket.emit(foo); 只触发当前用户的foo事件socket.broadcast.emit(foo); 触发除了当前用户的其他用户的foo事件
namespace 相当于建立新的频道,你可以在一个socket.io 服务上面隔离不同的连接、事件和中间件
默认的连接也是有namespace 的,就是 /
每一个 socket 连接都会有一个独一无二的标志,那就是 socket.id
socket.id 也是room 的标志,每个 socket 连接自身都拥有一间房room
那么我们就可以给这个room 发送消息,如果加入了房间room ,就能接受到room 里的广播信息
如果 socket 断开连接,也就是 disconnect 后,它会被自动移出 room
socket.join(rooms[, callback]) :加入房间socket.leave(room[, callback]) :离开房间socket.to(room) : 给房间发送消息
Socket.IO 聊天室
最后附上基于Socket.IO 和Express 的聊天室核心代码:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require("socket.io")(http);
var users = [];
var usersInfo = [];
app.use('/', express.static(__dirname + '/www'));
io.on('connection', (socket)=> {
io.emit('disUser', usersInfo);
socket.on('login', (user)=> {
if(users.indexOf(user.name) > -1) {
socket.emit('loginError');
} else {
users.push(user.name);
usersInfo.push(user);
socket.emit('loginSuc');
socket.nickname = user.name;
io.emit('system', {
name: user.name,
status: '进入'
});
io.emit('disUser', usersInfo);
console.log(users.length + ' user connect.');
}
});
socket.on('shake', ()=> {
socket.emit('shake', {
name: '您'
});
socket.broadcast.emit('shake', {
name: socket.nickname
});
});
socket.on('sendMsg', (data)=> {
var img = '';
for(var i = 0; i < usersInfo.length; i++) {
if(usersInfo[i].name == socket.nickname) {
img = usersInfo[i].img;
}
}
socket.broadcast.emit('receiveMsg', {
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
type: data.type,
side: 'left'
});
socket.emit('receiveMsg', {
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
type: data.type,
side: 'right'
});
});
socket.on('disconnect', ()=> {
var index = users.indexOf(socket.nickname);
if(index > -1 ) {
users.splice(index, 1);
usersInfo.splice(index, 1);
io.emit('system', {
name: socket.nickname,
status: '离开'
});
io.emit('disUser', usersInfo);
console.log('a user left.');
}
});
});
http.listen(3000, function() {
console.log('listen 3000 port.');
});
$(function() {
var socket = io();
$('#name').keyup((ev)=> {
if(ev.which == 13) {
inputName();
}
});
$('#nameBtn').click(inputName);
socket.on('loginSuc', ()=> {
$('.name').hide();
})
socket.on('loginError', ()=> {
alert('用户名已存在,请重新输入!');
$('#name').val('');
});
function inputName() {
var imgN = Math.floor(Math.random()*4)+1;
if($('#name').val().trim()!=='')
socket.emit('login', {
name: $('#name').val(),
img: 'image/user' + imgN + '.jpg'
});
return false;
}
socket.on('system', (user)=> {
var data = new Date().toTimeString().substr(0, 8);
$('#messages').append(`<p class='system'><span>${data}</span><br /><span>${user.name} ${user.status}了聊天室<span></p>`);
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
socket.on('shake', (user)=> {
var data = new Date().toTimeString().substr(0, 8);
$('#messages').append(`<p class='system'><span>${data}</span><br /><span>${user.name}发送了一个窗口抖动</span></p>`);
shake();
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
socket.on('disUser', (usersInfo)=> {
displayUser(usersInfo);
});
$('#sub').click(sendMsg);
$('#m').keyup((ev)=> {
if(ev.which == 13) {
sendMsg();
}
});
socket.on('receiveMsg', (obj)=> {
if(obj.type == 'img') {
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p style="padding: 0;">${obj.msg}</p>
</div>
</li>
`);
$('#messages').scrollTop($('#messages')[0].scrollHeight);
return;
}
var msg = obj.msg;
var content = '';
while(msg.indexOf('[') > -1) {
var start = msg.indexOf('[');
var end = msg.indexOf(']');
content += '<span>'+msg.substr(0, start)+'</span>';
content += '<img src="image/emoji/emoji%20('+msg.substr(start+6, end-start-6)+').png">';
msg = msg.substr(end+1, msg.length);
}
content += '<span>'+msg+'</span>';
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p style="color: ${obj.color};">${content}</p>
</div>
</li>
`);
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
var color = '#000000';
function sendMsg() {
if($('#m').val() == '') {
alert('请输入内容!');
return false;
}
color = $('#color').val();
socket.emit('sendMsg', {
msg: $('#m').val(),
color: color,
type: 'text'
});
$('#m').val('');
return false;
}
var timer;
function shake() {
$('.main').addClass('shaking');
clearTimeout(timer);
timer = setTimeout(()=> {
$('.main').removeClass('shaking');
}, 500);
}
function displayUser(users) {
$('#users').text('');
if(!users.length) {
$('.contacts p').show();
} else {
$('.contacts p').hide();
}
$('#num').text(users.length);
for(var i = 0; i < users.length; i++) {
var $html = `<li>
<img src="${users[i].img}">
<span>${users[i].name}</span>
</li>`;
$('#users').append($html);
}
}
$('#clear').click(()=> {
$('#messages').text('');
socket.emit('disconnect');
});
init();
function init() {
for(var i = 0; i < 141; i++) {
$('.emoji').append('<li id='+i+'><img src="image/emoji/emoji ('+(i+1)+').png"></li>');
}
}
$('#smile').click(()=> {
$('.selectBox').css('display', "block");
});
$('#smile').dblclick((ev)=> {
$('.selectBox').css('display', "none");
});
$('#m').click(()=> {
$('.selectBox').css('display', "none");
});
$('.emoji li img').click((ev)=> {
ev = ev || window.event;
var src = ev.target.src;
var emoji = src.replace(/\D*/g, '').substr(6, 8);
var old = $('#m').val();
$('#m').val(old+'[emoji'+emoji+']');
$('.selectBox').css('display', "none");
});
$('.edit #shake').click(function() {
socket.emit('shake');
});
$('#file').change(function() {
var file = this.files[0];
var reader = new FileReader();
reader.onerror = function(){
console.log('读取文件失败,请重试!');
};
reader.onload = function() {
var src = reader.result;
var img = '<img class="sendImg" src="'+src+'">';
socket.emit('sendMsg', {
msg: img,
color: color,
type: 'img'
});
};
reader.readAsDataURL(file);
});
});
|