websocket的出现使得从服务器向浏览器推送数据更加容易。但是低版本的浏览器不支持websocket,这时socket.io出现了。
使用socket.io的应用在支持websocket的浏览器运行的时候使用websocket,而在低版本的浏览器中则使用传统的方式与服务器交互(例如long-polling及其他的方式)。
long-polling的应用实现方式是这样的,客户端向服务器端发起请求,服务器端不会马上返回,而是保持这个连接直到服务器需要推送信息给客户端时,才返回给客户端数据。客户端接收到数据以后会再次发送一个请求,如此循环。
socket.io支持使用long-polling的客户端,这个特点使得我们的网站在低版本的浏览器也能运行,但是这会给socket.io服务器端程序带来一个问题。
比如我们需要用socket.io编写一个聊天程序,每当有一个新的用户时,建立一个新连接,服务器在内存中保存当前的所有连接。当需要给所有用户广播信息的时候,我们遍历所有的连接,给每个连接写数据。类似于:
//示意代码
var clients = []; //所有的用户
server.on(‘broadcast‘, function(){
for client in clients {
writeData(client, ‘hi‘);
}
});
现在我们来考虑一下writeData这个方法要怎么实现,因为要支持使用long-polling的客户端,long-polling是由多次请求组成的,所以在两次请求之间客户端和服务器端没有连接,假如这时需要推送数据到客户端,我们需要在服务器端把要推送的数据临时存下来,等待客户端发起请求的时候,再把数据发送到客户端。服务器端代码类似:
var long_polling_clients = [];
function writeData(client, data){
long_polling_clients.push([client, data]);
}
server.on(‘request‘, function(){
//从long_polling_clients取得client
//发送数据到客户端
});
当服务器端只有一台服务器的时候,程序没有问题,客户端发送的每次请求都连接到同一台服务器。
但是当服务器端有多台服务器时,程序就有可能出错。
假如C1客户端先连接到A服务器,并且服务器需要推送消息到客户端,这个服务器的会把要发送的消息和对应的客户端保存起来,等待客户端请求。
//A服务器
var long_polling_clients = [[客户端1, 消息]];
下一次客户端请求连接到了B服务器,B服务器并没有向C1客户端推送消息,因此B服务器的内存中没有记录C1客户端,所以不会向C1客户端写数据,这样程序就出错了。
//B服务器
var long_polling_clients = []
为了解决这个问题,最简单的办法是每次客户端连接服务器的时候都连同一台服务器,这可以使用nginx这样的工具来实现。
但是有时为了利用多核,我们会在服务器上运行多个socket.io实例,每个实例都有自己的内存空间。这时候怎么办呢?我们需要用到sticky-session这个库。
这个库调用cluster创建了多个socket.io实例,并且总是把相同ip的请求发送到同一个socket.io实例。
当然我们可以选择不把[客户端,消息]保存在内存中,而是用其他存储方式保存例如redis,这样多台服务器,多个socket.io实例都可以共享数据。
原文链接: http://suyuan.me/socket-ioyu-sticky-session/