JavaScript 长连接
提示
在 JavaScript 中,"长连接"(Long Connection)通常指的是一种持久化的网络通信方式,与传统的短连接(每次请求 - 响应后断开连接)不同,
长连接会保持客户端与服务器之间的连接状态,允许双方在任意时间双向发送数据。常见方案:长轮询、短轮询、websocket
长连接的作用
长连接的主要优势是实时性高,避免了频繁建立连接的开销,适合需要实时数据交互的场景。WebSocket 是目前实现长连接的最优方案,得到了所有现代浏览器的支持
示例:WebSocket
HTML5 标准中定义的全双工通信协议
建立在 TCP 之上,通过一次握手后保持持久连接
允许服务器主动向客户端推送数据
典型用途:实时聊天、股票行情、在线游戏等
- 在客户端
html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 客户端</title>
<style>
.container { max-width: 800px; margin: 20px auto; padding: 20px; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
.message { margin: 5px 0; padding: 8px; border-radius: 4px; }
.incoming { background-color: #e6f2ff; }
.outgoing { background-color: #f0f0f0; text-align: right; }
#inputForm { display: flex; gap: 10px; }
#messageInput { flex: 1; padding: 8px; }
button { padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #45a049; }
</style>
</head>
<body>
<div class="container">
<h1>WebSocket 聊天</h1>
<div id="messages"></div>
<form id="inputForm">
<input type="text" id="messageInput" placeholder="输入消息...">
<button type="submit">发送</button>
</form>
</div>
<script>
// 连接到 WebSocket 服务器
const ws = new WebSocket('ws://localhost:8080');
// 获取 DOM 元素
const messagesDiv = document.getElementById('messages');
const inputForm = document.getElementById('inputForm');
const messageInput = document.getElementById('messageInput');
// 连接成功时触发
ws.onopen = () => {
console.log('WebSocket 连接已建立');
addMessage('系统消息', '已连接到服务器', 'incoming');
};
// 接收到服务器消息时触发
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
addMessage('服务器', event.data, 'incoming');
};
// 连接关闭时触发
ws.onclose = () => {
console.log('WebSocket 连接已关闭');
addMessage('系统消息', '与服务器的连接已关闭', 'incoming');
};
// 发生错误时触发
ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
addMessage('系统消息', `错误: ${error}`, 'incoming');
};
// 表单提交事件
inputForm.addEventListener('submit', (e) => {
e.preventDefault();
const message = messageInput.value.trim();
if (message && ws.readyState === WebSocket.OPEN) {
// 发送消息到服务器
ws.send(message);
addMessage('我', message, 'outgoing');
messageInput.value = '';
}
});
// 添加消息到界面
function addMessage(sender, text, type) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
messageDiv.innerHTML = `<strong>${sender}:</strong> ${text}`;
messagesDiv.appendChild(messageDiv);
// 滚动到底部
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>- 在服务端
js
// 安装依赖: npm install ws
const WebSocket = require('ws');
// 创建 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket 服务器运行在 ws://localhost:8080');
// 当有客户端连接时
wss.on('connection', (ws) => {
console.log('新客户端已连接');
// 向客户端发送欢迎消息
ws.send('欢迎连接到 WebSocket 服务器!');
// 当收到客户端消息时
ws.on('message', (message) => {
console.log(`收到消息: ${message}`);
// 将消息广播给所有连接的客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
// 当连接关闭时
ws.on('close', () => {
console.log('客户端已断开连接');
});
// 发生错误时
ws.on('error', (error) => {
console.error('WebSocket 错误:', error);
});
});示例:长轮询
工作原理:客户端发送请求后,服务器不会立即响应,而是等待有数据或超时才返回。客户端收到响应后立即再次发起请求,保持连接 "长开"
客户端:每次请求成功后或者失败后再次发送请求
服务端:服务端使用 setInterval 定时查库,如果有新数据则直接返回,如果没有则等待超时
- 在客户端
js
// 客户端长轮询实现
function longPoll() {
// 发起请求
fetch('/long-poll')
.then(response => {
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => {
// 处理服务器返回的数据
console.log('收到数据:', data);
document.getElementById('result').textContent = JSON.stringify(data);
// 数据处理完成后,立即发起下一次轮询
longPoll();
})
.catch(error => {
console.error('轮询出错:', error);
// 出错后延迟一段时间再重试,避免频繁请求
setTimeout(longPoll, 3000);
});
}
// 页面加载完成后开始第一次轮询
window.onload = longPoll;- 在服务端
js
// 服务器端实现(使用Express框架)
const express = require('express');
const app = express();
const port = 3000;
// 存储等待中的请求
let pendingRequests = [];
// 模拟数据更新
function simulateDataUpdate() {
setInterval(() => {
const newData = {
timestamp: new Date().toISOString(),
message: '这是一条新消息',
random: Math.random()
};
// 如果有等待中的请求,立即响应
if (pendingRequests.length > 0) {
const resolve = pendingRequests.shift();
resolve(newData);
}
}, 5000); // 每5秒产生一次数据更新
}
// 启动模拟数据更新
simulateDataUpdate();
// 长轮询接口
app.get('/long-poll', (req, res) => {
// 设置超时时间(略小于客户端超时)
const timeout = 30000; // 30秒
// 存储当前请求的resolve函数,等待数据更新
const promise = new Promise((resolve) => {
pendingRequests.push(resolve);
// 超时处理
setTimeout(() => {
// 从等待队列中移除
const index = pendingRequests.indexOf(resolve);
if (index !== -1) {
pendingRequests.splice(index, 1);
}
// 返回空数据,表示超时
resolve(null);
}, timeout);
});
// 当有数据或超时时响应客户端
promise.then(data => {
res.json(data || { timeout: true });
});
});
// 提供客户端页面
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>长轮询示例</title>
</head>
<body>
<h1>长轮询数据接收</h1>
<div id="result"></div>
<script src="/client.js"></script>
</body>
</html>
`);
});
// 静态文件服务
app.use(express.static('public'));
app.listen(port, () => {
console.log(`长轮询服务器运行在 http://localhost:${port}`);
});示例:短轮询
工作原理:客户端设置一个定时器,按照固定间隔(如 2 秒)向服务器发送请求
在客户端:在ajax 的请求的 finally 回调中使用 setTimeout 定时再次发送 ajax 请求
在服务端:无需额外设置
js
// 短轮询客户端实现
class ShortPollingClient {
constructor(url, interval = 3000) {
this.url = url;
this.interval = interval; // 轮询间隔,默认3秒
this.polling = false;
this.timer = null;
}
// 开始轮询
start() {
if (this.polling) return;
this.polling = true;
console.log('开始短轮询...');
this.fetchData(); // 立即执行一次
}
// 停止轮询
stop() {
this.polling = false;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
console.log('已停止短轮询');
}
// 发送请求获取数据
async fetchData() {
if (!this.polling) return;
try {
const response = await fetch(this.url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
this.handleNewData(data);
} catch (error) {
console.error('轮询出错:', error);
} finally {
// 无论成功失败,都设置下一次轮询
if (this.polling) {
this.timer = setTimeout(() => this.fetchData(), this.interval);
}
}
}
// 处理新数据的回调函数
handleNewData(data) {
console.log('收到新数据:', data);
// 这里可以添加更新UI等逻辑
}
}
// 使用示例
// 启动轮询
const client = new ShortPollingClient('/api/data', 2000); // 每2秒轮询一次
client.start();
// 5秒后停止轮询(仅作示例)
setTimeout(() => {
client.stop();
// 10秒后再次启动轮询(仅作示例)
setTimeout(() => {
client.start();
}, 10000);
}, 5000);