websocket都知道,是用于CS全双工通信的。socket.io是在此基础上进行了封装,提供了namespace、Manager等概念。
demo采用服务端渲染,所以还是用静态资源引入。服务端也是用commonJs,不太明白为什么官网都是用ES6模式引入的例子。在API用法时,会说一下差别。文档全英文,可能有理解不到位的地方,望指正。
版本:4.4.0
基本使用
1. 建立连接
// server
var app = require('../app');
var http = require('http');
var server = http.createServer(app);
let io = require('socket.io')(server);
// client
// html
// script(src='/javascripts/socket.io.js')
// js
let socket = io('');
服务端可以用npm下载,客户端就从GitHub上下了配套的client静态js,跟socket.io-client不太一样,且官网也没有静态js的文档。
首先引入一下namespace和Manager,借用官网的图看下关系。
server
client
还有个room,关系上算是socket 1 --- * room。另外manager和socket 1-n的关系没太搞懂,client看起来像单例。
按个人理解,用聊天软件举例,namespace相当于账号,room相当于群组,manager就是个人。
namespace和manager是对应的
// server
io = io.of('/namespace1');
// client 静态js是没有Manager外部接口的
let socket = io('/namespace1');
2. 通信
总体来说还是eventEmitter那一套,不过底层原理应该不一样。
client.emit -- server.on
server.emit -- client.on
emit(eventName, ...params)
on(eventName, callback)
事件名可自定义,也有一些内置事件
- server/namespace
- connection 连接后即触发
- socket disconnect 断开后触发,socket是connection callback回调的参数
- client socket
- connect 连接后触发,相当于server connection,类似登录步骤
- disconnect 断开后触发
- connect_error 连接失败,一般是重连尝试失败
- manager
client socket.io即可拿到当前manager
- reconnect_attempt 服务器断开时尝试请求,轮询
- reconnect_error 轮询失败时
- reconnect 服务器重新连接
demo
因为公司不能连外网,简单记录下;client底子是官网直接搬的
// server
let idNameMap = Object.create(null);
let numUsers = 0;
io = io.of('/namespace1');
io.on('connection', (client) => {
numUsers++;
client.emit('login', { numUsers });
client.on('add user', (name) => {
idNameMap[client.id] = name;
client.broadcast.emit('user joined', { username: name, numUsers });
});
client.on('new message', (data) => {
client.broadcast.emit('new message', data);
});
client.on('typing', () => {
client.broadcast.emit('typing', {
username: idNameMap[client.id]
});
});
client.on('stop typing', () => {
client.broadcast.emit('stop typing', {
username: idNameMap[client.id]
});
});
client.on('disconnect', () => {
numUsers--;
console.log(idNameMap, client.id, numUsers);
client.broadcast.emit('user left', {
username: idNameMap[client.id],
numUsers
});
delete idNameMap[client.id];
});
client.on('request_reconnect', () => {
client.emit('reconnect');
});
});
// client
$(() => {
const FADE_TIME = 150; // ms
const TYPING_TIMER_LENGTH = 400; // ms
const COLORS = ['#e21400', '#91580f', '#f8a700', '#f78b00', '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', '#3b88eb', '#3824aa', '#a700ff', '#d300e7'];
var $window = $(window);
var $usernameInput = $('.user-name'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.input-message'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
let loginFlag = false;
var $currentInput = $usernameInput.focus();
var timer = null;
// let socket = io();
let socket = io('/namespace1');
const addParticipantsMessage = (data) => {
var message = '';
if (data.numUsers === 1) {
message += '现在只有你一个(`・ω・´)';
} else {
message += '现在有 ' + data.numUsers + ' 只小伙伴啦( ̄▽ ̄)/';
}
log(message);
};
// Sets the client's username
const setUsername = () => {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
if (username) {
$loginPage.fadeOut();
$chatPage.show();
$loginPage.off('click');
$currentInput = $inputMessage.focus();
// Tell the server your username
socket.emit('add user', username);
}
};
// Sends a chat message
const sendMessage = () => {
var message = $inputMessage.val().trim();
// Prevent markup from being injected into the message
message = cleanInput(message);
// if there is a non-empty message and a socket connection
if (message && connected) {
$inputMessage.val('');
let data = {
username,
message
};
addChatMessage(data);
// tell server to execute 'new message' and send along one parameter
socket.emit('new message', data);
}
};
// Log a message
const log = (message, options) => {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
};
// Adds the visual chat message to the message list
const addChatMessage = (data, options) => {
// Don't fade the message in if there is an 'X was typing'
var $typingMessages = getTypingMessages(data);
options = options || {};
if ($typingMessages.length !== 0) {
options.fade = false;
$typingMessages.remove();
}
var $usernameDiv = $('<span class="username"/>').text(data.username).css('color', getUsernameColor(data.username));
var $messageBodyDiv = $('<span class="messageBody">').text(data.message);
var typingClass = data.typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>').data('username', data.username).addClass(typingClass).append($usernameDiv, $messageBodyDiv);
addMessageElement($messageDiv, options);
};
// Adds the visual chat typing message
const addChatTyping = (data) => {
data.typing = true;
data.message = '正在编辑...';
addChatMessage(data);
};
// Removes the visual chat typing message
const removeChatTyping = (data) => {
getTypingMessages(data).fadeOut(function () {
$(this).remove();
});
};
// Adds a message element to the messages and scrolls to the bottom
// el - The element to add as a message
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
const addMessageElement = (el, options) => {
var $el = $(el);
// Setup default options
if (!options) {
options = {};
}
if (typeof options.fade === 'undefined') {
options.fade = true;
}
if (typeof options.prepend === 'undefined') {
options.prepend = false;
}
// Apply options
if (options.fade) {
$el.hide().fadeIn(FADE_TIME);
}
if (options.prepend) {
$messages.prepend($el);
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
};
// Prevents input from having injected markup
const cleanInput = (input) => {
return $('<div/>').text(input).html();
};
// Updates the typing event
const updateTyping = () => {
if (connected) {
if (!typing) {
typing = true;
socket.emit('typing');
}
lastTypingTime = new Date().getTime();
setTimeout(() => {
var typingTimer = new Date().getTime();
var timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
socket.emit('stop typing');
typing = false;
}
}, TYPING_TIMER_LENGTH);
}
};
// Gets the 'X is typing' messages of a user
const getTypingMessages = (data) => {
return $('.typing.message').filter(function (i) {
return $(this).data('username') === data.username;
});
};
// Gets the color of a username through our hash function
const getUsernameColor = (username) => {
// Compute hash code
var hash = 7;
for (var i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
}
// Calculate color
var index = Math.abs(hash % COLORS.length);
return COLORS[index];
};
// Keyboard events
$window.keydown((event) => {
// Auto-focus the current input when a key is typed
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
$currentInput.focus();
}
// When the client hits ENTER on their keyboard
if (event.which === 13) {
if (username) {
sendMessage();
socket.emit('stop typing');
typing = false;
} else {
setUsername();
}
}
});
$inputMessage.on('input', () => {
updateTyping();
});
// Click events
// Focus input when clicking anywhere on login page
$loginPage.click(() => {
$currentInput.focus();
});
// Focus input when clicking on the message input's border
$inputMessage.click(() => {
$inputMessage.focus();
});
// Socket events
// Whenever the server emits 'login', log the login message
socket.on('login', (data) => {
connected = true;
// Display the welcome message
if (!loginFlag) {
loginFlag = true;
var message = '欢迎来到技术交流群(ಥ_ಥ) ';
log(message, {
prepend: true
});
addParticipantsMessage(data);
}
});
// Whenever the server emits 'new message', update the chat body
socket.on('new message', (data) => {
addChatMessage(data);
});
// Whenever the server emits 'user joined', log it in the chat body
socket.on('user joined', (data) => {
log(data.username + ' 进来了(*^▽^*)');
addParticipantsMessage(data);
});
// Whenever the server emits 'user left', log it in the chat body
socket.on('user left', (data) => {
log(data.username + ' 离开了!!!∑(゚Д゚ノ)ノ');
addParticipantsMessage(data);
removeChatTyping(data);
});
// Whenever the server emits 'typing', show the typing message
socket.on('typing', (data) => {
addChatTyping(data);
});
// Whenever the server emits 'stop typing', kill the typing message
socket.on('stop typing', (data) => {
removeChatTyping(data);
});
socket.on('connect', () => {
console.log('connect..');
});
socket.on('disconnect', () => {
log('服务器已断开...');
});
socket.io.on('reconnect', () => {
console.log('...xxx');
log('服务器已重连...');
});
socket.io.on('reconnect_attempt', () => {
// 发起轮询时
console.log('reconnect_attempt');
});
socket.io.on('reconnect_error', () => {
// 发起轮询失败时
console.log('reconnect_error');
});
socket.io.on('reconnect_failed', () => {
console.log('reconnect_failed');
});
socket.on('connect_error', () => {
log('服务器重连失败...');
});
// todo refresh
});
问题
- 服务器热更新会重置全局变量,所以有undefined离开的情况,或者重连应该强制退出?
网友评论