连夜撸了一个简易聊天室

从物联网到聊天室:我是如何用MQTT协议重构了一个十万人在线的WebSocket项目 本文分享了如何通过MQTT协议重构一个十万人在线的WebSocket项目,解决了连接管理、消息延迟和离线同步等关键问题。通过引入EMQX作为MQTT Broker,系统性能显著提升,连接建立耗时降低4倍,群消息延迟减少6.7倍,服务器数量减少72%。文章还总结了协议选型和分层架构的实战经验。 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:cnblogs.com/

rynxiao/p/13825438.html

分不清轮询、长轮询?不知道什么时候该用websocket还是SSE,看这篇就够了。

所谓的“实时推送”,从表面意思上来看是,客户端订阅的内容在发生改变时,服务器能够实时地通知客户端,进而客户端进行相应地反应。客户端不需要主观地发送请求去获取自己关心的内容,而是由服务器端进行“推送”。

注意上面的推送二字打了引号,这就意味着在现有的几种实现方式中,并不是服务器端主动地推送,而是通过一定的手段营造了一种实时的假象。就目前现有的几种技术而言,主要有以下几类:

  • 客户端轮询:传统意义上的轮询(Short Polling)

  • 服务器端轮询:长轮询(Long Polling)

  • 全双工通信:Websocket

  • 单向服务器推送:Server-Sent Events(SSE)

文中会以一个简易聊天室的例子来分别通过上述的四种方式实现,代码地址mini-chatroom(存在些许bug,主要是为了做演示用)

overview

轮询(Short Polling)

轮询的实现原理:客户端向服务器端发送一个请求,服务器返回数据,然后客户端根据服务器端返回的数据进行处理;然后客户端继续向服务器端发送请求,继续重复以上的步骤,如果不想给服务器端太大的压力,一般情况下会设置一个请求的时间间隔。

shortPolling

使用轮询明显的优点是基础不需要额外的开发成本,请求数据,解析数据,作出响应,仅此而已,然后不断重复。缺点也显而易见:

  • 不断的发送和关闭请求,对服务器的压力会比较大,因为本身开启Http连接就是一件比较耗资源的事情

  • 轮询的时间间隔不好控制。如果要求的实时性比较高,显然使用短轮询会有明显的短板,如果设置interval的间隔过长,会导致消息延迟,而如果太短,会对服务器产生压力

代码实现

var ShortPollingNotification = {
  datasInterval: null,
  subscribe: function() {
    this.datasInterval = setInterval(function() {
      Request.getDatas().then(function(res) {
        window.ChatroomDOM.renderData(res);
      });
    }, TIMEOUT);
    return this.unsubscribe;
  },
  unsubscribe: function() {
    this.datasInterval && clearInterval(this.datasInterval);
  }
}
shortPolling

下面是对应的请求,注意左下角的请求数量一直在变化

shortNetwork

在上图中,每隔1s就会发送一个请求,看起来效果还不错,但是如果将timeout的值设置成5s,效果将大打折扣,如图:

shortPolling5s

长轮询(Long Polling)

长轮询的基本原理:客户端发送一个请求,服务器会hold住这个请求,直到监听的内容有改变,才会返回数据,断开连接,客户端继续发送请求,重复以上步骤。或者在一定的时间内,请求还得不到返回,就会因为超时自动断开连接。

longPolling

长轮询是基于轮询上的改进版本,主要是减少了客户端发起Http连接的开销,改成了在服务器端主动地去判断所关心的内容是否变化,所以其实轮询的本质并没有多大变化,变化的点在于:

  • 对于内容变化的轮询由客户端改成了服务器端(客户端会在连接中断之后,会再次发送请求,对比短轮询来说,大大减少了发起连接的次数)

  • 客户端只会在数据改变时去作相应的改变,对比短轮询来说,并不是全盘接收

代码实现

// 客户端
var LongPollingNotification = {
    // ....
    subscribe: function() {
      var that = this;

      // 设置超时时间
      Request.getV2Datas(this.getKey(),{ timeout: 10000 }).then(function(res) {
        var data = res.data;
        window.ChatroomDOM.renderData(res);
        // 成功获取数据后会再次发送请求
        that.subscribe();
      }).catch(function (error) {
        // timeout 之后也会再次发送请求
        that.subscribe();
      });
      return this.unsubscribe;
    }

    // ....
}

笔者采用的是express,默认不支持hold住请求,因此用了一个express-longpoll的库来实现。

下面是一个原生不用库的实现(这里只是介绍原理),整体的思路是:如果服务器端支持hold住请求的话,那么在一定的时间内会自轮询,然后期间通过比较key值,判断是否返回新数据

  • 客户端第一次会带一个空的key值,这次会立即返回,获取新内容,服务器端将计算出的contentKey返回给客户端

  • 然后客户端发送第二次请求,带上第一次返回的contentKey作为key值,然后进行下一轮的比较

  • 如果两次的key值相同,就会hold请求,进行内部轮询,如果期间有新内容或者客户端timeout,就会断开连接

  • 重复以上步骤

// 服务器端

router.get('/v2/datas', function(req, res) {
  const key = _.get(req.query, 'key', '');
  let contentKey = chatRoom.getContentKey();

  while (key === contentKey) {
    sleep.sleep(5);
    contentKey = chatRoom.getContentKey();
  }

  const connectors = chatRoom.getConnectors();
  const messages = chatRoom.getMessages();
  res.json({
    code: 200,
    data: { connectors: connectors, messages: messages, key: contentKey },
  });
});

以下是用 express-longpoll 的实现片段

// mini-chatroom/public/javascripts/server/longPolling.js

function pushDataToClient(key, longpoll) {
  var contentKey = chatRoom.getContentKey();

  if (key !== contentKey) {
    var connectors = chatRoom.getConnectors();
    var messages = chatRoom.getMessages();

    longpoll.publish(
      '/v2/datas',
      {
        code: 200,
        data: {connectors: connectors, messages: messages, key: contentKey},
      }
    );
  }
}

longpoll.create("/v2/datas", function(req, res, next) {
  key = _.get(req.query, 'key', '');
  pushDataToClient(key, longpoll);
  next();
});

intervalId = setInterval(function() {
  pushDataToClient(key, longpoll);
}, LONG_POLLING_TIMEOUT);

为了方便演示,我将客户端发起请求的timeout改成了4s,注意观察下面的截图:

longPollingNetwork

可以看到,断开连接的两种方式,要么是超时,要么是请求有数据返回。

基于iframe的长轮询模式

这种模式的具体的原理为:

  • 在页面中嵌入一个iframe,地址指向轮询的服务器地址,然后在父页面中放置一个执行函数,比如execute(data)

  • 当服务器有内容改变时,会向iframe发送一个脚本<script>parent.execute(JSON.stringify(data))</script>

  • 通过发送的脚本,主动执行父页面中的方法,达到推送的效果

具体可以参看这里

Websocket

The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.

The protocol consists of an opening handshake followed by basic message framing, layered over TCP.

The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections (e.g., using XMLHttpRequest or iframe and long polling).

The WebSocket Protocol attempts to address the goals of existing bidirectional HTTP technologies in the context of the existing HTTP infrastructure; as such, it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries, even if this implies some complexity specific to the current environment.

特征

  • websocket是双向通信的,设计的目的主要是为了减少传统轮询时http连接数量的开销

  • 建立在TCP协议之上,握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器

  • 与HTTP兼容性良好,同样可以使用80和443端口

  • 没有同源限制,客户端可以与任意服务器通信

  • 可以发送文本,也可以发送二进制数据。

  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL

websocket

关于Websocket API方面的知识,这里不再作讲解,可以自己查阅Websocket API MDN

兼容性

websocket兼容性良好,基本支持所有现代浏览器

websocket1

代码实现

笔者这里采用的是socket.io,是基于websocket的封装,提供了客户端以及服务器端的支持

// 客户端
var WebsocketNotification = {
  // ...
  subscribe: function(args) {
    var connector = args[1];
    this.socket = io();

    this.socket.emit('register', connector);

    this.socket.on('register done', function() {
      window.ChatroomDOM.renderAfterRegister();
    });

    this.socket.on('data', function(res) {
      window.ChatroomDOM.renderData(res);
    });

    this.socket.on('disconnect', function() {
      window.ChatroomDOM.renderAfterLogout();
    });
  }
  // ...
}

// 服务器端
var io = socketIo(httpServer);

io.on('connection', (socket) => {
  socket.on('register', function(connector) {
    chatRoom.onConnect(connector);

    io.emit('register done');

    var data = chatRoom.getDatas();
    io.emit('data', { data });
  });

  socket.on('chat', function(message) {
    chatRoom.receive(message);

    var data = chatRoom.getDatas();
    io.emit('data', { data });
  });
});

响应格式如下:

websocket-request-response

Server-Sent Events(SSE)

传统意义上服务器端不会主动推送给客户端消息,一般都是客户端主动去请求服务器端获取最新的数据。SSE就是一种可以主动从服务端推送消息的技术。

SSE的本质其实就是一个HTTP的长连接,只不过它给客户端发送的不是一次性的数据包,而是一个stream流,格式为text/event-stream,所以客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。

  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。

  • SSE 默认支持断线重连,WebSocket 需要自己实现。

  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。

  • SSE 支持自定义发送的消息类型。

基本的使用方法,参看SSE API

sse

兼容性

目前除了IE以及低版本的浏览器不支持,基本支持绝大多数的现代浏览器。

[](https://caniuse.com/?search=Server-Sent Events)

代码实现

// 客户端
var SSENotification = {
  source: null,
  subscribe: function() {
    if ('EventSource' in window) {
      this.source = new EventSource('/sse');

      this.source.addEventListener('message', function(res) {
        const d = res.data;
        window.ChatroomDOM.renderData(JSON.parse(d));
      });
    }
    return this.unsubscribe;
  },
  unsubscribe: function () {
    this.source && this.source.close();
  }
}

// 服务器端
router.get('/sse', function(req, res) {
  const connectors = chatRoom.getConnectors();
  const messages = chatRoom.getMessages();
  const response = { code: 200, data: { connectors: connectors, messages: messages } };

  res.writeHead(200, {
    "Content-Type":"text/event-stream",
    "Cache-Control":"no-cache",
    "Connection":"keep-alive",
    "Access-Control-Allow-Origin": '*',
  });

  res.write("retry: 10000\n");
  res.write("data: " + JSON.stringify(response) + "\n\n");

  var unsubscribe = Event.subscribe(function() {
    const connectors = chatRoom.getConnectors();
    const messages = chatRoom.getMessages();
    const response = { code: 200, data: { connectors: connectors, messages: messages } };
    res.write("data: " + JSON.stringify(response) + "\n\n");
  });

  req.connection.addListener("close", function () {
    unsubscribe();
  }, false);
});

下面是控制台的情况,注意观察响应类型

sse-type

详情中注意查看请求类型,以及EventStream消息类型

sse3

总结

  • 短轮询、长轮询实现成本相对比较简单,适用于一些实时性要求不高的消息推送,在实时性要求高的场景下,会存在延迟以及会给服务器带来更大的压力

  • websocket目前而言实现成本相对较低,适合于双工通信,对于多人在线,要求实时性较高的项目比较实用

  • SSE只能是服务器端推送消息,因此对于不需要双向通信的项目比较适用

参考连接

  • The WebSocket Protocol

  • Websocket API MDN

  • Server-sent events MDN

  • WebSocket 教程

  • Server-Sent Events 教程

  • webSocket(二) 短轮询、长轮询、Websocket、sse

我只是一只小菜鸟,但我并没有停下学习的脚步^_^ 另外,觉得这篇文章不错,可以随手点个赞么?如需转载,请标明出处哟,求星星????????



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,艿一口,点个赞!????

h5匿名聊天PHP+TXT的轻型在线聊天室源码(无需数据库) h5自适应简易聊天室简约轻型聊天室源码,自适应电脑/手机,采用php语言+txt数据库,无后台,搭建即可使用,简单方便,理论上可以对接任何php程序~ 数据使用txt存放,默认显示近50条聊天记录(也可清空聊天记录) 聊天室各种设备自适应大小 采用jquery+ajax轮询方式,适合小型聊天环境,用户名随机生成 面板可更换肤色(黑色和白色),支持自定义昵称(昵称用户名需在源码里面修改) 源码技术本地亲测,无加密可以二次开发,内附详细安装说明~功能就直接看截图吧~ 立即下载

相关推荐

原理图转PCB

本文讲述了原理图的编译、原理图转PCB的步骤和易错点。

m0_58083051的博客 3293

撸一个简易聊天室,不信你学不会实时消息推送(附源码)

分不清轮询、长轮询?不知道什么时候该用websocket还是SSE,看这篇就够了。所谓的“实时推送”,从表面意思上来看是,客户端订阅的内容在发生改变时,服务器能够实时地通知客户端,进而客...

Java笔记虾 771

读取rosbag中的IMU信息并转为tum格式的csv和txt

rostopic -b xx.bag -p /xx > xxx.txt #for euroc structvio import os, sys,re,csv name="street_07" # basedir="/media/yinjie/2498AF1F98AEEF0C/adataset/15results/imu_bagtxt/"+name+'.txt' # txtdir="/media/yinjie/2498AF1F98AEEF0C//adataset/15results/imu_o

qq_42928559的博客 1921

支持多种推送方式的消息推送设计方案

通过邮件、短信等方式向用户发送通知是一项很常见的业务场景。如何设计一套好用、简洁消息推送架构?这是一个问题。保存推送消息本身一套消息文本支持多种类型消息推送消息推送记录占用空间小支持消息重试推送机制。

Mrqiang9001 1537

flowable工作流看这一篇就够了(基础篇)

接下来我们构建一个非常简单的请假流程,Flowable引擎需要流程定义为BPMN 2.0格式,这是一个业界广泛接受的XML标准。在Flowable术语中,我们将其称为一个流程定义(process definition)。一个流程定义可以启动多个流程实例(process instance)。流程定义可以看做是重复执行流程的蓝图。在这个例子中,流程定义定义了请假的各个步骤,而一个流程实例对应某个雇员提出的一个请假申请。

qq_38196449的博客 2万+

2.2 保姆级教程:手把手带你构建第一个 LangGraph 应用

本文是一份保姆级教程,手把手教你构建第一个LangGraph应用——"智能工具助手"Agent。教程分为8个步骤:从环境准备到工具定义(使用Tavily搜索API和自定义计算器),再到定义图状态、创建决策节点和执行节点,最后连接工作流并编译运行。通过这个项目,你将学会如何让Agent智能选择工具(搜索或计算),掌握LangGraph开发的基本流程,为构建更复杂系统打下基础。关键点包括状态管理、节点设计和条件路由的实现。

yonggeit的博客 3117

如果让你实现实时消息推送你会用什么技术?轮询、websocket还是sse

大厂技术高级前端Node进阶点击上方程序员成长指北,关注公众号 回复1,加入高级Node交流群前言在日常的开发中,我们经常能碰见服务端需要主动推送给客户端数据的业务场景,比如_数据大屏的实时数据_,比如_消息中心的未读消息_,比如_聊天功能_等等。本文主要介绍SSE的使用场景和如何使用SSE。image.png服务端向客户端推送数据的实现方案有哪几种?我们常规实现这些需求的方案有以下三种...

程序员成长指北 455

ABAP Channels 之 WebSocket 通信实践:用 ABAP Push Channel 打造实时应用

摘要:本文介绍了ABAP Push Channel(APC)技术,它通过WebSocket协议实现ABAP系统与外部客户端的实时双向通信。文章对比了传统HTTP轮询与WebSocket的差异,解释了APC的事件模型及其接口演进(从IF_APC_WS_EXTENSION到IF_APC_WSP_EXTENSION)。通过仓库拣货等实际案例,展示了APC在企业级应用中的价值,如实时监控、协作场景等。文中还详细阐述了ABAP引擎中WebSocket的交互模型,说明其资源优化设计原理,并提供了基础代码示例。该技术解决

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 298

聊聊 消息推送 架构设计

程序员的成长之路互联网/程序员/技术/资料共享关注阅读本文大概需要 8 分钟。来自:技术自由圈构建企业级统一基础推送服务,支持通过多渠道推送,能够统一集成的电子邮件、短信、聊天、钉钉、企业微信和其他公共社交应用:聊天 - 微信Wechat/QQ站内推送通知(移动设备和Web浏览器)站外推送通知(移动设备,APP没有开启)短信(如登录密码、营销活动)电子邮件钉钉企业微信企业级统一基础推送服务,是一...

良月柒 469

Unity 面试篇|(四)Unity性能优化篇 【全面总结 | 持续更新】

Unity中,CPU准备好需要绘制的元素,对底层图形程序接口进行调用的过程,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:动态合批静态合批降低shader的等级特性场景优化策略——遮挡技术。rectMask2D替代Mask。

游戏开发小Y的博客 13万+

PSM倾向性评分分析介绍、spss进行倾向性评分分析示例

PSM倾向性评分分析介绍、spss进行倾向性评分分析示例

statistics+insight+vista+power 1483

你还在使用websocket实现实时消息推送吗?

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享点击关注#互联网架构师公众号,领取架构师全套资料 都在这里0、2T架构师学习资料干货分上一篇:2T架构师学习资料干货分享大家好,我是互联网架构师!前言在日常的开发中,我们经常能碰见服务端需要主动推送给客户端数据的业务场景,比如数据大屏的实时数据,比如消息中心的未读消息,比如聊天功能等等。本文主要介绍SSE的使用场景和如何使用SS...

emprere的博客 313

别再盲目学AI了,先搞懂这5种最赚钱的变现模式

2026年,“手搓”成为一个热词——用AI工具独立完成从想法到产品的全过程。央视《焦点访谈》专门报道了这一现象。云南小伙刘梓瑜用一台电脑、几款AI工具、3000元成本和10天时间,创作了AI短片,48小时播放量破千万。华为在HDC 2026上发布的,是一个“懂鸿蒙的编程智能体”,面向独立开发者一站式开箱即用。它内置UI意图验证Agent,能在模拟器上自动运行应用、判断UI和交互逻辑是否正确。故障修复成功率超过80%,分钟级自主修复。

ZDQ58818的博客 311

连夜撸一个简易聊天室,不信你学不会实时消息推送!【附源码】

点击上方果汁简历,选择“置顶公众号”优质文章,第一时间送达分不清轮询、长轮询?不知道什么时候该用websocket还是SSE,看这篇就够了。所谓的“实时推送”,从表面意思上来看是,客...

weixin_37097680的博客 206

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

30 张「酷到爆炸」的极客风格壁纸

PS:有朋友反映直接保持的图片分辨率低,我把源文件分享出来,需要的自取。 链接: https://pan.baidu.com/s/1K5t9fpE3U6EsSSFuldy6fg 密码: ...

公众号:Java后端 2816

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

一个帮你轻松搞定第三方登陆的 Java 开源组件

点击上方Java后端,选择设为星标优质文章,及时送达今天给大家推荐的这个开源项目,可以说非常非常的棒,有了它,可以让你大大提高第三方授权登录开发效率。没错,这个开源项目就是:JustA...

公众号:Java后端 418

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

一个超火的网站“Omegle”

一个超火的网站“Omegle”     在美国有一个小网站突然暴红。这个网站叫「Omegle」(http://omegle.com/),页面非常简单,实际就两个页面。它的首页就有一个很大的按钮,上面写着“开始聊天”按下去以后,它立刻将你和目前在线上的某位陌生人“速配”,然后你们进入一个聊天室,开始一对一的文字对话。而当你不想聊了,你只需要按下“结束”按钮,然后又出现一个按钮“再找一位陌生人

王英雄的博客 7735
上一篇: 你的微服务网关还只在用负载均衡吗?
下一篇: 后端不哭!最新优化性能经验分享来啦
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值