77. 使用HTTP/2实现服务端主动推送消息给客户端
HTTP/2
协议的服务器主动推送机制是通过服务器在接收到客户端请求后,主动向客户端推送相关资源的方式来实现的。下面将详细解释如何在服务器端和客户端实现HTTP/2
的服务器主动推送,并给出相应的代码示例。
客户端实现:
前端使用EventSource
对象订阅/api/subscribe
接口,监听服务器发送的事件。
const eventSource = new EventSource('/api/subscribe');
eventSource.onmessage = event => {
const data = JSON.parse(event.data);
console.log('Received data:', data);
// 判断接收到的数据是否是最终结果
if (data.status === 'completed') {
// 停止订阅
eventSource.close();
}
};
eventSource.onerror = error => {
console.error('SSE error:', error);
};
服务器端实现:
后端在接收到订阅请求后,先使用stream.pushStream()
方法向客户端推送中间结果,然后再模拟等待一段时间后返回最终结果。
const http2 = require('http2');
const fs = require('fs');
// 创建HTTP/2服务器
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
});
// 处理订阅请求
server.on('stream', (stream, headers) => {
// 模拟等待特定操作的时间
setTimeout(() => {
// 推送中间结果给客户端
stream.pushStream({ ':path': '/api/subscribe' }, (pushStream) => {
pushStream.respond({
'content-type': 'application/json',
':status': 200
});
pushStream.end(JSON.stringify({ status: 'processing' }));
});
// 模拟等待更长时间后返回最终结果
setTimeout(() => {
stream.respond({
'content-type': 'application/json',
':status': 200
});
stream.end(JSON.stringify({ status: 'completed', result: 'Operation completed' }));
}, 5000); // 等待5秒钟
}, 2000); // 等待2秒钟
});
// 监听端口
server.listen(3000, () => {
console.log('Server started on port 3000');
});
通过结合长轮询和HTTP/2
的服务器推送,可以实现前端请求接口后等待一段时间,直到后端执行了特定操作后再将结果返回给前端的需求。
请注意,上述示例代码仅为演示目的,并未考虑错误处理、安全性等方面的细节。在实际应用中,需要根据具体情况进行适当的调整和优化。
每日一游 - 贪吃蛇小游戏
<html>
<head>
<title>贪吃蛇游戏</title>
<style>
/* 游戏画布样式 */
#game-canvas {
width: 400px;
height: 400px;
border: 1px solid black;
position: relative;
}
/* 蛇身和食物样式 */
.snake-segment {
width: 20px;
height: 20px;
background-color: green;
position: absolute;
}
.food {
width: 20px;
height: 20px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<h1>贪吃蛇游戏</h1>
<div id="game-canvas"></div>
<div>
<p>得分:<span class="score">0</span></p>
<button id="restart-button">重新开始</button>
</div>
<script>
// 获取游戏画布元素
const gameCanvas = document.getElementById('game-canvas');
// 定义游戏画布的宽度和高度
const canvasWidth = 400;
const canvasHeight = 400;
// 定义蛇身的尺寸和初始位置
const segmentSize = 20;
let snakeSegments = [
{ x: segmentSize * 3, y: 0 },
{ x: segmentSize * 2, y: 0 },
{ x: segmentSize, y: 0 }
];
// 随机生成初始位置
const initialX = Math.floor(Math.random() * (canvasWidth / segmentSize - 3) + 3) * segmentSize;
const initialY = Math.floor(Math.random() * (canvasHeight / segmentSize)) * segmentSize;
// 定义蛇的初始移动方向
let direction = 'right';
// 定义食物的初始位置
let food = { x: 200, y: 200 };
// 创建蛇身元素和食物元素
function createSegmentElement(x, y) {
const segment = document.createElement('div');
segment.className = 'snake-segment';
segment.style.left = x + 'px';
segment.style.top = y + 'px';
return segment;
}
function createFoodElement(x, y) {
const foodElement = document.createElement('div');
foodElement.className = 'food';
foodElement.style.left = x + 'px';
foodElement.style.top = y + 'px';
return foodElement;
}
// 更新蛇身的位置
function updateSnakePosition() {
const segments = document.getElementsByClassName('snake-segment');
for (let i = 0; i < snakeSegments.length; i++) {
segments[i].style.left = snakeSegments[i].x + 'px';
segments[i].style.top = snakeSegments[i].y + 'px';
}
}
// 更新食物的位置
function updateFoodPosition() {
const foodElement = document.querySelector('.food');
foodElement.style.left = food.x + 'px';
foodElement.style.top = food.y + 'px';
}
// 清除画布
function clearCanvas() {
gameCanvas.innerHTML = '';
}
// 绘制蛇身
function drawSnake() {
snakeSegments.forEach(segment => {
const segmentElement = createSegmentElement(segment.x, segment.y);
gameCanvas.appendChild(segmentElement);
});
}
// 绘制食物
function drawFood() {
const foodElement = createFoodElement(food.x, food.y);
gameCanvas.appendChild(foodElement);
}
// 显示得分
function getScore() {
const scoreText = document.querySelector(".score")
scoreText.innerText = snakeSegments.length - 3
}
// 移动蛇的函数
function moveSnake() {
// 创建新的蛇头
const head = { x: snakeSegments[0].x, y: snakeSegments[0].y };
// 根据方向更新蛇头的位置
switch (direction) {
case 'up':
head.y -= segmentSize;
break;
case 'down':
head.y += segmentSize;
break;
case 'left':
head.x -= segmentSize;
break;
case 'right':
head.x += segmentSize;
break;
}
// 将新的蛇头插入到蛇身数组的第一个位置
snakeSegments.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
// 生成新的食物位置
initFoodPosition();
} else {
// 如果没有吃到食物,移除蛇身数组的最后一个元素,即蛇尾
snakeSegments.pop();
}
// 清除画布
clearCanvas();
// 绘制蛇身和食物
drawSnake();
drawFood();
// 实时分数更新
getScore()
// 检查游戏是否结束
if (isCollision()) {
clearInterval(gameLoop);
alert('游戏结束!得分:' + (snakeSegments.length - 3));
}
}
// 检查蛇头是否与蛇身或游戏边界发生碰撞
function isCollision() {
const head = snakeSegments[0];
// 检查是否与蛇身碰撞
for (let i = 1; i < snakeSegments.length; i++) {
if (head.x === snakeSegments[i].x && head.y === snakeSegments[i].y) {
return true;
}
}
// 检查是否与游戏边界碰撞
if (
head.x < 0 ||
head.x >= canvasWidth ||
head.y < 0 ||
head.y >= canvasHeight
) {
return true;
}
return false;
}
// 监听键盘按键事件,改变蛇的移动方向
document.addEventListener('keydown', function(event) {
switch (event.key) {
case 'ArrowUp':
if (direction !== 'down') {
direction = 'up';
}
break;
case 'ArrowDown':
if (direction !== 'up') {
direction = 'down';
}
break;
case 'ArrowLeft':
if (direction !== 'right') {
direction = 'left';
}
break;
case 'ArrowRight':
if (direction !== 'left') {
direction = 'right';
}
break;
}
});
// 初始化食物位置
function initFoodPosition() {
let validPosition = false;
while (!validPosition) {
food.x = Math.floor(Math.random() * (canvasWidth / segmentSize)) * segmentSize;
food.y = Math.floor(Math.random() * (canvasHeight / segmentSize)) * segmentSize;
// 检查食物位置是否与蛇身重叠
let overlap = false;
for (let i = 0; i < snakeSegments.length; i++) {
if (food.x === snakeSegments[i].x && food.y === snakeSegments[i].y) {
overlap = true;
break;
}
}
if (!overlap) {
validPosition = true;
}
}
}
// 获取重新开始按钮元素
const restartButton = document.getElementById('restart-button');
// 重新开始游戏
function restartGame() {
clearInterval(gameLoop); // 清除游戏循环
snakeSegments = [
{ x: segmentSize * 3, y: 0 },
{ x: segmentSize * 2, y: 0 },
{ x: segmentSize, y: 0 }
]; // 重置蛇身位置
direction = 'right'; // 重置蛇的移动方向
initFoodPosition(); // 重新生成食物位置
clearCanvas(); // 清除画布
drawSnake(); // 绘制蛇身
drawFood(); // 绘制食物
gameLoop = setInterval(moveSnake, 500); // 启动游戏循环
}
// 监听重新开始按钮的点击事件
restartButton.addEventListener('click', restartGame);
// 初始化游戏
function initGame() {
// 生成初始的食物位置
initFoodPosition();
// 将蛇身元素和食物元素添加到游戏画布中
drawSnake();
drawFood();
// 启动游戏循环
gameLoop = setInterval(moveSnake, 200);
}
// 开始游戏
initGame();
</script>
</body>
</html>