双向通讯解决方案

双向通讯解决方案

短轮询

短轮询是一种双向通讯解决方案,通过定期向服务器发送请求来获取最新信息。该方法适用于应用程序和服务器之间的简单交互,但会导致不必要的网络流量和服务器负载。

以下是使用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,**消息类型

  2. id ,消息的ID

  3. data 消息的数据字段。 客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。

  4. **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这样的其他解决方案。

最后更新于