资讯专栏INFORMATION COLUMN

在PHP服务中使用Websocket

ideaa / 1185人阅读

摘要:协议在年诞生,年成为国际标准。手时不容易屏蔽,能通过各种代理服务器。没有同源限制,客户端可以与任意服务器通信。协议标识符是如果加密,则为,服务器网址就是。

WebSocket

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

为什么需要 WebSocket?

需求是:用户停留页面 15 分钟,且没有任何操作,则弹出登陆窗口,让用户重新登陆。

一般这样的需求实现多为长连接轮询,会有浏览器的卡顿、服务端消耗及不容易维护等问题。

后来发现 websocket 这样的通讯方式,主要有以下优点:

建立在 TCP 协议之上,服务器端的实现比较容易。

手时不容易屏蔽,能通过各种 HTTP 代理服务器。

数据格式比较轻量,性能开销小,通信高效。

可以发送文本,也可以发送二进制数据。

没有同源限制,客户端可以与任意服务器通信。

协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

目标已经选定,那么如何实现呢?

PHP 已经有非常好用的异步网络通信框架 swoole,节省了自己实现 websocket 服务的时间。我的使用的是 laravel 框架,最终选择了 laravel-swoole 扩展。

安装配置

引入 laravel-swoole 扩展包 wiki 。启用 websocket.enabled 及其他相应的配置,通过下面的命名可以非常方便的管理服务:

php artisan swoole:http {start|stop|restart|reload|infos}

修改配置文件中的默认 handler 配置为自定义的类:主要是为了自定义 websocket 的生命周期中的一些回调。

    /*
    |--------------------------------------------------------------------------
    | Websocket handler for onOpen and onClose callback
    | Replace this handler if you want to customize your websocket handler
    |--------------------------------------------------------------------------
    */
    "handler" => AppListenersSwooleWebsocketHandler::class,

    /*
    |--------------------------------------------------------------------------
    | Default frame parser
    | Replace it if you want to customize your websocket payload
    |--------------------------------------------------------------------------
    */
    "parser" => SwooleTWHttpWebsocketSocketIOSocketIOParser::class,

当配置完成后,会在 routes 目录中添加一个名为 websocket.php 的文件。可以非常方便像定义 laravel 路由一样,定义各种事件。例如:

//Websocket::on("open", function ($websocket, Request $request) {
//    Log::info("websocket","open 111 +" . $websocket->getSender());
//});
//
//Websocket::on("connect", function ($websocket, Request $request) {
//    Log::info("websocket","Connected ++ 222" . $websocket->getSender());
//    // called while socket on connect.
//});
//
//Websocket::on("disconnect", function ($websocket) {
//    Log::info("websocket","Disconnected ++ 333" . $websocket->getSender());
//    // called while socket on disconnect
//});

// 在 UserController 中的 checkLogin 方法上会带有$websocket, $data这两个参数。
Websocket::on("loginCheck", "AppHttpControllersApiUserController@checkLogin");

Websocket::on("logout", "AppHttpControllersApiUserController@sendLogout");
使用 控制器:
public function checkLogin($websocket, $data)
{
    if (empty($data["holding"])) {
        $websocket->emit("message", ["code" => self::HTTP_UNPROCESSABLE_ENTITY, "message" => "参数错误"]);

        return false;
    }
    $flag = true;
    $step = 1;
    while ($flag) {
        $step++;
        if ( ! $this->validateLoginStatus($data["holding"])) {
            $websocket->emit("message", ["code" => self::HTTP_UNAUTHORIZED, "message" => "登陆超时"]);
            unset($step);
            $flag = false;
        }else {
            if($step === 1) {
                $websocket->emit("message", ["code" => self::HTTP_OK, "message" => "success"]);
            }
        }
        sleep(1);
    }
}
前端调用

这里一定要注意数据包的结构,之前就踩了比较多的坑,API docs 才找到正确的结构:

    var websocket = new WebSocket("ws://127.0.0.1:1215");
    websocket.onopen = function (evt) {
        console.log("已连接websocket服务器");
        // 这里比较关键,通道建立后,可以进非常方便的进行轮询
        setInterval(function() {
           if (websocket.bufferedAmount == 0)
              var data = {"holding": "eyJLQNDqj0y473pCJ6zjMTUyOTk5NzU1MgnVMQ==$d84XkeMCv7umajhMRiU"};
              websocket.send(encodeMessage("loginCheck", data));
        }, 50);
    };
    // 监听消息体
    websocket.onmessage = function (evt) {
        console.log(decodeMessage(evt.data))
    };
    // 监听关闭消息
    websocket.onclose = function (evt) {
        console.log("websocket close");
    };
    //监听连接错误信息
    websocket.onerror = function (evt) {
        console.log(evt);
    };

    function decodeMessage(str) {
        return JSON.parse(str.substring(2))[1] || [];
    }

    function encodeMessage(event, data) {
        return JSON.stringify([
            event,
            data
        ])
    }
Swoole扩展安装

因为 swoole 的安装依赖 phpsockets 模块的开启。

安装 swoole

中间报错,需要安装以下依赖:

yum -y install gcc postgresql-devel gcc-c++

下载 swoole 扩展源码,安装 安装步骤 进行安装即可。

性能监控

查看当前

$ netstat -n | awk "/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}"

TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1

常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。

删除进程

查看进程数

$ ps -eaf |grep "swoole" | grep -v "grep"| awk "{print $2}"|wc -l

批量删除进程:

$ ps -eaf |grep "swoole" | grep -v "grep"| awk "{print $2}"|xargs kill -9

重启服务。

参考文章

阮一峰WebSocket教程

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106904.html

相关文章

  • Websocket解析及实现

    摘要:早期的轮询是通过不断自动刷新页面而实现的。长轮询的另一个问题是缺乏标准实现。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务器端的连接不过期。协议解析协议包含两部分一部分是握手,一部分是数据传输。 Websocket是什么? Websocket是一个因为应用场景越来越复杂而提出的,针对浏览器和web服务器之间双向持续通信而设计,而且优雅地兼容HTTP的协议(我猜想:同...

    XboxYan 评论0 收藏0
  • php只能做网站?基于swoole+websocket开发双向通信应用

    摘要:那么,是否就无法用来开发双向通信的应用呢答案是否定的。内置通信支持,可以与程序基于进行双向通信。通信协议于年被定为标准,并由补充规范。前言 众所周知,PHP用于开发基于HTTP协议的网站应用非常便捷。而HTTP协议是一种单向的通信协议,只能接收客户端的请求,然后响应请求,不能主动向客户端推送信息。因此,一些实时性要求比较高的应用,如实时聊天、直播应用、在线网页游戏等,就不适合采用HTTP协议...

    番茄西红柿 评论0 收藏0
  • php只能做网站?基于swoole+websocket开发双向通信应用

    摘要:那么,是否就无法用来开发双向通信的应用呢答案是否定的。内置通信支持,可以与程序基于进行双向通信。通信协议于年被定为标准,并由补充规范。前言 众所周知,PHP用于开发基于HTTP协议的网站应用非常便捷。而HTTP协议是一种单向的通信协议,只能接收客户端的请求,然后响应请求,不能主动向客户端推送信息。因此,一些实时性要求比较高的应用,如实时聊天、直播应用、在线网页游戏等,就不适合采用HTTP协议...

    琛h。 评论0 收藏0
  • 实战 swoole【聊天室】

    摘要:是一个请求对象,包含了客户端发来的握手请求信息事件函数中可以调用向客户端发送数据或者调用关闭连接事件回调是可选的当服务器收到来自客户端的数据帧时会回调此函数。 前言:了解概念之后就应该练练手啦,不然就是巨婴 有收获的话请加颗小星星,没有收获的话可以 反对 没有帮助 举报三连 代码仓库 实战swoole【聊天室】 在线体验 准备工作 需要先看初识swoole【上】,了解基本的服务端...

    andycall 评论0 收藏0
  • swoole——从入门到放弃(一)

    摘要:进程可以使用函数向进程投递新的任务。当前的进程在调用回调函数时会将进程状态切换为忙碌,这时将不再接收新的,当函数返回时会将进程状态切换为空闲然后继续接收新的。当进程投递的任务在中完成时,进程会通过方法将任务处理的结果发送给进程。 swoole——从入门到放弃(一) 一、swoole的源码包安装 下载swoole源码:git clone https://gitee.com/swoole...

    morgan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<