双向通讯解决方案

双向通讯解决方案

短轮询

短轮询是一种双向通讯解决方案,通过定期向服务器发送请求来获取最新信息。该方法适用于应用程序和服务器之间的简单交互,但会导致不必要的网络流量和服务器负载。
以下是使用JavaScript实现短轮询的示例代码:
function pollServer() { fetch('/api/updates') .then(response => response.json()) .then(data => { // 处理从服务器返回的数据 console.log(data); // 继续轮询 setTimeout(pollServer, 5000); }) .catch(error => { // 处理错误 console.error(error); // 继续轮询 setTimeout(pollServer, 5000); }); } // 启动轮询 pollServer();
在这个示例中,pollServer() 函数使用 fetch() 方法向服务器发送请求,并处理从服务器返回的 JSON 数据。然后,函数使用 setTimeout() 方法在 5 秒后继续轮询。如果发生错误,函数将处理错误并继续轮询。
 

长轮询

长轮询是一种双向通讯解决方案,它通过将客户端的请求挂起,等待服务器有新数据时再返回响应。相比于短轮询,长轮询能够减少不必要的网络流量和服务器负载,同时也能够更快地响应新数据。
以下是使用 Node.js 实现长轮询的示例代码:
const http = require('http'); function longPolling(req, res) { // 模拟异步操作,随机时间后返回数据 setTimeout(() => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ data: 'Hello, world!' })); }, Math.floor(Math.random() * 20000)); // 随机时间 0~20s } http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/api/updates') { longPolling(req, res); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found'); } }).listen(3000, () => { console.log('Server listening on port 3000'); });
在这个示例中, longPolling() 函数模拟了一个异步操作,随机时间后返回数据。然后,服务器启动并监听在端口 3000 上。当客户端向 /api/updates 发送 GET 请求时,服务器将调用 longPolling() 函数,并在有新数据时返回响应。如果发生错误或客户端关闭连接,函数将终止并重新开始监听。
长轮询的开销比短轮询小一些,但是缺点也很明显,一条长轮询请求就会占用一条请求连接,但是长时间的保持请求连接也会消耗一定的资源。
 

使用iframe实现双向通讯

另一种实现双向通讯的方法是使用iframe。在这种方法中,应用程序将一个隐藏的iframe插入到HTML页面中,然后通过该iframe与服务器进行通信。服务器可以在iframe中插入脚本以响应来自客户端的请求,并将数据发送回客户端以更新应用程序。
以下是使用JavaScript实现iframe通讯的示例代码:
<!-- 在HTML页面中插入一个隐藏的iframe --> <iframe id="my-iframe" style="display:none;"></iframe> <script> // 获取iframe元素 const iframe = document.getElementById('my-iframe'); // 定义处理消息的回调函数 function handleMessage(event) { // 处理从服务器返回的数据 console.log(event.data); } // 添加消息事件监听器,监听postMessage window.addEventListener('message', handleMessage); // 向服务器发送请求 iframe.src = '/api/updates'; </script>
在这个示例中,应用程序将一个隐藏的iframe插入到HTML页面中,并使用JavaScript获取该iframe元素。然后,应用程序定义一个处理消息的回调函数,并使用window.addEventListener()方法将其添加到message事件监听器中。最后,应用程序将iframe的src属性设置为服务器的URL,以向服务器发送请求。
服务器可以在响应中插入JavaScript脚本以更新应用程序。以下是一个使用Node.js实现的服务器示例代码:
const http = require('http'); function handleRequest(req, res) { // 检查请求是否来自iframe if (req.headers['x-requested-with'] === 'XMLHttpRequest') { // 插入脚本以更新应用程序 res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('<script>window.parent.postMessage({ data: "Hello, world!" }, "*");</script>'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found'); } } http.createServer(handleRequest).listen(3000, () => { console.log('Server listening on port 3000'); });
在这个示例中,服务器检查请求是否来自iframe,并在响应中插入JavaScript脚本以更新应用程序。脚本使用window.parent.postMessage()方法将数据发送回客户端。
这种方式的缺点在浏览器会一直处于加载中的状态,浏览器标签页会出现加载状态图标。
 

使用SSE实现双向通讯

SSE(Server-Sent Events,服务器推送事件)是一种HTML5技术,它允许服务器向客户端发送数据,而无需客户端发起请求。SSE使用HTTP协议,并且可以在浏览器和服务器之间建立持久连接,以便服务器能够实时地向客户端发送数据。
以下是使用JavaScript实现SSE通讯的示例代码:
const eventSource = new EventSource('/api/updates'); eventSource.addEventListener('ping', function(data){ // 处理从服务器返回的数据 console.log(event.data); }) eventSource.onerror = function(error) { // 处理错误 console.error(error); };
在这个示例中,EventSource对象用于创建SSE连接,并监听ping事件来处理从服务器返回的数据。如果发生错误,onerror事件处理程序将会被调用。
SSE的消息是有格式要求的,规范定义了四个字段:
  1. event,消息类型
  1. id ,消息的ID
  1. data 消息的数据字段。 客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。
  1. retry,指定客户端重连的时间。只接受整数,单位是毫秒。如果这个值不是整数则会被自动忽略。
四个字段采用name: value的形式定义,且每个字段之间用换行符隔开,最后用两个换行符表示消息结束。
以下是使用Node.js实现SSE的服务器示例代码:
const http = require("http"); const path = require("path"); const fs = require("fs"); http.createServer((req, res) => { if (req.url === "/events" && (req.headers.accept && req.headers.accept === 'text/event-stream')) { res.setHeader("content-type", "text/event-stream"); // 告诉浏览器使用事件流 setInterval(() => { res.write("event: ping\n"); // 事件类型 res.write(`id: ${+new Date()}\n`); // 消息 ID res.write("data: 7\n"); // 消息数据 res.write("retry: 10000\n"); // 重连时间 res.write("\n\n"); // 消息结束 }, 1000); return; } else if (req.url === "/index.html") { res.setHeader("content-type", "text/html"); const data = fs.readFileSync(path.join(__dirname, "./index.html")); res.end(data); return; } res.end("true"); }).listen(5500);
在这个示例中,服务器首先检查请求是否来自SSE连接,如果是,则会将响应头字段Content-Type设置为text/event-stream,然后使用setInterval()方法每隔1秒向客户端发送一个事件。
相较于Websocket而言,SSE更轻量,开发成本更低,更适合做服务器推送类的服务,但是缺点是在HTTP2之前版本使用时,由于浏览器请求连接数量的限制,通常最多在所有标签页中只支持6个连接。
此外为了避免连接中断,需要额外实现心跳机制或者断线重连机制

Websocket

WebSocket是另一种双向通讯解决方案,允许客户端和服务器之间进行实时全双工通讯。与长轮询和SSE不同,WebSocket提供了一种持久连接,可以实现低延迟通讯和减少网络开销。
以下是使用Node.js实现WebSocket的示例代码(此处使用了ws库):
const WebSocket = require('ws'); const server = new WebSocket.Server({ port: 3000 }); server.on('connection', (socket) => { console.log('Client connected'); socket.on('message', (message) => { console.log(`Received message: ${message}`); // Echo the message back to the client socket.send(`Echo: ${message}`); }); socket.on('close', () => { console.log('Client disconnected'); }); });
在这个示例中,使用ws模块创建了一个WebSocket服务器,并在端口3000上进行监听。当客户端连接时,服务器记录一个消息并设置事件监听器,用于接收来自客户端的消息和断开连接的通知。当服务器接收到消息时,记录它并将其回显回客户端。
要在客户端JavaScript应用程序中使用WebSocket,可以创建一个新的WebSocket对象,并设置事件监听器,用于接收来自服务器的消息和连接状态的更改通知。以下是一个示例:
const socket = new WebSocket('ws://localhost:3000'); socket.addEventListener('open', (event) => { console.log('Connected to server'); }); socket.addEventListener('message', (event) => { console.log(`Received message: ${event.data}`); }); socket.addEventListener('close', (event) => { console.log('Disconnected from server'); });
在这个示例中,创建了一个新的WebSocket对象,并连接到ws://localhost:3000的服务器。当连接建立时,客户端记录一个消息并设置事件监听器,用于接收来自服务器的消息和连接状态的更改通知。当客户端接收到消息时,记录它。
WebSocket是实时低延迟双向通讯的绝佳选择。但是,它可能需要更多的服务器资源和更复杂的客户端代码,而不像长轮询或SSE这样的其他解决方案。