php 实现长连接
长连接技术(Long Polling)
在服务器端hold住一个连接, 不立即返回, 直到有数据才返回, 这就是长连接技术的原理
长连接技术的关键在于hold住一个HTTP请求, 直到有新数据时才响应请求, 然后客户端再次自动发起长连接请求.
那怎么样hold住一个请求呢?服务器端的代码可能看起来像这样的
set_time_limit(0); //这句很重要, 不至于运行超时
while (true) {
if (hasNewMessage()) {
echo json_encode(getNewMessage());
break;
}
usleep(100000); //避免太过频繁的查询
}
没错,就是通过循环来实现hold住一个请求, 不至于立即返回. 查询到有新数据之后才响应请求. 然后客户端处理数据后,再次发起长连接请求.
客户端的代码是像这样的
<script type="text/javascript">
(function longPolling() {
$.ajax({
"url": "server.php",
"data": data,
"dataType": "json",
"success": function(data) {
processData(data);
longPolling();
},
"error": function(data) {
longPolling();
}
});
})();
</script>
一个简易的聊天室
通过长连接, 我们可以开发一个简易的web聊天室
下面, 我们通过redis开发一个简易的web聊天室
每一个客户端发起长连接时, 在服务器端生成一个消息队列, 对应该用户. 然后监听有无新数据, 有则返回数据到客户端进行处理, 并再起发起长连接请求.
每一个客户端发起消息时, 进行消息队列的广播.
下面是代码片段:
<?php
namespace churchLongPolling;
use Closure;
use churchLongPollingQueueRedisQueue;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationJsonResponse;
class Server
{
public $event = [];
public $redisQueue = null;
public $request = null;
public $response = null;
public function __construct()
{
$this->redisQueue = new RedisQueue();
$this->request = Request::createFromGlobals();
$this->response = new JsonResponse();
}
public function on($event, Closure $closure)
{
if (is_callable($closure)) {
$this->event[$event][] = $closure;
}
}
public function fire($event)
{
if (isset($this->event[$event])) {
foreach ($this->event[$event] as $callback) {
call_user_func($callback, $this);
}
}
}
public function sendMessage($data)
{
switch ($data["type"]) {
case "unicast": //单播
$this->unicast($data["target"], $data["data"], $data["resource"]);
break;
case "multicast": //组播
foreach ($data["target"] as $target) {
$this->unicast($target, $data["data"], $data["resource"]);
}
break;
case "broadcast": //广播
foreach ($this->redisQueue->setQueueName("connections") as $target) {
$this->unicast($target, $data["data"], $data["resource"]);
}
break;
}
$this->fire("message");
}
public function unicast($target, $message, $resource = "system")
{
$redis_queue = new RedisQueue();
$redis_queue->setQueueName($target)->push($resource . ":" . $message);
}
public function getMessage($target)
{
return $this->redisQueue->setQueueName($target)->pop();
}
public function hasMessage($target)
{
return count($this->redisQueue->setQueueName($target));
}
public function run()
{
$data = $this->request->request;
while (true) {
if ($data->get("action") == "getMessage") {
if ($this->hasMessage($data->get("target"))) {
$this->response->setData([
"state" => "ok",
"message" => "获取成功",
"data" => $this->getMessage($data->get("target"))
]);
$this->response->send();
break;
}
} elseif ($data->get("action") == "connect") {
$exist = false;
foreach ($this->redisQueue->setQueueName("connections") as $connection) {
if ($connection == $data->get("data")) {
$exist = true;
}
}
if (! $exist) {
$this->redisQueue->setQueueName("connections")->push($data->get("data"));
}
$this->fire("connect");
break;
}
usleep(100000);
}
}
}
已将源代码开源到github
基于长连接开发的web版简易聊天室
长连接避免了过于频繁的轮询. 但服务器维持一个长连接也有额外的资源消耗. 大并发时性能不理想. 在小型应用里面可以考虑使用
更建议客户端使用html5的websocket协议, 服务器端使用swoole.
有关swoole, 你可以戳这里
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: PHP学习笔记-Session
- 下一篇: 表示 十六进制字符串 对 10取模的方法