使用HTTP/2实现服务端主动推送消息给客户端

news2024/11/25 2:42:29

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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/685006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试真题(Java),素数伴侣(100%通过+复盘思路)

一、题目描述 若两个正整数的和为素数&#xff0c;则这两个正整数称之为“素数伴侣”&#xff0c;如2和5、6和13&#xff0c;它们能应用于通信加密。现在密码学会请你设计一个程序&#xff0c;从已有的 N &#xff08; N 为偶数&#xff09;个正整数中挑选出若干对组成“素数伴…

老鸟是这样实现springboot日志打印的~

文章目录 前言一、实现一个全局日志打印二、使用步骤1. 新增一个自定义注解2. 拦截注解,并实现相应的打印日志功能3. 使用 总结 前言 项目中有时候为了与前端,与后端(微服务/远程调用http) 等撕逼,我们不得不做好应对措施,最终的就是打印清晰我们的入参出参日志,这为以后撕逼,…

Nik Silver Efex 黑白胶片效果滤镜

Nik Silver Efex 为获得优质黑白效果而精心设计算法&#xff0c;是世界领先级的黑白胶片滤镜集。 内置 64 个黑白&#xff08;包括单色、双色等&#xff09;预设供选择&#xff0c;主要分为经典 Classic、现代 Modern、复古 Vintage、阿弗格 En Vogue等四大风格&#xff0c;另外…

银行数字化转型导师坚鹏:兴业银行《天才与算法》读书拆解培训

兴业银行杭州分行《天才与算法》读书拆解培训圆满结束 兴业银行股份有限公司&#xff08;简称“兴业银行”&#xff09;成立于1988年8月&#xff0c;2022年总资产9.27万亿元&#xff0c;是经国务院、中国人民银行批准成立的首批股份制商业银行之一&#xff0c;总行设在福州市。…

【MySQL 高级(进阶)SQL 语句】

目录 一、命令操作1、select ----显示表格中一个或数个字段的所有数据记录2、select 指定字段的显示顺序3、select distinct 不显示重复的数据记录4、where 有条件的查询5、and和or 命令 ---- 且和或6、in 显示已知的值的数据记录7、between 显示两个值范围内的数据记录8、通配…

最短路径算法(Python数学建模)

0. 前言 最短路径算法是一种用于计算图中两个节点之间最短路径的算法。在图论中&#xff0c;最短路径通常指的是图中连接两个节点的路径中具有最小权重&#xff08;或成本&#xff09;的路径。 以下是两种常见的最短路径算法&#xff1a; Dijkstra算法&#xff1a;Dijkstra算…

Python对csv文件一键多值保存为json本地文件再读取加速效率(3)

最近发现做办公自动化表格匹配的时候还是csv格式的文件最快、效率是最高的 今天接到一个需求就是大致内容之这样的 1、给我一张表格直邮一列A列&#xff0c;内容是运单号 2、需要用相同的单号去另外一张表格匹配数据 3、其实就是Excel中的常见的vlookup 但是想要匹配的表格有几…

D351周赛复盘:美丽下标对数目(互质/数学运算)+数组划分若干子数组

文章目录 6466.美丽下标对数目思路互质的含义 python写法cpp写法 6910. 将数组划分成若干好子数组的方式思路完整版ans (ans * (ls[i 1] - ls[i]))含义重要问题1&#xff1a;为什么ls[i 1] - ls[i]能代表所有这两个1划分出来的子数组&#xff1f;重要问题2&#xff1a;为什么…

java 版本企业招标投标管理系统源码,多个行业+及时准确+全程电子化

计算机与网络技术的不断发展&#xff0c;推动了社会各行业信息化的步伐。时至今日&#xff0c;电子政务、电子商务已经非常普及&#xff0c;云计算、大数据、工业4.0、“互联网”等发展理念也逐步深入人心&#xff0c;如何将传统行业与互联网科技有效结合起来&#xff0c;产生1…

谷歌浏览器无法翻译成中文,谷歌翻译,最新(沉浸式翻译和划词翻译,chrome无法翻译,谷歌浏览器无法翻译此网页)

简介&#xff1a;谷歌浏览器自带的翻译功能&#xff0c;对我们来说用处很大&#xff0c;但有的时候突然就会变成“无法翻译此网页”&#xff0c;之前给大家提供过两种无法翻译此网页的解决方案&#xff0c;这次再给大家分享下两款别的翻译方法&#xff1b; 一、上次介绍&#x…

如何用ChatGPT使开发效率提高50%以上?

简介 ChatGPT是一个大型语言模型&#xff0c;由OpenAI开发。它被训练用于进行对话式交互&#xff0c;能够理解和生成自然语言文本。ChatGPT可以用于多种任务和场景&#xff0c;包括但不限于&#xff1a;智能助手、创意生成、语言学习、编程辅助等。ChatGPT的优势在于它的广泛知…

搭建个人音乐库-navidrome

文章目录 前言navidrome 服务搭建准备音乐材料客户端 前言 搭建个人音乐库。好玩而已。 没有做过多的筛选。在navidrome 和 koel 之间&#xff0c;选择了navidrome来搭建音乐库。 主要分为这几个步骤&#xff1a; 在服务器上搭建navidrome服务(web端)。准备音乐材料。手机上…

免费去除视频水印的几个方法!记得收藏好!

怎么去除视频水印&#xff1f;相信大家应该不难发现&#xff0c;现在很多网上的视频保存下来都会有水印&#xff0c;原本是想拿这些视频当作素材来使用&#xff0c;结果就是让人很苦恼&#xff0c;但其实我们可以借助记灵在线工具将它们的水印去掉&#xff0c;下面我给大家分享…

谷歌带走了我最爱的全文翻译,连夜找来1个复活方法和6个替代神器!

想必前段时间大家都被谷歌翻译退出中国的相关文章刷屏过了 虽然表面上谷歌官方给出的原因是中国用户太少、使用率太低了&#xff0c;所以才选择退出中国市场。但根据网上的猜测&#xff0c;更大的可能应该是因为给谷歌翻译接入的 googleapis.com 在9月28日因某种神秘力量而国内…

JVM原理简介

前言 JVM一直是java知识里面进阶阶段的重要部分&#xff0c;如果希望在java领域研究的更深入&#xff0c;则JVM则是如论如何也避开不了的话题&#xff0c;本系列试图通过简洁易读的方式&#xff0c;讲解JVM必要的知识点。 运行流程 我们都知道java一直宣传的口号是&#xff1a;…

应急响应篇之Linux入侵排查

0x00 前言 当企业发生黑客入侵、系统崩溃或其它影响业务正常运行的安全事件时&#xff0c;急需第一时间进行处理&#xff0c;使企业的网络信息系 统在最短时间内恢复正常工作&#xff0c;进一步查找入侵来源&#xff0c;还原入侵事故过程&#xff0c;同时给出解决方案与防范措…

【CV】从分类到回归:常见算法评价指标,如ROC,MAP等

目录 分类问题准确率&#xff08;Accuracy&#xff09;精确率&#xff08;Precision&#xff09;召回率或真正率&#xff08;Recall&#xff09;假正率&#xff08;False Positive Rate, FPR&#xff09;特异性&#xff08;Specificity&#xff09;ROC曲线P-R曲线&#xff08;精…

AI换脸背后的产业链详解,往后神仙姐姐背后有可能是......

近期在各大平台都可以看到换脸新闻&#xff0c;和大家分享一下最近让我心痛的一张图片 那除了大家用来恶搞之外&#xff0c;AI诈骗的新闻层出不穷。我们国内目前今天最大的是下面这起事件&#xff1a; 而国外&#xff0c;因为技术更加成熟一点&#xff0c;所以被诈骗的金额高达…

PostgreSQL修炼之道之数据库优化(十八)

12.1 数据库优化准则和方法 12.1.1 数据库优化准则 数据库优化的思路有很多种。比较常用的是下面两种优化思路。 第一种思路&#xff1a;有人说过&#xff0c;“The fastest way to do something is dont do it”&#xff0c;意思是说&#xff0c;“做得最快的方法就是不做”…

mysql 集群实验~~双主双从搭建

这里写目录标题 搭建mysql集群实现双主双从的同步一、部署环境1.1 分别安装mysql服务并设置域名解析 二、 配置双主MySQL服务器2.1由于是双主&#xff0c;所以master2是master1的从&#xff0c;同时master1又是master2的从所以都要开启二进制和中继日志2.2 重启服务2.3 测试双主…