介绍
Server-Sent 事件 (SSE) 是一种用于在浏览器和服务器之间进行实时通信的技术。与 WebSockets 不同的是,SSE 建立在 HTTP 协议基础上,而不需要建立全双工的连接。SSE 可以用于实现类似在线聊天等实时通信的功能,具有轻量级、易用等特点。
在本文中,我们将介绍如何使用 SSE 实现一个简单的在线聊天应用。
实现原理
SSE 的原理很简单:浏览器通过向服务器发送一个 HTTP 请求,请求建立一个 SSE 连接。服务器接收到该请求后,将连接保持打开,并不断向浏览器发送事件消息。浏览器通过监听 EventSource 对象的 onmessage 事件,即可接收到事件消息,并进行相应的处理。
下面是一组示例代码,演示了如何在服务器使用 Node.js 和 Express 框架实现 SSE。
// www.javascriptcn.com code example
// server.js
const express = require('express')
const app = express()
// 定义路由,将客户端请求转发到 SSE 处理函数
app.get('/chat', function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream', // 告诉浏览器这是一个 SSE 连接
'Cache-Control': 'no-cache', // 禁止浏览器缓存响应
'Connection': 'keep-alive' // 保持响应连接打开
})
// 每隔一秒钟向客户端发送一条消息
setInterval(function() {
res.write(`data: ${Date.now()}\n\n`)
}, 1000)
})
// 在端口 8080 上启动服务器
app.listen(8080)上述代码实现了一个路由,用于处理客户端的 /chat 请求。该路由在响应头中设置了一些 SSE 相关的元信息,告诉浏览器这是一个 SSE 连接;然后使用 setInterval 定时向客户端发送一条消息,消息内容为当前时间戳。这个例子相当简单,只是为了演示 SSE 的基本实现原理。实际应用中,消息的内容和来源可能要复杂得多。
下面是客户端代码,使用了 EventSource 对象从服务器接收 SSE 消息。
// www.javascriptcn.com code example
// client.js
const eventSource = new EventSource('/chat')
// 监听事件消息
eventSource.addEventListener('message', function(event) {
// 处理消息内容
console.log(event.data)
})这段代码简单明了,只需要在 EventSource 构造函数中传入 SSE 连接的 URL,然后监听 message 事件,即可接收到服务器发送的消息。在生产环境中,建议使用 WebSocket 等专业的实时通信技术代替 SSE。
实践应用
下面我们来使用 SSE 实现一个在线聊天应用。在线聊天应用的实现方式很多,本文只介绍一种基于 SSE 的实现。该实现方式支持多个用户同时聊天,用户之间的聊天消息是实时同步的。
// www.javascriptcn.com code example
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>在线聊天</title>
<style>
.chat-box {
width: 100%;
height: 300px;
border: 1px solid #999;
overflow-y: scroll;
margin-bottom: 10px;
}
.input-box {
width: 100%;
display: flex;
margin-bottom: 10px;
}
.input-box input {
flex-grow: 1;
margin-right: 10px;
}
.input-box button {
flex-grow: 0;
}
</style>
</head>
<body>
<div class="chat-box"></div>
<div class="input-box">
<input type="text" name="message">
<button type="button">发送</button>
</div>
<script>
const chatBox = document.querySelector('.chat-box')
const inputBox = document.querySelector('.input-box')
const inputField = inputBox.querySelector('input')
const sendButton = inputBox.querySelector('button')
let userName
// 获取本地用户名
userName = localStorage.getItem('userName')
if (!userName) {
userName = prompt('请输入您的用户名:')
localStorage.setItem('userName', userName)
}
// 向服务器建立 SSE 连接
const eventSource = new EventSource('/chat')
// 监听事件消息
eventSource.addEventListener('message', function(event) {
// 解析消息内容
const message = JSON.parse(event.data)
// 将消息输出到聊天框
const msgEl = document.createElement('div')
msgEl.innerHTML = `${message.time} ${message.user} 说:${message.content}`
chatBox.appendChild(msgEl)
})
// 发送新消息
sendButton.addEventListener('click', function() {
const message = {
user: userName,
content: inputField.value
}
fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(message)
})
inputField.value = ''
})
</script>
</body>
</html>// www.javascriptcn.com code example
// server.js
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
// 存储聊天消息的队列
const chatMessages = []
// 定义路由,生成 SSE 连接
app.get('/chat', function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
// 将未读消息逐个发送给客户端
for (let i = 0; i < chatMessages.length; i++) {
res.write(`data: ${JSON.stringify(chatMessages[i])}\n\n`)
}
// 将当前连接添加到连接队列中
const clientId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
const client = { res, id: clientId }
connectionQueue.push(client)
// 监听连接断开事件
req.on('close', function() {
connectionQueue = connectionQueue.filter(c => c.id !== clientId)
console.log(`Client ${clientId} disconnected`)
})
})
// 接收新消息
app.post('/chat', bodyParser.json(), function(req, res) {
const message = req.body
console.log(`[${message.time}] ${message.user} 说:${message.content}`)
// 将聊天消息添加到消息队列中
chatMessages.push(message)
// 将消息广播给所有连接
connectionQueue.forEach(function(client) {
client.res.write(`data: ${JSON.stringify(message)}\n\n`)
})
res.send('OK')
})
// 在端口 8080 上启动服务器
app.listen(8080, function() {
console.log('Server started')
})
// 连接队列,用于保存所有连接
let connectionQueue = []在服务器端,我们维护了两个队列: chatMessages 和 connectionQueue。chatMessages 保存所有聊天消息,当有新的消息时,将消息广播给所有处于连接状态的客户端;connectionQueue 保存所有的连接对象,当一个连接断开时,将该连接从队列中删除。
注意:由于 SSE 的连接是长连接,而且当前实现方式只支持单进程单线程模型。如果同时有大量的客户端连接,可能会导致服务器的资源崩溃。在生产环境中,我们需要使用专业的实时通信服务器,比如 Socket.IO、Pusher 等。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67c923dfe46428fe9e030dd9