使用 Socket.IO 可以多久发送一次数据?

Posted

技术标签:

【中文标题】使用 Socket.IO 可以多久发送一次数据?【英文标题】:How frequently can I send data using Socket.IO? 【发布时间】:2018-04-26 18:32:15 【问题描述】:

我正在创建一个 Web 应用程序,该应用程序需要非常频繁地从服务器向客户端发送少量数据(每个套接字 3 个整数值),我想看看是否存在使用更新客户端的最大频率Socket.IO.

我希望实现至少 50 个套接字连接每秒发送 20 个更新。理想的数量是每秒发送 50 次更新的 200 个套接字连接。

问题:使用 Socket.IO 发送新数据的频率是否有限制?

注意:我认识到这也成为服务器-客户端连接的速度问题,因此任何有关我需要的连接速度的信息都非常感谢。我计算出,如果发送的每个数据包大约为 500 字节,那么我将能够在 1 MB/s 连接上每秒向 100 个连接发送 20 个更新。

【问题讨论】:

您的应用程序是否真的需要每秒接收/处理所有 50 次更新(在某些 FSA/状态完整流程修改中是否有任何类型的日志或依赖链,需要获取所有排队的中间值——或者如果使用最近交付的最后一个可用值,控制回路逻辑是否保持稳定的控制功能? @user3666197 本质上,我们正在运行一个交通模拟,其中后端计算大部分计算并将每辆车的位置数据(例如纬度、经度和方向)发送到客户。客户端将处理信息并更新视图。我正在努力确保汽车不断更新。我以前从未使用过网络套接字,但我希望为每辆车打开一个单独的连接。我知道只打开一个连接并一次发送所有数据会更有效,但首先我想看看这种方式是否可行。 目标是减少更新量 [1/s] * 传播 [B] 一旦“地理本地化”用户 GUI 对接收的兴趣接近于零(强制执行) 更新所有汽车在城市另一端的 X 更新位置。虽然地理定位逻辑很难编码,但“保持最新更新”不是并且可以帮助(分布式)用户 GUI 减少流量和 GUI 重新绘制,因为“已过时”[ LAT,LON ]-位置(它知道已经过时,已被更新的位置取代)根本不被处理。任何其他方法都是有风险的(延迟+抖动)+废话。 如果一个工具不能帮助做到这一点,整个故事就是生成大量已经过时的数据,这些数据在技术上被强制交付,已经获得了所有成本(消耗的时间、资源, 网络传输费用)但是 向客户 GUI 交付了零价值,因为即使在发送这些数据驱动的更新时,系统也知道,实际上有更新的已呈现 “旧”数据的位置(位置更新)变得过时且为零(实际上是 ,如果考虑成本)值。 智能传输避免了这种情况 【参考方案1】:

这是一个非常依赖系统、网络和代码的问题。

这是我之前用于类似的普通 socket.io 测试的一个小型测试工具,我已经插入了一些位来满足您的问题。

服务器

const io = require('socket.io')(8082)
const connections = []

io.on('connection', function(socket)

  connections.push(socket);
  const slog = (msg, ...args) => console.log('%s %s '+msg, Date.now(), socket.id, ...args)
  slog('Client connected. Total: %s', connections.length)

  socket.on('disconnect', function(data)
    connections.splice(connections.indexOf(socket), 1);
    slog('Client disconnected. Total: %s', connections.length)
  )

  socket.on('single', function(data)
    socket.emit('single',[ 0, now, now, now ])
  )

  socket.on('start', function(data = )
    slog('Start stream', data)
    sendBatch(1, data.count, data.delay)
  )

  socket.on('start dump', function(data = )
    slog('Start dump', data)
    sendBatch(1, data.count)
  )

  function sendBatch(i, max, delay)
    if ( i > max ) return slog('Done batch %s %s', max, delay)
    socket.emit('batch',[ i, now, now, now ])
    if (delay) 
      setTimeout(()=> sendBatch(i++, max, delay), delay)
     else 
      setImmediate(()=> sendBatch(i++, max))
    
  

)

客户

const io = require('socket.io-client')
const socket = io('http://localhost:8082', transports: ['websocket'])

socket.on('connect_error', err => console.error('Socket connect error:', err))
socket.on('connect_timeout', err => console.error('Socket connect timeout:', err))
socket.on('reconnect', err => console.error('Socket reconnect:', err))
socket.on('reconnect_attempt', err => console.error('Socket reconnect attempt:', err))
socket.on('reconnecting', err => console.error('Socket reconnecting', err))
socket.on('reconnect_error', err => console.error('Socket reconnect error:', err))
socket.on('reconnect_failed', err => console.error('Socket reconnect failed:', err))

function batch(n)
  socket.on('batch', function(data)
    if ( data[0] >= n ) 
      let end = Date.now()
      let persec = n / (( end - start ) / 1000)
      console.log('Took %s ms for %s at %s/s', end - start, n, persec.toFixed(1))
      return socket.close()
    
  )


function startDump(count = 500000)
  socket.emit('start dump',  count: count )
  console.log('Start dump', count)
  batch(count)

function startStream(count = 50, delay = 1000)
  socket.emit('start',  count: count, delay: delay )
  console.log('Start stream', count, delay)
  batch(count)


function pingIt(i, max = 50)
  socket.on('single', function(data)
    console.log('Got a single with:', data)
    if (i >= max) 
      let end = Date.now()
      let persec = i / (end - start) * 1000
      console.log('Took %s ms %s/s', end - start, persec.toFixed(2))
      return socket.close()
    
    socket.emit('single', i+=1)
  )
  socket.emit('single', i)


let start = Date.now()

//console.log('args command: %s  count: %s  delay: %s',process.argv[2], process.argv[3], process.argv[4])
switch(process.argv[2])
  case 'ping':   pingIt(0, process.argv[3]); break
  case 'stream': startStream(process.argv[3], process.argv[4]); break
  case 'dump':   startDump(process.argv[3]); break
  default:       console.log('ping stream dump'); socket.close()

测试请求/响应往返

 node socketio-client.js ping 4

为了测试吞吐量,尽可能快地转储消息。

 node socketio-client.js dump 100000

测试 1000 条消息流,每条消息之间有 18 毫秒的延迟,大约每秒 50 条消息。

 node socketio-client.js stream 1000 18

在我的开发机器上,我每秒可以将大约 40000 条消息转储到单个 localhost 客户端,其中 4 个整数作为 2 GHz CPU 上的有效负载(计数器 + 3 个时间戳)。服务器和客户端node 进程各自使用 95-100% 的 CPU 内核。所以纯吞吐量看起来不错。

我可以每秒向 100 个本地客户端发送 100 条消息,服务器进程的 CPU 使用率为 55%。

我的开发机器上的单个 node 进程每秒无法向 100 个客户端发送超过 130-140 条消息。

新的高频 Intel Skylake CPU 服务器可能会在本地消除这些数字。添加一个可能很脆弱的网络连接,它会立即恢复。除了本地网络延迟之外的任何事情都可能会影响您认为您将通过如此高的消息速率获得的东西。任何较慢的延迟抖动都会对客户端消息的“帧速率”造成严重破坏。可能需要时间戳消息并在客户端上跟踪它们。

如果您确实遇到了问题,还有像 ws 这样的较低级别的 websocket 库需要您进行更多的实现,但可以让您更好地控制套接字连接,并且您可能会从中获得更多的性能。

您拥有的连接越多,您与其余代码和套接字代码的争用就越多。您可能最终需要使用multiple nodes 来保持顺畅。 Cluster can split the app across multiple Node.js processes。您可能需要 Redis、ZeroMQ 或 Nanomsg 之类的东西来管理 IPC。 Node 9 中的 V8 支持 SharedArrayBuffer 和 Atomics,但在 Node 中还没有多少可以与 worker 一起使用。

【讨论】:

【参考方案2】:

我可以使用 Socket.IO 多久发送一次数据?

使用 Socket.IO 发送新数据的频率是否有限制?

没有编码限制。它仅取决于您在两端处理消息的能力以及传递它们的带宽。如果你真的想知道你自己对所运行的硬件、网络和操作系统的限制,你必须设计一个测试来发送具有代表性大小的快速发射数据包,看看你可以在一秒钟内发送多少个它们。都到了目的地,两端都没有看到错误。

理想的数量是 200 个套接字连接,每秒发送 50 个更新。

您的服务器需要能够每秒发送 10,000 条消息,每个客户端需要能够每秒处理 50 条传入消息。理论上,只要有正确的硬件和正确的网络连接,这一切都是可能的。

但是,每秒 50 次更新听起来可能既不必要又低效。没有最终用户会每 20 毫秒感知一次变化,这就是每秒 50 次更新的结果。因此,将每个客户端的更新批处理为每秒 10 次更新会更有效率。

我计算出,如果要发送的每个数据包大约为 500 字节,那么我可以在 1 MB/s 的连接上每秒向 100 个连接发送 20 个更新。

这种类型的计算只适用于发送大块数据。对于大量小消息,由于 TCP 数据包开销和大量小消息的 webSocket/socket.io 开销开始成为总带宽消耗的可测量百分比,因此存在许多低效率,并且由于 TCP 是一种可靠的协议,因此有还有 ACK 来回流动以确认交付。如果数据包很小,您可能没有整体带宽问题,问题将更多地在于处理大量小数据包以及执行此操作的开销。

如果您可以将更新合并为每秒更少的更新,您将获得更好的可扩展性。

【讨论】:

以上是关于使用 Socket.IO 可以多久发送一次数据?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Socket.IO,我可以发送消息并将它们存储在数据库中吗? [关闭]

在 now.js/socket.io 聊天中增强安全性

在Socket.IO中将数据保存在套接字上

发送公共消息,包括发送方 Socket.io

如何使用 socket.io 发送图像文件(二进制数据)?

Socket.io 无法正常工作