作者 | jaychen
原文 | http://imweb.io/topic/584412459be501ba17b10a7b
消息实时推送,指的是将消息实时地推送到浏览器,用户不需要刷新浏览器就可以实时获取最新的消息,实时聊天室的技术原理也是如此。传统的Web站点为了实现推送技术,所用的技术都是轮询,这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求。
短轮询(Polling)
客户端需要定时往浏览器轮询发送请求,且只有当服务有数据更新后,客户端的下一次轮询请求才能拿到更新后的数据,在数据更新前的多次请求相当于无效。这对带宽资源造成了极大的浪费,若提高轮询定时器时间,又会有数据更新不及时的烦恼。
commet 为了解决短轮询的弊端,一种基于http长连接的"服务器推"方式被hack出来。其与短轮询的区别主要是,采用commet时,客户端与服务端保持一个长连接,当数据发生改变时,服务端主动将数据推送到客户端。Comet 又可以被细分为两种实现方式,一种是长轮询机制,一种是流技术。
长轮询跟短轮询不同的地方是,客户端往服务端发送请求后,服务端判断是否有数据更新,若没有,则将请求hold住,等待数据更新时,才返回响应。这样则避免了大量无效的http请求,但即使采用长轮询方式,接受数据更新的最小时间间隔还是为2*RTT(往返时间)。
流技术(http stream)基于iframe实现。通过HTML标签iframe src指向服务端,建立一个长连接。当有数据推送,则往客户端返回,无须再请求。但流技术有个缺点就是,在浏览器顶部会一直出现页面未加载完成的loading标示。
websocket
为了解决服务端如何更快地实时推送数据到客户端以及以上推送方式技术的不足,HTML5中定义了Websocket协议,它是一种在单个TCP连接上进行全双工通讯的协议。与http协议不同的请求/响应模式不同,Websocket在建立连接之前有一个Handshake(Opening Handshake)过程,建立连接之后,双方即可双向通信。当然,由于websocket是html5新特性,在部分浏览器(IE10以下)是不支持的。 我们来看下websocket的握手报文:
请求报文:
socket.io(http://socket.io) 是一个完全由JavaScript实现,基于Node.js、支持WebSocket的协议用于实时通信、跨平台的开源框架。Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询机制以及其它实时通信方式,并封装成了通用的接口,并能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。
首先,我们创建一个socket.io server对象,指定监听80端口。并且指定收到message消息,以及socket端口的监听方法。接着,当socket建立连接后,通过socket.emit方法,可以往客户端发送消息。
客户端的代码也非常简单,只要引入socket.io对应的客户端库(https://github.com/socketio/socket.io-client)。 在socket建立连接的回调中,使用socket.emit以及socket.on就可以分别做消息的发送以及监听了。
若只是单机部署应用,单纯使用socket.io的消息事件监听处理即可满足我们的需求。但随着业务的扩大,我们需要考虑多机集群部署,客户端可以连接到任一节点,并发送消息。如何做到多节点的同时推送,我们需要建立一套多节点之间的消息分发/订阅架构。这时我们引入redis的pub/sub功能。
redis redis是一个key-value存储系统,在该项目中主要起到一个消息分发中心(publish/subscribe)的作用。用户通过socket.io namespace 订阅房间号后,socket.io server则往redis订阅(subscribe)该房间号channel。当在该房间中的某一用户发送消息时,则通过redis的publish功能往redis该房间号channel publish消息。这样所有订阅该房间号channel的websocket连接则会收到消息回调,然后推送给客户端。
nginx 由于采用了集群架构,则需要nginx来做反向代理。需要注意的是,websocket的支持需要nginx1.3以上版本。并且我们需要通过配置ip_hash做粘性会话(ip_hash)处理,避免在低版本浏览器socket.io使用兼容方案轮询请求,请求到不同机器,造成session异常。
客户端通过socket.io namespace 指定对应roomid,请求到nginx。nginx根据ip_hash反向代理到对应机器的某一端口的socket.io server 进程。建立websocket连接,并往redis订阅对应到房间(roomid)channel。到这个时候,一个订阅了某一房间的websocket通道建立完成。 当用户发送消息时,socket.io server捕获到该房间到消息后,即往redis对应房间id的channel publish消息。这时所有订阅了该房间id channel的socket.io server就会收到订阅响应,接着找到对应房间id的webscoket通道,并将消息推送到客户端。
nginx配置(nginx版本须>1.3): 在http{}里配置定义upstream,并设置ip_hash。使同一个ip的请求能够落在同一个机器同一个进程中。 如果改节点挂了,则自动重连到另外一个节点,该方案对于后期扩容也非常方便。
在server中,配置location:
cluster.js 我们采用了多进程的设计,充分利用cpu多核优势。通过主进程统一管理维护子进程,每个进程监听一个端口。
fork_server.js
客户端:
gihub源码地址:https://github.com/493326889/node-multiple-rooms-chat