SSE简介
SSE
是Server-Sent Events
的缩写。通常情况下,是我们的浏览器向服务器发起请求后,服务器响应,然后关闭连接。为了能够保持通信,以便在服务器有事件发生时主动通知浏览器,后来人们又发明了很多技术,包括websocket
等。但是websocket
对于代码改动较大,所以又出现了SSE
,它的特点是基本不用改写原有的逻辑,只是增加一些小的改动就能实现服务器与客户端之间的长连接,达到服务器主动通知客户端的目的。
但是我在按照网上教程真正去在nginx
环境下实现SSE
时,颇费了一番周章,留在这里,以便有同学遇到类似问题时参考。
客户端
SSE
的主要原理是由客户端,也就是浏览器里的javascript
发起一个类似于ajax
的请求,但和ajax
不同的是,这是一个一直保持的长连接,一旦请求建立之后,客户端开始安静地等待服务端向它发回数据,这个连接可以保持很长很长时间。
所以客户端的代码很简单:
source = new EventSource('http://api.server.com/path/file.php?param=value');
source.onmessage = function (event) {
json = JSON.parse(event.data);
... 后面是你处理数据的部分 ...
};
不用担心断掉,即使断掉的话,客户端会在断掉之后的3秒之后自动再次重新向服务端发起连接请求,而且这个重新连接是浏览器自动帮助我们实现的,我们在编程时可以完全不去考虑它。
服务器端
php部分
header('Access-Control-Allow-Origin: http://www.server.com');
//发送SSE应答
header('X-Accel-Buffering: no');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$old_md5 = '';
//执行100次,每次睡眠3秒钟,总共300秒,也就是5分钟
for ($i=0; $i<100; $i++) {
//保持一个长连接,每隔3秒钟回答客户端一次
$o_data = 你的数据;
$str_return = json_encode($o_data);
$md5 = md5($str_return);
if ($md5 != $old_md5) {
//如果内容发生了变化,则推送,否则不必推送,以节省网络流量
echo 'data: ' . $str_return . "\n\n";
$old_md5 = $md5;
}
ob_flush();
flush();
//等待3秒钟,开始下一次查询
sleep(3);
}
逐行分解一下:
这三句话必不可少:
header('X-Accel-Buffering: no');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
其中的第一句话,是配置nginx
必需的。因为nginx
缺省会对所有来自php
的数据作缓存,它一定要等到所有数据全部写进缓存后才一股脑发给客户端,而我们要建立的是一个很长的连接,这个连接过程中随时可能要发送数据,不需要缓存,所以这里必须通过X-Accel-Buffering
告诉nginx
你对我这段代码不要给我做缓存,否则你会在客户端等很长很长时间却一个字符也收不到。
注意后面那个100
次的for
循环,网上几乎所有的例子里在这里都会使用while(1)
的一个无限循环,但我个人的经验恰恰相反,在这里应该是一个for
循环,而不是while
循环。为什么呢?因为我们必须使得整个程序的执行时间可控,如果你用for
循环100
次,每次sleep
3秒钟的话,整个脚本的执行时长也就是在5分钟左右,而如果你使用while
无限循环的话,整个时间会几乎是无限长。有的同学可能会说我在php.ini
里设置了max_excution_time
是60
秒钟啊,很好,我一开始也是这么认为的,但是:sleep(3)的时间不算在内!所以你可以想象一下你的脚本要执行多长时间才会结束并安静地从内存中退出去?
nginx设置
最后,还需要配置nginx
,只需要一句话,在你的nginx.conf
的location
里:
fastcgi_read_timeout 600s;
这是由于nginx
在和我们的php-fpm
进行通讯的时候,这个地方的缺省值是60
,如果它等了60
秒,结果php-fpm
一个字符也没有送过来的话,nginx
会强行中止与我们的程序的连接,但我们的程序实际还没有执行结束,这时候js
客户端发现连接断了,就会自动重启一个新的连接,结果我们服务器端的资源很快被耗尽了。在这里设为600
秒的意思是告诉nginx
,即使我的php
代码一个字符也不给你发送,你也必须老老实实呆够10
分钟才可以退出。而实际上是,还记得我们前面的for
循环吗?我们会在100
次等待3
秒后,也就是5
分钟内退出我们的php
脚本执行,不会凑够10
分钟的。所以这里很安全。