将一个STDIN通过websocket流式传输到多个客户端

时间:2017-09-14 09:44:54

标签: linux unix websocket

目前我在Linux中有“streamgenerator”,它将数据输出到stdout / namedpipe等。我发现websocketd在线有点接近我需要的但问题是它为每个连接的客户端产生了新的进程。
我正在寻找的是将相同的数据(单个进程生成数据到stdout)流式传输到多个客户端。

$ websocketd --port=8080 ./streamgenerator  

将为websocket的每个新连接创建新的streamgenerator进程。我想要的是一个streamgenerator的实例和它为所有客户端复制的输出。

有一些简单的方法吗?我现在唯一想到的就是编写C程序,它将STDIN带入大小为X的缓冲区,并且每个客户端都有指向该缓冲区的指针(指向客户端能够读取的位置直到该点)....如果客户端连接他将开始只获取新数据...如果客户端太慢...他的“READ”指针将从缓冲区中掉出,他的连接将被丢弃,因为他无法跟上。

我的问题是,没有开发此工具有什么办法吗?首先我认为管道到命名管道,然后使websocketd从它读取...但这当然不起作用,因为websocket的第一个客户端将读取数据并扔掉它们...

2 个答案:

答案 0 :(得分:1)

根据websocketd source code,它似乎使用基于流程的服务器(每个连接的新进程)....我不是GO程序员,所以我不确定我& #39;正确阅读,但README似乎表明了相同的概念。

...因此

  

问题是它为每个连接的客户端生成新进程

这是不可避免的。该设计意味着新的连接本质上是分叉的,以及它们的STDIN和STDOUT。流式传输到原始STDIN的数据无法从新连接中访问...

...所以使用streamgenerator websocketd script.rb并不是一个选项。

  

现在我想到的只有写C程序......

我认为这可能是避免多流程设计的唯一方法。

你不必为此而使用C.你可以很容易地使用Ruby或node.js(它们可能更容易创作,而你为了方便性而付出代价)。

  

我的问题是,没有开发此工具有什么办法吗?

我不这么认为。

然而,facil.io可以使编写C web套接字工具变得相当简单,该工具使用它的原生Pub / Sub API广播数据(允许您在将来使用Redis轻松扩展) ......但作为作者,我有偏见。

修改

这是一个简短的Ruby脚本,它将数据从管道广播到任何连接的Web套接字连接(由行分隔的数据)。

档案#!/usr/bin/env ruby require 'iodine' class Example def self.call(env) if env['upgrade.websocket?'.freeze] && env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze env['upgrade.websocket'.freeze] = Example.new return [0,{}, []] # It's possible to set cookies for the response. end [404, {"Content-Length" => "12"}, ["Bad Request."] ] end def on_open subscribe channel: :stream, force: :text end def on_message data close # are we expecting any messages? end def on_close # nothing to do end def on_shutdown # server is going away, notify client. end end Iodine::Rack.app = Example # remove these two lines for automatice, core related, detection Iodine.processes = 1; Iodine.threads = 1; # initialize the Redis engine for each iodine process. require 'uri' if ENV["REDIS_URL"] uri = URI(ENV["REDIS_URL"]) Iodine.default_pubsub = Iodine::PubSub::RedisEngine.new(uri.host, uri.port, 0, uri.password) else puts "* No Redis, it's okay, pub/sub will still run on the whole process cluster." end # Create the loop that reads from ARGF (the pipe) # defer threading because we might fork the main server root_pid = Process.pid Iodine.run do puts "Starting to listen to pipe" if(root_pid == Process.pid) Thread.new do ARGF.each_line do |s| Iodine.publish channel: :stream, message: s puts "read:", s end end end end # start iodine Iodine.start

$ streamgenerator | ruby script.rb

您可以使用以下方式在终端上使用它:

Iodine.processes = 8

这只是一个肮脏的例子,但它显示了这是多么容易。

哦,它需要the iodine gem,这是fail.io的Ruby端口(也是我的)。

编辑2

我添加了一些代码,允许您将Redis与我提供的示例代码一起使用,并将发布限制为单个进程。

Redis引擎是facil.io的原生(它在C中,带有用于碘的Ruby桥),您可以使用它来发送命令以及Pub / Sub。

如果您使用Redis在多台计算机上进行扩展,我会考虑将脚本拆分为发布者脚本和服务器应用程序。

此外,如果您使用多台计算机,则只需要Redis。如果您使用 $ ./streamgenerator | pub --channel "redisstream" $ websocketd --port=8080 sub --channel "redisstream" 运行碘,则发布/订阅引擎仍然有效。

如果您需要,Iodine还有许多其他功能,例如静态文件服务等。

您还可以将整个事物打包到中间件中,并使用碘作为服务器(用于Websocket支持)使其成为现有Rails / Sintara / Rack项目的一部分。

...

至于:

websocketd

听起来这会缓解这个问题,虽然我认为import pandas df = pandas.read_csv("trial.csv") 仍然会为每个连接打开一个新进程,它使用的资源多于一个偶数的反应堆模式"例如服务器使用的那个,如nginx(和碘,乘客,美洲狮和其他一些人)。

答案 1 :(得分:0)

我不明白这个问题。不应该这样做:

streamgenerator | tee fifo1 | tee fifo2 | tee fifo3