# nginx-push-stream-module 模块 #### 1. 介绍 > A pure stream http push technology for your Nginx setup. > > Comet made easy and really scalable. > > Supports EventSource, WebSocket, Long Polling, and Forever Iframe. [nginx-push-stream-module](https://github.com/wandenberg/nginx-push-stream-module)是nginx的一个模块,用它可以轻易实现websocket服务器。 以前我们实现websocket的服务器,不外乎两种方式,第一种是嵌入到web进程中,成为web进程的一部分,只是以路由的方式提供服务,还有一种是单独的进程,比如用puma来启动包含actioncable的rails项目。 这两种方式多多少少跟web进程都有些关系,嵌入型的就不用多说,就算是单独的进程这种方式,也是用了另一种服务器去启动。 现在我们来考虑另外一种方式,就是用完全独立于web进程的服务器,比如,之前我们的web是用ruby写的,可能会用puma或unicorn来启动,现在我们可以用c++启动websocket服务器,而web进程是通过http请求等方式来连接websocket服务器。 当然,这一篇文章,我们是用nginx来启动一个websocket的服务器,nginx本身没有这样的功能,需要其中的一个模块,就是本章介绍的`nginx-push-stream-module`。 现在我们先来跑一下流程,再来讲述一下它的原理以及为什么能够这样做。 #### 2. 使用 首先得先安装一下这个模块。 ##### 2.1 安装 安装很简单,跟之前的模块安装一模一样的步骤,具体可以查看这篇文章[nginx之编译第三方模块(六)](http://www.rails365.net/articles/nginx-zhi-bian-yi-di-san-fang-mo-kuai-liu)。 现在来列出一下大概的流程。 ``` $ git clone https://github.com/wandenberg/nginx-push-stream-module.git # 进入到nginx源码目录,--add-module后面接nginx-push-stream-module的源码目录 $ ./configure --add-module=../nginx-push-stream-module # 编译 $ make # 安装 $ sudo make install # 结束老的nginx进程 $ sudo nginx -s quit # 开启新的nginx进程 $ sudo nginx ``` 接着我们来使用这个模块。 ##### 2.2 配置 在配置文件`nginx.conf`中添加下面这样的内容: ``` http { push_stream_shared_memory_size 32M; server { location /channels-stats { # activate channels statistics mode for this location push_stream_channels_statistics; # query string based channel id push_stream_channels_path $arg_id; } location /pub { # activate publisher (admin) mode for this location push_stream_publisher admin; # query string based channel id push_stream_channels_path $arg_id; } location ~ /ws/(.*) { # activate websocket mode for this location push_stream_subscriber websocket; # positional channel path push_stream_channels_path $1; # message template push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\"}"; push_stream_websocket_allow_publish on; # ping frequency push_stream_ping_message_interval 10s; } } } ``` ##### 2.3 原理 其中`push_stream_shared_memory_size`是添加在`http`下,其他的都在`server`下。 `push_stream_shared_memory_size`我就不详说了,应该设置的是一个内存的容量,我对此细节并不了解,按照默认的就好了。 我们来讲述一下`server`之下的三个`location`。 - /channels-stats - /pub - ~ /ws/(.\*) 第一个是关于websocket统计相关的东西,这个稍后再讲。 另外两个是关于发布订阅的。 其中客户端连接到服务器使用的是`~ /ws/(.*)`这个`location`,而服务器端向客户端推送消息是使用`/pub`这个`location`。 至于客户端与服务器端是如何交互的,我们可以回顾一下。 客户端,比如浏览器,发送`new WebSocket`给websocket服务器,表示要建立websocket请求,这个过程表示的是订阅,一直在等待服务器发送消息过来,一旦有消息过来,就会更改一些状态,比如DOM更新等。这个过程,不止只有一个客户端连接上服务器,可能有好多客户端同时连接。假如现在有了业务变化,服务器需要向所有的客户端推送消息,这个过程就是发布,广播消息。通过什么广播消息呢,这个机制可以自己实现,也可以用redis的pub/sub功能,比如,一旦客户端连接上服务器,就会订阅redis的一个channel,而发布的时候,就是往这个channel里推送消息,这样,所有的客户端都能接收到消息。 `nginx-push-stream-module`不需要redis的pub/sub,它是自己实现的。 ##### 2.4 测试 现在我们开始来测试一下这个应用。 还记得之前提到的`/channels-stats`这个`location`吗?它是统计信息用的。 我们先来看下它的结果。 ``` $ curl -s -v 'http://localhost/channels-stats' ``` 输出的内容主要是下面的json信息: ``` {"hostname": "macintoshdemacbook-air.local", "time": "2016-05-07T12:02:34", "channels": 0, "wildcard_channels": 0, "published_messages": 0, "stored_messages": 0, "messages_in_trash": 0, "channels_in_trash": 0, "subscribers": 0, "uptime": 19755, "by_worker": [ {"pid": "21117", "subscribers": 0, "uptime": 19755} ]} ``` 上面的信息包括主机名,时间,通道的个数,消息的个数等,我们先不管。 现在我们用浏览器建立一个连接到websocket服务器,也就是要请求`~ /ws/(.*)`这个`location`。 ``` ws = new WebSocket("ws://localhost/ws/ch1"); ws.onmessage = function(evt){console.log(evt.data);}; ``` 很简单,使用`new WebSocket`建立一个websocket请求,地址为`ws://localhost/ws/ch1`。 `ch1`是通道的名称,`push_stream_channels_path $1;`这一行配置指的就是它。 `onmessage`表示接收服务器端的消息,一旦有消息过来,就用`console.log`输出来。 我们一直在关注着浏览器的输出。 现在我们给客户端推送一条消息,自然是使用`/pub`这个`location`。 ``` $ curl http://localhost/pub\?id\=ch1 -d "Some Text" {"channel": "ch1", "published_messages": 1, "stored_messages": 0, "subscribers": 1} ``` 使用的是curl这个命令,`ch1`表示的是通道名,它可以以参数的形式来指定,这样就会灵活很多,不同类型的连接可以用不同的通道名。 果然浏览器输出了信息了: ``` {"id":1,"channel":"ch1","text":"Some Text"} ``` `id`是消息的编号,默认从1开始,这个数字会自增,`channel`表示通道名,`text`是服务器端发送的信息。 输出的内容,跟`push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\"}";`这里定义的模版有关。 果然是推送了什么内容就是输出了什么内容。 现在我们来看看统计内容的输出: ``` $ curl -s -v 'http://localhost/channels-stats' {"hostname": "macintoshdemacbook-air.local", "time": "2016-05-07T12:24:13", "channels": 1, "wildcard_channels": 0, "published_messages": 1, "stored_messages": 0, "messages_in_trash": 0, "channels_in_trash": 0, "subscribers": 1, "uptime": 21054, "by_worker": [ {"pid": "21117", "subscribers": 1, "uptime": 21054} ]} ``` 可以看到`"channels": 1`表示有一个通道,之前是没有的,`"published_messages": 1`表示发布的消息也多了一条了。 我们可以发起多个`new WebSocket`或开多个浏览器进行测试,那样可以观看到更多的效果。 之前通过curl工具,向`/pub`这个`location`发送了http请求,这个就间接向客户端发送数据,只是表现方式跟之前的不太一样。 ##### 2.5 ruby 在实际的编程中,我们可以会用ruby应用结合nginx的`nginx-push-stream-module`这个模块来做应用,总不至用curl这个工具,这个工具主要用于测试,我们现在试一下用ruby来代替curl。 开启一个ruby的命令终端`irb`。 ``` require 'net/http' uri = URI("http://localhost/pub\?id\=ch1") http = Net::HTTP.new(uri.host, uri.port) req = Net::HTTP::Post.new(uri.to_s) req.body = 'Some Text' http.request(req) ``` 你会发现,效果是一样的。 `nginx-push-stream-module`是个不错的工具,如果灵活运用它,肯定有意想不到的好处。 完结。