Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。
使用npm安装socket.io
$ npm install socket.io
创建 app.js
文件
var express = require('express');
var path = require('path');
var app = express();
app.get('/', function (req, res) {
res.sendFile(path.resolve('index.html'));
});
var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function (socket) {
console.log('客户端已经连接');
socket.on('message', function (msg) {
console.log(msg);
socket.send('sever:' + msg);
});
});
server.listen(80);
服务端运行后会在根目录动态生成socket.io的客户端js文件
客户端可以通过固定路径/socket.io/socket.io.js
添加引用
客户端加载socket.io文件后会得到一个全局的对象ioconnect
函数可以接受一个url
参数,url可以socket服务的http完整地址,也可以是相对路径,如果省略则表示默认连接当前路径
创建index.html文件
<script src="/socket.io/socket.io.js"></script>
<script>
window.onload = function(){
const socket = io.connect('/');
//监听与服务器端的连接成功事件
socket.on('connect',function(){
console.log('连接成功');
});
//监听与服务器端断开连接事件
socket.on('disconnect',function(){
console.log('断开连接');
});
};
</script>
成功建立连接后,我们可以通过socket
对象的send
函数来互相发送消息
修改index.html
var socket = io.connect('/');
socket.on('connect',function(){
//客户端连接成功后发送消息'welcome'
socket.send('welcome');
});
//客户端收到服务器发过来的消息后触发
socket.on('message',function(message){
console.log(message);
});
修改app.js
var io = require('scoket.io')(server);
io.on('connection',function(socket){
//向客户端发送消息
socket.send('欢迎光临');
//接收到客户端发过来的消息时触发
socket.on('message',function(data){
console.log(data);
});
});
send
函数只是emit
的封装node_modules\socket.io\lib\socket.js
源码function send(){
var args = toArray(arguments);
args.unshift('message');
this.emit.apply(this, args);
return this;
}
emit
函数有两个参数
事件名称 | 含义 |
---|---|
connection | 客户端成功连接到服务器 |
message | 接收到客户端发送的消息 |
disconnect | 客户端断开连接 |
error | 监听错误 |
事件名称 | 含义 |
---|---|
connect | 成功连接到服务器 |
message | 接收到服务器发送的消息 |
disconnect | 客户端断开连接 |
error | 监听错误 |
`
jsio.on('connection', function (socket) { //向客户端发送消息 socket.send('/ 欢迎光临'); //接收到客户端发过来的消息时触发 socket.on('message',function(data){ console.log('/'+data); }); }); io.of('/news').on('connection', function (socket) { //向客户端发送消息 socket.send('/news 欢迎光临'); //接收到客户端发过来的消息时触发 socket.on('message',function(data){ console.log('/news '+data); }); });
### 5.2 客户端连接命名空间
```js
window.onload = function(){
var socket = io.connect('/');
//监听与服务器端的连接成功事件
socket.on('connect',function(){
console.log('连接成功');
socket.send('welcome');
});
socket.on('message',function(message){
console.log(message);
});
//监听与服务器端断开连接事件
socket.on('disconnect',function(){
console.log('断开连接');
});
var news_socket = io.connect('/news');
//监听与服务器端的连接成功事件
news_socket.on('connect',function(){
console.log('连接成功');
socket.send('welcome');
});
news_socket.on('message',function(message){
console.log(message);
});
//监听与服务器端断开连接事件
news_socket.on('disconnect',function(){
console.log('断开连接');
});
};
socket.join('chat');//进入chat房间
socket.leave('chat');//离开chat房间
广播就是向多个客户端都发送消息
io.emit('message','全局广播');
socket.broadcast.emit('message', msg);
socket.broadcast.emit('message', msg);
从服务器的角度来提交事件,提交者会包含在内
//2. 向myroom广播一个事件,在此房间内包括自己在内的所有客户端都会收到消息
io.in('myroom').emit('message', msg);
io.of('/news').in('myRoom').emit('message',msg);
从客户端的角度来提交事件,提交者会排除在外
//2. 向myroom广播一个事件,在此房间内除了自己外的所有客户端都会收到消息
socket.broadcast.to('myroom').emit('message', msg);
socket.broadcast.to('myroom').emit('message', msg);
io.sockets.adapter.rooms
取得进入房间内所对应的所有sockets的hash值,它便是拿到的socket.id
let roomSockets = io.sockets.adapter.rooms[room].sockets;
app.js
//express+socket联合使用
//express负责 返回页面和样式等静态资源,socket.io负责 消息通信
let express = require('express');
const path = require('path');
let app = express();
app.get('/news', function (req, res) {
res.sendFile(path.resolve(__dirname, 'public/news.html'));
});
app.get('/goods', function (req, res) {
res.sendFile(path.resolve(__dirname, 'public/goods.html'));
});
let server = require('http').createServer(app);
let io = require('socket.io')(server);
//监听客户端发过来的连接
//命名是用来实现隔离的
let sockets = {};
io.on('connection', function (socket) {
//当前用户所有的房间
let rooms = [];
let username;//用户名刚开始的时候是undefined
//监听客户端发过来的消息
socket.on('message', function (message) {
if (username) {
//如果说在某个房间内的话那么他说的话只会说给房间内的人听
if (rooms.length > 0) {
for (let i = 0; i < rooms.length; i++) {
//在此处要判断是私聊还是公聊
let result = message.match(/@([^ ]+) (.+)/);
if (result) {
let toUser = result[1];
let content = result[2];
sockets[toUser].send({
username,
content,
createAt: new Date()
});
} else {
io.in(rooms[i]).emit('message', {
username,
content: message,
createAt: new Date()
});
}
}
} else {
//如果此用户不在任何一个房间内的话需要全局广播
let result = message.match(/@([^ ]+) (.+)/);
if (result) {
let toUser = result[1];
let content = result[2];
sockets[toUser].send({
username,
content,
createAt: new Date()
});
} else {
io.emit('message', {
username,
content: message,
createAt: new Date()
});
}
}
} else {
//如果用户名还没有设置过,那说明这是这个用户的第一次发言
username = message;
//在对象中缓存 key是用户名 值是socket
sockets[username] = socket;
socket.broadcast.emit('message', {
username: '系统',
content: `<a>${username}</a> 加入了聊天`,
createAt: new Date()
});
}
});
//监听客户端发过来的join类型的消息,参数是要加入的房间名
socket.on('join', function (roomName) {
let oldIndex = rooms.indexOf(roomName);
if (oldIndex == -1) {
socket.join(roomName);//相当于这个socket在服务器端进入了某个房间
rooms.push(roomName);
}
})
//当客户端告诉服务器说要离开的时候,则如果这个客户端就在房间内,则可以离开这个房间
socket.on('leave', function (roomName) {
let oldIndex = rooms.indexOf(roomName);
if (oldIndex != -1) {
socket.leave(roomName);
rooms.splice(oldIndex, 1);
}
});
socket.on('getRoomInfo', function () {
console.log(io);
//let rooms = io.manager.rooms;
console.log(io);
});
});
// io.of('/goods').on('connection', function (socket) {
// //监听客户端发过来的消息
// socket.on('message', function (message) {
// socket.send('goods:' + message);
// });
// });
server.listen(8080);
/**
* 1. 可以把服务分成多个命名空间,默认/,不同空间内不能通信
* 2. 可以把一个命名空间分成多个房间,一个客户端可以同时进入多个房间。
* 3. 如果在大厅里广播 ,那么所有在大厅里的客户端和任何房间内的客户端都能收到消息。
*/
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.1/css/bootstrap.css">
<style>
.user {
color: green;
cursor: pointer;
}
</style>
<title>聊天室</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading text-center">
<div>
<button class="btn btn-danger" onclick="join('red')">进入红房间</button>
<button class="btn btn-danger" onclick="leave('red')">离开红房间</button>
</div>
<div>
<button class="btn btn-success" onclick="join('green')">进入绿房间</button>
<button class="btn btn-success" onclick="leave('green')">进入绿房间</button>
</div>
<div>
<button class="btn btn-primary" onclick="getRoomInfo()">
获取房间信息
</button>
</div>
</div>
<div class="panel-body">
<ul class="list-group" id="messages" onclick="clickUser(event)">
</ul>
</div>
<div class="panel-footer">
<div class="row">
<div class="col-md-10">
<input id="textMsg" type="text" class="form-control">
</div>
<div class="col-md-2">
<button type="button" onclick="send()" class="btn btn-primary">发言</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io('/');
let textMsg = document.querySelector('#textMsg');
let messagesEle = document.querySelector('#messages');
socket.on('connect', function () {
console.log('客户端连接成功');
});
socket.on('message', function (messageObj) {
let li = document.createElement('li');
li.innerHTML = `<span class="user">${messageObj.username}</span>:${messageObj.content} <span class="text-right">${messageObj.createAt.toLocaleString()}</span>`;
li.className = 'list-group-item';
messagesEle.appendChild(li);
});
function send() {
let content = textMsg.value;
if (!content)
return alert('请输入聊天内容');
socket.send(content);
}
function join(name) {
//向后台服务器发送一个消息,join name是房间名
socket.emit('join2', name);
}
function leave(name) {
//向后台服务器发送一个消息,离开某个房间
socket.emit('leave3', name);
}
function getRoomInfo() {
socket.emit('getRoomInfo');
}
function clickUser(event) {
console.log('clickUser', event.target.className);
if (event.target.className == 'user') {
let username = event.target.innerHTML;
textMsg.value = `@${username} `;
}
}
</script>
</body>
</html>
let express = require('express');
let http = require('http');
let path = require('path')
let app = express();
let mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'chat'
});
connection.connect();
app.use(express.static(__dirname));
app.get('/', function (req, res) {
res.header('Content-Type', "text/html;charset=utf8");
res.sendFile(path.resolve('index.html'));
});
let server = http.createServer(app);
//因为websocket协议是要依赖http协议实现握手的,所以需要把httpserver的实例的传给socket.io
let io = require('socket.io')(server);
const SYSTEM = '系统';
//保存着所有的用户名和它的socket对象的对应关系
let sockets = {};
let mysockets = {};
let messages = [];//从旧往新旧的 slice
//在服务器监听客户端的连接
io.on('connection', function (socket) {
console.log('socket', socket.id)
mysockets[socket.id] = socket;
//用户名,默认为undefined
let username;
//放置着此客户端所在的房间
let rooms = [];
// 私聊的语法 @用户名 内容
socket.on('message', function (message) {
if (username) {
//首先要判断是私聊还是公聊
let result = message.match(/@([^ ]+) (.+)/);
if (result) {//有值表示匹配上了
let toUser = result[1];//toUser是一个用户名 socket
let content = result[2];
let toSocket = sockets[toUser];
if (toSocket) {
toSocket.send({
user: username,
content,
createAt: new Date()
});
} else {
socket.send({
user: SYSTEM,
content: `你私聊的用户不在线`,
createAt: new Date()
});
}
} else {//无值表示未匹配上
//对于客户端的发言,如果客户端不在任何一个房间内则认为是公共广播,大厅和所有的房间内的人都听的到。
//如果在某个房间内,则认为是向房间内广播 ,则只有它所在的房间的人才能看到,包括自己
let messageObj = {
user: username,
content: message,
createAt: new Date()
};
//相当于持久化消息对象
//messages.push(messageObj);
connection.query(`INSERT INTO message(user,content,createAt) VALUES(?,?,?)`, [messageObj.user, messageObj.content, messageObj.createAt], function (err, results) {
console.log(results);
});
if (rooms.length > 0) {
/**
socket.emit('message', {
user: username,
content: message,
createAt: new Date()
});
rooms.forEach(room => {
//向房间内的所有的人广播 ,包括自己
io.in(room).emit('message', {
user: username,
content: message,
createAt: new Date()
});
//如何向房间内除了自己之外的其它人广播
socket.broadcast.to(room).emit('message', {
user: username,
content: message,
createAt: new Date()
});
});
*/
let targetSockets = {};
rooms.forEach(room => {
let roomSockets = io.sockets.adapter.rooms[room].sockets;
console.log('roomSockets', roomSockets);//{id1:true,id2:true}
Object.keys(roomSockets).forEach(socketId => {
if (!targetSockets[socketId]) {
targetSockets[socketId] = true;
}
});
});
Object.keys(targetSockets).forEach(socketId => {
mysockets[socketId].emit('message', messageObj);
});
} else {
io.emit('message', messageObj);
}
}
} else {
//把此用户的第一次发言当成用户名
username = message;
//当得到用户名之后,把socket赋给sockets[username]
sockets[username] = socket;
//socket.broadcast表示向除自己以外的所有的人广播
socket.broadcast.emit('message', { user: SYSTEM, content: `${username}加入了聊天室`, createAt: new Date() });
}
});
socket.on('join', function (roomName) {
if (rooms.indexOf(roomName) == -1) {
//socket.join表示进入某个房间
socket.join(roomName);
rooms.push(roomName);
socket.send({
user: SYSTEM,
content: `你成功进入了${roomName}房间!`,
createAt: new Date()
});
//告诉客户端你已经成功进入了某个房间
socket.emit('joined', roomName);
} else {
socket.send({
user: SYSTEM,
content: `你已经在${roomName}房间了!请不要重复进入!`,
createAt: new Date()
});
}
});
socket.on('leave', function (roomName) {
let index = rooms.indexOf(roomName);
if (index == -1) {
socket.send({
user: SYSTEM,
content: `你并不在${roomName}房间,离开个毛!`,
createAt: new Date()
});
} else {
socket.leave(roomName);
rooms.splice(index, 1);
socket.send({
user: SYSTEM,
content: `你已经离开了${roomName}房间!`,
createAt: new Date()
});
socket.emit('leaved', roomName);
}
});
socket.on('getAllMessages', function () {
//let latestMessages = messages.slice(messages.length - 20);
connection.query(`SELECT * FROM message ORDER BY id DESC limit 20`, function (err, results) {
// 21 20 ........2
socket.emit('allMessages', results.reverse());// 2 .... 21
});
});
});
server.listen(8080);
/**
* socket.send 向某个人说话
* io.emit('message'); 向所有的客户端说话
*
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<style>
.user {
color: red;
cursor: pointer;
}
</style>
<title>socket.io</title>
</head>
<body>
<div class="container" style="margin-top:30px;">
<div class="row">
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="text-center">欢迎来到珠峰聊天室</h4>
<div class="row">
<div class="col-xs-6 text-center">
<button id="join-red" onclick="join('red')" class="btn btn-danger">进入红房间</button>
<button id="leave-red" style="display: none" onclick="leave('red')" class="btn btn-danger">离开红房间</button>
</div>
<div class="col-xs-6 text-center">
<button id="join-green" onclick="join('green')" class="btn btn-success">进入绿房间</button>
<button id="leave-green" style="display: none" onclick="leave('green')" class="btn btn-success">离开绿房间</button>
</div>
</div>
</div>
<div class="panel-body">
<ul id="messages" class="list-group" onclick="talkTo(event)" style="height:500px;overflow-y:scroll">
</ul>
</div>
<div class="panel-footer">
<div class="row">
<div class="col-xs-11">
<input onkeyup="onKey(event)" type="text" class="form-control" id="content">
</div>
<div class="col-xs-1">
<button class="btn btn-primary" onclick="send(event)">发言</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script>
let contentInput = document.getElementById('content');//输入框
let messagesUl = document.getElementById('messages');//列表
let socket = io('/');//io new Websocket();
socket.on('connect', function () {
console.log('客户端连接成功');
//告诉服务器,我是一个新的客户,请给我最近的20条消息
socket.emit('getAllMessages');
});
socket.on('allMessages', function (messages) {
let html = messages.map(messageObj => `
<li class="list-group-item"><span class="user">${messageObj.user}</span>:${messageObj.content} <span class="pull-right">${new Date(messageObj.createAt).toLocaleString()}</span></li>
`).join('');
messagesUl.innerHTML = html;
messagesUl.scrollTop = messagesUl.scrollHeight;
});
socket.on('message', function (messageObj) {
let li = document.createElement('li');
li.className = "list-group-item";
li.innerHTML = `<span class="user">${messageObj.user}</span>:${messageObj.content} <span class="pull-right">${new Date(messageObj.createAt).toLocaleString()}</span>`;
messagesUl.appendChild(li);
messagesUl.scrollTop = messagesUl.scrollHeight;
});
// click delegate
function talkTo(event) {
if (event.target.className == 'user') {
let username = event.target.innerText;
contentInput.value = `@${username} `;
}
}
//进入某个房间
function join(roomName) {
//告诉服务器,我这个客户端将要在服务器进入某个房间
socket.emit('join', roomName);
}
socket.on('joined', function (roomName) {
document.querySelector(`#leave-${roomName}`).style.display = 'inline-block';
document.querySelector(`#join-${roomName}`).style.display = 'none';
});
socket.on('leaved', function (roomName) {
document.querySelector(`#join-${roomName}`).style.display = 'inline-block';
document.querySelector(`#leave-${roomName}`).style.display = 'none';
});
//离开某个房间
function leave(roomName) {
socket.emit('leave', roomName);
}
function send() {
let content = contentInput.value;
if (content) {
socket.send(content);
contentInput.value = '';
} else {
alert('聊天信息不能为空!');
}
}
function onKey(event) {
let code = event.keyCode;
if (code == 13) {
send();
}
}
</script>
</body>
</html>