WebSocket
Node.js中主要通过socket.io模块进行WebSocket网络编程,该模块提供了服务器端和客户端相关的组件,可以很方便地为应用加入WebSocket支持,并且支持不同的浏览器。
1、WebSocket的基本实现
WebSocket是从HTML5开始提供的一种浏览器与服务器间进行通信的网络技术。使用WebSocket时,浏览器和服务器只需要做一个握手的动作,然后浏览器和服务器之间就形成了一条快速通道,两者之间就可以直接进行数据的互相传送。
使用socket.io模块可以很方便地实现WebSocket网络编程,使用之前,首先需要通过npm包管理器进行下载和安装,命令如下:
js
npm install socket.io
上述命令默认下载的是socket.io模块的最新版本,如果要下载指定的版本,比如下载1.x.x,在上述命令后面添加
js
npm install socket.io@1
安装socket.io模块后,如果要在程序中使用,需要进行引入,代码如下:
js
const socketio = require('socket.io');
使用socket.io模块进行WebSocket网络编程时,通常需要3个步骤,依次为创建WebScoket服务器、创建WebSocket客户端、服务器端和客户端的通信,下面分别进行讲解。
1.1、WebSocket服务器端实现
创建WebSocket服务器时,首先需要借助http模块的createServer()方法创建一个Web服务器;然后导入socket.io模块,并在WebSocket服务器构造函数中传入创建的Web服务器,从而创建一个WebSocket服务器;最后监听connection事件,判断是否有客户端连接。
socket.io模块可以监听的服务器端事件如表所示。
| 事件名称 | 说明 |
|---|---|
connection |
客户端成功连接到服务器 |
disconnect |
客户端断开连接 |
message |
捕获客户端发送的信息 |
error |
发生错误 |
例如,下面代码中通过http模块创建了一个Web服务器,其中读取一个名称为index.html的客户端文件;然后使用socket.io模块创建一个WebSocket服务器,为WebSocket服务器设置connection监听事件,当用户在WebSocket客户端发起socket请求时,会触发该事件,监听是否有客户端连接。代码如下:
js
const fs = require("fs")
const http = require("http");
//创建Web服务器
let server = http.createServer((req, res)=>{
if (req.url == "/") {
//读取客户端文件
fs.readFile("index.html", (err, data)=>{
res.end(data);
});
}
});
server.listen(52273, function (socket) {
console.log("监听地址在:http://127.0.0.1:52273")
});
//创建WebSocket服务器
const io = require('socket.io');
let mySocket = io(server);
//监听客户端连接
mySocket.sockets.on("connection", (socket)=>{
console.log("1个客户端连接了");
});
1.2、WebSocket客户端实现
创建WebSocket客户端时,需要加载socket.io客户端代码文件,即socket.io.js,然后通过socket.io模块中的全局对象io的connect()方法来向服务器端发起连接请求。connect()方法的语法格式如下:
js
io.connect(url)
参数url为可选参数,表示要连接的WebSocket服务器地址,其可以是WebSocket服务器的http完整地址,也可以是相对路径,如果省略,则表示默认连接当前路径。
例如,在1.1节的示例代码中,可以看到使用http模块创建的Web服务器中读取了一个index.html客户端文件,该文件中主要引入socket.io.js客户端代码文件,并使用全局对象io的connect()方法向服务端发起连接请求。代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Document</title>
</head>
<body>
<h2 style="color:red;text-align: center;margin: 20px auto">我是你们朝思暮想的客户端index.html</h2>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect();
</script>
</body>
</html>
编写完index.html客户端文件后,再次运行12.1.1节中编写的服务器端文件,并单击运行结果中的链接http://127.0.0.1:52273,效果如图所示。
监听地址在:http://127.0.0.1:52273
1个客户端连接了

上面代码中,在客户端文件中引入了一个socket.io.js文件,该文件是在使用socket.io模块时自动下载到项目中的。我们可以在启动Web服务器后,在浏览器的地址栏中输入地址http://127.0.0.1:52273/socket.io/socket.io.js,查看socket.io.js文件。
1.3、服务端和客户端的通信
创建了服务器端和客户端以后,就可以在服务器端和客户端之间传输数据了。socket.io模块使用事件的方式进行数据传输,其中,socket.io可以监听的客户端事件如表所示。
| 事件名称 | 说明 |
|---|---|
connect |
成功连接到服务器 |
connecting |
正在连接 |
disconnect |
断开连接 |
connect_failed |
连接失败 |
error |
连接错误 |
message |
捕获服务器端发送的信息 |
reconnect_failed |
重新连接失败 |
reconnect |
重新连接成功 |
要实现监听和发送事件,同样使用on()方法和emit()方法,具体如下。
- on():监听socket事件。
- emit():发送socket事件。
示例:实现服务器端与客户端之间的通信。
- 编写js.js文件,该文件中首先引入相关模块,然后创建Web服务器与WebSocket服务器,使用on()方法监听connection事件,在有客户端连接时,继续监听是否有客户端发送的clientData事件,该事件是客户端自定义的一个事件,如果监听到该事件,则输出客户端传输的数据,并使用emit()方法向客户端发送一个自定义的serverData事件。js.js文件如下:
js
//引入模块
const http = require('http');
const fs = require('fs');
//创建Web服务器
let server = http.createServer((request, response)=>{
//读取index.html
fs.readFile('index.html', (error, data)=>{
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(52273, ()=>{
console.log('服务器监听地址在 http://127.0.0.1:52273');
});
//创建WebSocket服务器
let io = require('socket.io')(server);
io.sockets.on('connection', (socket)=>{
console.log('客户端已连接!');
//监听客户端的事件clientData,该事件在客户端定义
socket.on('clientData', (data)=>{
//显示客户端发来的数据
console.log('客户端发来的数据是:', data);
//向客户端发送serverData事件和数据
socket.emit('serverData', "谢谢,同乐同乐");
});
});
- 编写index.html文件,在该文件中生成socket对象,然后使用其on()方法监听服务器端发送的serverData事件,如果监听到,输出传输的数据;然后为按钮创建一个点击事件,该点击事件中,获取文本框中输入的文本内容,并使用emit()方法向服务器端发送一个自定义的clientData事件。index.html文件中的代码如下:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="/socket.io/socket.io.js"></script>
</head>
<body onload="start()">
<fieldset>
<legend>发送消息</legend>
<div><label for="text">发送内容:</label><input type="text" id="text"/></div>
<div><input type="button" id="button" value="确定"/></div>
</fieldset>
</body>
<script>
//向服务器端发起连接请求
let socket = io.connect();
function start() {
//监听服务器端的事件和数据
socket.on('serverData', (data)=>{
alert("来自服务器端的消息"+"\n" + data);
});
//创建表单点击事件
document.getElementById('button').onclick = ()=>{
//获取表单数据
let text = document.getElementById('text').value;
//向服务器端发送事件clientData和数据
socket.emit('clientData', text);
};
};
</script>
</html>

2、socket数据通信类型
使用socket.io模块进行socket数据通信,主要有3种类型,如表所示。
| 类型名称 | 说明 |
|---|---|
public |
向所有客户端传递数据(包含自己) |
broadcast |
向所有客户端传递数据(不包含自己) |
private |
向特定客户端传递数据 |
2.1、public通信类型
public通信类型的示意图如图所示。客户A向WebSocket服务器发送一个事件,WebSocket所有的客户端(客户A、客户B和客户C)都会接收到这个事件。

实现public通信的方法非常简单,直接使用io.sockets.emit()方法即可,其语法格式如下:
js
io.sockets.emit(event, data)
参数event表示要发送的事件,参数data表示要发送的数据。
示例:使用socket发布一则通知。
- 新建一个js.js文件,该文件中通过在WebSocket服务器端使用io.sockets.emit()方法向客户端发送一则通知,主要代码如下:
js
//引入模块
const http = require('http');
const fs = require('fs');
const socketio = require('socket.io');
//创建Web服务器
let server = http.createServer((request, response)=>{
//读取客户端index.html文件
fs.readFile('index.html', (error, data)=>{
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(52273, ()=>{
console.log('服务器监听地址在 http://127.0.0.1:52273');
});
//创建WebSocket服务器
let io = socketio(server);
io.sockets.on('connection', (socket)=>{
//监听客户端的事件receiveData
socket.on('receiveData', (data)=>{
console.log("客户端的消息:"+data)
io.sockets.emit('serverData', data); //发送消息(public通信类型)
});
});
- 新建客户端页面index.html,在该页面中,通过监听服务器端的serverData自定义事件,接收服务器端发送的通知并显示。代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.bold {
font-weight: bold;
}
ul {
list-style: none;
}
</style>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<form action="">
<fieldset style="width: 360px;margin: 0 auto">
<legend>发布公告</legend>
<textarea id="text" style="width: 320px;height: 90px;margin-left: 10px"></textarea><br>
<div style="text-align: center"><button id="btn" type="button">发送</button></div>
</fieldset>
<ul id="box"></ul>
</form>
<script>
window.onload = function () {
let nick = "";
let box = document.getElementById("box")
const socket = io.connect()
socket.on("serverData", (data)=>{
console.log(data)
let html1 = "<span class='bold'>" + data + "</span>"
let li = document.createElement("li")
li.innerHTML += html1
box.append(li)
})
document.getElementById("btn").onclick = function () {
let text = document.getElementById("text").value
socket.emit("receiveData", text)
}
}
</script>
</body>
</html>
运行js.js文件,然后在浏览器中多次打开http://127.0.0.1:52273网页地址(这里打开了3次),此时打开的页面的初始效果相同,如图所示。

在任意一个对话框中,输入一则通知后,单击"发送"按钮,此时就可以看到浏览器中的3个打开页面中都显示该通知,如图所示。

2.2、broadcast通信类型
broadcast通信类型的示意图如图所示。客户A向WebSocket服务器发送一个事件,WebSocket的客户A之外的其他所有客户端都会接收到这个事件。

实现broadcast通信的方法非常简单,直接使用socket.broadcast.emit()方法即可,其语法格式如下:
js
socket.broadcast.emit(event, data)
参数event表示要发送的事件,参数data表示要发送的数据。
示例:实现群发消息功能。
- 新建一个js.js文件,该文件中通过在WebSocket服务器端使用socket.broadcast.emit()方法向客户端群发消息,主要代码如下:
js
//引入模块
const http = require('http');
const fs = require('fs');
const socketio = require('socket.io');
//创建Web服务器
let server = http.createServer((request, response)=>{
//读取index.html
fs.readFile('index.html', (error, data)=>{
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(52273, ()=>{
console.log('服务器监听地址在 http://127.0.0.1:52273');
});
//创建WebSocket服务器
let io = socketio(server);
io.sockets.on('connection', (socket)=>{
//监听客户端的事件clientData
socket.on('receiveData', (data)=>{
console.log("客户端的消息:"+data)
//发送消息(broadcast通信类型)
socket.broadcast.emit('serverData', data);
});
});
- 新建客户端页面index.html,在客户端页面中,通过监听服务器端的serverData自定义事件,接收服务器端发送的消息并显示。代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<form action="">
<fieldset style="width: 200px;margin: 0 auto">
<legend>发布公告</legend>
<textarea id="text" cols="50" rows="10"></textarea><br>
<button id="btn">我写好了</button>
</fieldset>
<div id="box"></div>
</form>
<script>
let box = document.getElementById("box")
const socket = io.connect()
window.onload = function () {
socket.on("serverData", (data)=>{
let html=data+"<br>"
box.innerHTML+=html //将获取的信息添加到div中
})
document.getElementById("btn").onclick=function () {
let text = document.getElementById("text").value
socket.emit("receiveData", text) //发送消息
}
}
</script>
</body>
</html>
运行js.js文件,然后在浏览器中多次打开http://127.0.0.1:52273网页地址(这里打开了3次,并且,为方便描述,笔者按照打开顺序依次将它们称为1号客户端、2号客户端和3号客户端),此时打开的页面的初始效果相同,如图所示。

接下来,在1号客户端的文本框中输入消息后,单击"我写好了"按钮,即可将消息群发出去,这时2号客户端和3号客户端可以接收到该消息,但是1号客户端接收不到该消息,效果如图所示。

2.3、private通信类型
private通信类型的示意图如图所示。客户A向WebSocket服务器发送一个事件,WebSocket会向指定的客户端(如客户C)发送这个事件。

实现private通信的方法非常简单,直接使用io.to(id).emit()方法即可,其语法格式如下:
js
io.to(id).emit(event, data)
参数id表示客户端的名称,参数event表示要发送的事件,参数data表示要发送的数据。
示例:实现与好友聊天功能。
- 创建一个js.js文件,该文件中的only事件中的数据是其他好友向小B单独发送的内容,而all事件中的数据为群发的内容。服务器向客户端发送的事件有toOne和toMany,它们分别将单独发送的内容和群发的内容发送至客户端。js.js文件中的代码如下:
js
//引入模块
const http = require('http');
const fs = require('fs');
const socketio = require('socket.io');
//创建Web服务器
let server = http.createServer((request, response)=>{
//读取index.html
fs.readFile('index.html', (error, data)=>{
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(52273, ()=>{
console.log('服务器监听地址在 http://127.0.0.1:52273');
});
//创建列表,用来记录连接的客户端
let list = [];
//创建WebSocket服务器
let io = socketio(server);
io.sockets.on('connection', (socket)=>{
//有客户端连接时,将其id保存到列表中
list.push(socket.id)
console.log(list)
socket.on("only",(data)=>{
//向指定客户端发送private类型消息
io.to(list[0]).emit('toOne', data)
})
socket.on("all",(data)=>{
io.sockets.emit("toMany",data)
})
})
- 新建客户端页面index.html,该页面中使用socket.on()方法监听toOne和toMany事件,并将这两个事件中附带的消息内容显示在网页中。代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<form action="">
<fieldset style="width: 360px;margin: 0 auto">
<legend>发送消息</legend>
<label>内容:<input id="text" type="text"><br></label>
<div id="sendTo">
<button id="btn" type="button">群发</button>
<button id="btnB" type="button">发送给小B</button>
</div>
</fieldset>
<div id="box"></div>
</form>
<script>
let box = document.getElementById("box")
let sendTo = document.getElementById("sendTo")
let socket = io.connect()
socket.on("toOne", (data)=>{
let html1 = "<span>收到一条私信:</span>"
html1 += "<span class='bold'>" + data + "</span>"
let li = document.createElement("li")
li.innerHTML += html1
box.append(li)
})
socket.on("toMany", (data)=>{
let html1 = "<span>收到一条群发消息:</span>"
html1 += "<span class='bold'>" + data + "</span>"
let li = document.createElement("li")
li.innerHTML += html1
box.append(li)
})
document.getElementById("btn").onclick = function() {
let text = document.getElementById("text").value
socket.emit("all", text)
}
document.getElementById("btnB").onclick = function() {
let text = document.getElementById("text").value
socket.emit("only", text)
}
</script>
</body>
</html>
运行js.js文件,3次打开http://127.0.0.1:52273网页地址(按打开顺序,第一个为"小B"客户端),此时打开的3个网页的效果都如图所示。

分别在3个浏览器中模拟与好友聊天,效果如图所示。
群发:

2号给小B私信:

3、客户端分组的实现
除了3种常规通信类型外,在服务器端向客户端发送消息时,还可以对要接收消息的客户端进行分组,下面进行讲解。
3.1、创建分组
使用socket.io模块实现客户端分组功能时可以通过socket.join()方法进行分组,其语法格式如下:
js
socket.join(room)
参数room表示分组的组名。
例如,下面代码创建了两个分组:group1和group2:
js
socket.on('group1', function (data) {
socket.join('group1');
});
socket.on('group2',function(data){
socket.join('group2');
});
一个客户端可以进入多个分组。
3.2、退出分组
客户端能够进入分组,同样也可以退出分组,退出分组使用socket.leave()方法实现,其语法格式如下:
js
socket.leave(data.room);
其中,data为要退出分组的客户端;room为要退出的分组的组名。
其中,data为要退出分组的客户端;room为要退出的分组的组名。
例如,下面代码使指定用户(由客户端传递的数据data提供)退出group2分组:
js
socket.on('leavegroup2', function (data) {
var room="group2"
socket.leave(data.room);
io.sockets.in('group2').emit('leave2', data);
});
3.3、向分组中的用户发送消息
向分组中的用户发送消息时,可以分为两种情况。第一种情况是,分组中的所有人(包括自己)都可以接收到消息,这时可以使用io.sockets.in().emit()方法,其语法格式如下:
js
io.sockets.in(room).emit(event,data)
第二种情况是,分组中除自己以外的所有人都可以接收该消息,这时可以使用io.sockets.broadcast.to(). emit()方法,其语法格式如下:
js
io.sockets.broadcast.to(room).emit(event,data)
参数room表示接收该消息的分组的组名,参数event表示要发送的事件,参数data表示要发送的数据。
示例:实现进群通知和退群通知。
- 创建socket服务器端js.js文件,在服务器端监听的事件有group1、group2、leavegroup1和leavegroup2,分别表示客户端进入group1分组、客户端进入group2分组、客户端离开group1分组和客户端离开group2分组,在监听到相应的事件发生时,分别向相应的分组内发送进入和离开的通知消息。代码如下:
js
//引入模块
const fs = require('fs');
//创建服务器
const server = require('http').createServer();
const io = require('socket.io')(server);
server.on('request', (request, response)=>{
//读取客户端文件
fs.readFile('index.html', (error, data)=>{
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(data);
});
}).listen(52273, ()=>{
console.log('服务器监听地址是 http://127.0.0.1:52273');
});
//监听connection事件
io.sockets.on('connection', (socket)=>{
//监听客户端进入group1分组
socket.on('group1', (data)=>{
socket.join('group1');
io.sockets.in('group1').emit('welcome1', data);
});
//监听客户端进入group2分组
socket.on('group2', (data)=>{
socket.join('group2');
io.sockets.in('group2').emit('welcome2', data);
});
//监听客户端离开group1分组
socket.on('leavegroup1', (data)=>{
socket.leave(data.room);
io.sockets.in('group1').emit('leave1', data);
});
//监听客户端离开group2分组
socket.on('leavegroup2', (data)=>{
socket.leave(data.room);
io.sockets.in('group2').emit('leave2', data);
});
});
- 新建客户端页面index.html,在该页面中定义一个show()函数,用来以指定的格式显示进群和退群通知;然后分别监听服务器端发送的进入和离开事件,并调用show()函数显示相应的信息;另外,在客户端页面中,需要使用socket.emit()方法向服务器端发送group1、group2、leavegroup1和leavegroup2这4个事件,以便让服务器端进行监听。index.html页面的关键代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<fieldset>
<legend>进群</legend>
<label id="none">
<span>设置昵称:</span>
<input type="text" id="name" style=""/>
<button type="button" id="setName">设置名称</button>
</label>
<div class="btnbox">
<button type="button" id="group1">进入1群</button>
<button type="button" id="group2">进入2群</button>
<button type="button" id="leavegroup1">退出1群</button>
<button type="button" id="leavegroup2">退出2群</button>
</div>
<div>
<ul id="box1"><p>客户1群</p></ul>
<ul id="box2"><p>客户2群</p></ul>
</div>
</fieldset>
<script>
window.onload = function(){
let nickname = "";
let socket = io.connect();
//监听事件
//客户进入2群
socket.on("welcome2", (data)=>{
show(2, "进入", data)
})
//客户进入1群
socket.on("welcome1", (data)=>{
show(1, "进入", data)
})
//客户离开1群
socket.on("leave1", (data)=>{
show(1, "退出", data)
})
//客户离开2群
socket.on("leave2", (data)=>{
show(2, "退出", data)
})
//发送进入1群事件
document.getElementById("group1").onclick = ()=>{
if(nickname == ""){
setName()
}
socket.emit("group1", nickname)
}
//发送进入2群事件
document.getElementById("group2").onclick = ()=>{
if(nickname == ""){
setName()
}
socket.emit("group2", nickname)
}
//发送离开1群事件
document.getElementById("leavegroup1").onclick = ()=>{
socket.emit("leavegroup1", nickname)
}
//发送离开2群事件
document.getElementById("leavegroup2").onclick = ()=>{
socket.emit("leavegroup2", nickname)
}
document.getElementById("setName").onclick = ()=>{
setName()
}
function setName(){
let name = document.getElementById("name")
if(name.value == ""){
alert("请设置昵称")
} else {
nickname = name.value
document.getElementById("none").style.display = "none"
}
}
function show(room, out, data){
let box = document.getElementById("box" + room)
box.innerHTML += "<li><span class='red'>" + data + "</span><span>"
+ out + "</span><span>本群</span></li>"
}
}
</script>
</body>
</html>
运行js.js文件,然后分别打开3次http://127.0.0.1:52273网页地址,其初始效果如图所示:

设置名称后如下:

进群退群后:

4、项目示例------聊天室
服务端:
js
//引入模块
const http = require('http');
const fs = require('fs');
const socketio = require('socket.io');
//创建Web服务器
const server = http.createServer((request, response)=>{
//读取文件
fs.readFile('index.html', (error, data)=>{
response.setDefaultEncoding = 'utf-8';
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(data);
});
}).listen(52273, ()=>{
console.log('Server Running at http://127.0.0.1:52273');
});
//创建WebSocket服务器
let io = socketio(server);
//监听连接事件
io.sockets.on('connection', (socket)=>{
//监听客户端消息事件
socket.on('message', (data)=>{
//向所有客户端发送获取到的消息
io.sockets.emit('message', data);
});
});
前端实现:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<title>Document</title>
</head>
<body>
<script>
$(document).ready(function () {
//向服务器端发送连接请求
var socket = io.connect();
//监听message事件
socket.on('message', function (data) {
//显示聊天消息
var output = '';
output += '<li>';
output += ' <h3>' + data.name + '</h3>';
output += ' <p>' + data.message + '</p>';
output += ' <p>' + data.date + '</p>';
output += '</li>';
$(output).prependTo('#content');
$('#content').listview('refresh');
});
//发送事件
$('button').click(function () {
socket.emit('message', {
name: $('#name').val(),
message: $('#message').val(),
date: new Date().toUTCString()
});
});
});
</script>
<div data-role="page">
<div data-role="header">
<h1>聊天室</h1>
</div>
<div data-role="content">
<h3>昵称</h3>
<input id="name" />
<a data-role="button" href="#chatpage">开始聊天</a>
</div>
</div>
<div data-role="page" id="chatpage">
<div data-role="header">
<h1>聊天室</h1>
</div>
<div data-role="content">
<input id="message" />
<button>发送信息</button>
<ul id="content" data-role="listview" data-inset="true"></ul>
</div>
</div>
</body>
</html>
设置昵称:

聊天:
