websocket 导致大量apache进程_Swoole学习笔记四:搭建WebSocket长连接 之 基本框架搭建...

436bbb793b34ecb6641c779e1e330a77.png

0、前言

在实际项目中,长连接我们会经常用到,有时候为了后台的信息即时性,我们也会使用到长连接进行消息推送等。
同时,在一些例如即时通讯工具、游戏开发、行程APP开发中。长连接都是基础的、必须掌握的技能点。

下面我们就来学习下,如何使用Swoole,搭建一个基本的WebSocket架构。

1、注意事项

(以下标记可以理解为一个class的实例,或一个请求实例。)
1、上下线状态管理,由于长连接是基于TCP握手协议构建的,所以当我们握手时需要标记对应实例的上线状态,当该标记挥手离开时,我们需要标记其下线状态,又或者直接杀掉该进程。

2、消息广播过滤,前面说到,标记会存在下线情况,当标记下线的时候,如果你还向该标记push消息,就会出现警告错误,所以需要利用上下线状态,避开这些可能已经挂掉的请求。

3、消息广播管理,由于WebSocket只有一个动作接收所有请求消息,所以不管是上线、下线、聊天消息等内容都是发送到这个动作中,所以我们需要合理的设计广播内容数据格式,至少都需要有一个code请求状态跟user_id身份标记。

2、服务端server.php代码

<?php
// +----------------------------------------------------------------------
// 小黄牛blog - Swoole 即时通讯交互处理
// +----------------------------------------------------------------------
// Copyright (c) 2018 https://xiuxian.junphp.com All rights reserved.
// +----------------------------------------------------------------------
// Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// Author: 小黄牛 <1731223728@qq.com>
// +----------------------------------------------------------------------
class Server{
    /**
     * 客户端身份存储器
    */
    private $_data = []; 
    /**
     * WS的启动实例
    */
    private $_ws;
    /**
     * host-IP,0.0.0.0表示允许接收所有请求
    */
    private $_host = '0.0.0.0';
    /**
     * 端口号
    */
    private $_port = '9502';
    /**
     * 这是启动服务端的入口
    */
    public function run() { 
        $this->start_service(); 
        $this->start_handshake();
        $this->start_message();
        $this->end();
    }
    /**
     * ①启动websocker服务
    */
    private function start_service() {
        # 创建websocket服务器对象,监听0.0.0.0:9502端口
        $this->_ws = new swoole_websocket_server($this->_host, $this->_port);
    }
    /**
     * ②监听WebSocket握手申请
    */
    private function start_handshake() {
        # 监听WebSocket连接打开事件
        $this->_ws->on('open', function ($ws, $request){
            # 加入身份存储器
            $this->_data[$request->fd]['fd']      = $request->fd;
            # 默认离线状态
            $this->_data[$request->fd]['status']  = 0;
        });
    }
    /**
     * ③监听客户端消息发送请求
    */
    private function start_message() {
        # 监听WebSocket消息事件
        $this->_ws->on('message', function ($ws, $frame) {
            $data = json_decode($frame->data, true);
            # 登录广播处理
            if ($data['code'] == 1) {
                # 修改fd对应的身份
                $this->_data[$frame->fd]['user_id'] = $data['user_id'];
                # 设置上线状态
                $this->_data[$frame->fd]['status']  = 1;
                # 发送广播上线消息
                $data['content'] = '【'.$data['user_id'].'】骑着小黄牛上线啦~!';
                $this->broadcast($ws, $this->json($data), $frame->fd);
            # 其他请求
            } else {
                # 广播消息
                $this->broadcast($ws, $frame->data, $frame->fd);
            }
        });
    } 
    /**
     * ④监听客户端退出事件
    */
    private function end() {
        # 监听WebSocket连接关闭事件
        $this->_ws->on('close', function ($ws, $fd) {
            # 获取用户ID
            $user_id = $this->_data[$fd]['user_id'];
            # 设置离线状态
            $this->_data[$fd]['status']  = 0;
            $data = [
                'code' => 2,
                'user_id' => $user_id,
                'content' => '【'.$user_id.'】骑着小扫帚灰溜溜的走了~~!'
            ];
            # 广播消息
            $this->broadcast($ws, $this->json($data));
        });
        $this->_ws->start();
    }
    /**
     * 广播消息
     * @todo 无
     * @author 小黄牛
     * @version v1.0.0.1 + 2018.11.12
     * @deprecated 暂不弃用
     * @global 无
     * @param object $wx 实例
     * @param string $content 广播内容
     * @param string $id 自己的身份凭证 如果为空则也向自己广播
     * @return void
    */
    private function broadcast($ws, $content, $id=null) {
        # 向所有人广播
        foreach ($this->_data as $k=>$v) {
            # 不向自己广播,并且要在线的
            # 注意,这里一定要有上线状态的限制,否则假设用户已经退出,但你的进程还开着,实际上已经关闭,这时候push就会报错
            if ($k != $id && $v['status'] == 1) {
                $ws->push($v['fd'], $content);
            }
        }
    }
    /**
     * 数组转json
     * @todo 无
     * @author 小黄牛
     * @version v1.0.0.1 + 2018.11.08
     * @deprecated 暂不弃用
     * @global 无
     * @param array $array 数组
     * @return json
    */
    private function json($array) {
        return json_encode($array, JSON_UNESCAPED_UNICODE);
    }
}
$socketServer = new Server();
$socketServer->run();

3、前端WebSocket代码

<?php
// +----------------------------------------------------------------------
// 小黄牛blog - websocket
// +----------------------------------------------------------------------
// Copyright (c) 2018 https://xiuxian.junphp.com All rights reserved.
// +----------------------------------------------------------------------
// Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// Author: 小黄牛 <1731223728@qq.com>
// +----------------------------------------------------------------------
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Swoole+Websocket案例 - 小黄牛</title>
<script src="./js/jquery.min.js"></script>
<style>
html,body{margin:0;padding:0;font-size:13px}
.left{width: 20%;height: 600px;border: 1px solid #ddd;float: left;}
.right{width: 79.7%;height: 400px;border: 1px solid #ddd;border-left: 0px;float: left;overflow: auto;}
.bottom{width: 79.7%;height: 199px;border: 1px solid #ddd;border-left: 0px;border-top: 0px;float: left;}
#content{width: 99.5%;height: 165px;}
.blue{color:blue}
.red{color:red}
.div_left{width:100%;float:left}
.div_right{width:100%;float:left;text-align: right;}
.div_centent{width:100%;float:left;text-align: center;}
</style>
</head>
<body>
<div id="USER"></div>
<div class="left">
    <ul>
    </ul>
</div>
<div class="right">
</div>
<div class="bottom">
    <textarea id="content"></textarea>
    <button type="button" id="submit">发送消息</button>
</div>
<h3>使用方法:</h3>
<p>①:CD进您的server.php文件目录</p>
<p>②:如果您是调试阶段,可以直接php server.php,激活程序,这样的话在运行过程中出错,能在cmd界面查看报错内容</p>
<p>③:如果您是部署阶段,可以使用nohup server.php >>/dev/null 2>&1 &命令,后台守护进程运行。</p>
</body>
</html>
<script>
var wsServer = 'ws://47.106.187.208:9502';
var websocket = new WebSocket(wsServer);
// 生成一个唯一ID,假设这是userID
var USER_ID = "static" + Math.round(Math.random() * 10000);
$('#USER').html('您的USER_ID为:'+USER_ID);
// 打开连接成功
websocket.onopen = function (evt) {
    console.log("WebSocker - 链接成功.");
    var data = {
        'code':1, // 我们假设code为1时,是绑定登录请求
        'user_id':USER_ID
    };
    // 前端发送json前,必须先转义成字符串
    data = JSON.stringify(data);
    websocket.send(data);
};
// 接收服务端断开时的消息通知
websocket.onclose = function (evt) {
    alert("链接断开了");
};
// 接收到服务端的消息通知
websocket.onmessage = function (evt) {
    console.log('Retrieved data from server: ' + evt.data);
    var obj = JSON.parse(evt.data); 
    // 登录广播
    if (obj.code == 1) {
        // 存在修改上线状态
        if ($("#"+obj.user_id).length>0) {
            $("#"+obj.user_id+' span').removeClass('blue');
            $("#"+obj.user_id+' span').addClass('red');
            $("#"+obj.user_id+' span').html('离线');
        // 不存在,添加用户列表
        } else {
            $('.left ul').append('<li id="'+obj.user_id+'">'+obj.user_id+' <span class="blue">(在线)</span></li>');
        }
        $('.right').append('<div class="div_centent">'+obj.content+'</div>');
    // 下线广播
    } else if (obj.code == 2) {
        $("#"+obj.user_id+' span').removeClass('blue');
        $("#"+obj.user_id+' span').addClass('red');
        $("#"+obj.user_id+' span').html('离线');
        $('.right').append('<div class="div_centent">'+obj.content+'</div>');
    // 聊天消息广播
    } else if (obj.code == 3) {
        $('.right').append('<div class="div_left">'+obj.user_id+':'+obj.content+'</div>');
        // 聊天界面默认自动底部
        $('.right').scrollTop( $('.right')[0].scrollHeight );
    }
};
// 接收Socket连接失败时的异常通知
websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
};
// 点击发送消息按钮
$('#submit').click(function(){
    var content = $('#content').val();
    $('.right').append('<div class="div_right">'+content+':'+USER_ID+'</div>');
    var data = {
        'code':3, // 我们假设code为3时,既为聊天消息广播请求
        'user_id':USER_ID,
        'content':content
    };
    // 前端发送json前,必须先转义成字符串
    data = JSON.stringify(data);
    websocket.send(data);
    // 输入表单清空
    $('#content').val('');
    // 聊天界面默认自动底部
    $('.right').scrollTop( $('.right')[0].scrollHeight );
});
</script>

4、使用方法

①:使用CD命令进您的server.php文件目录
②:如果您是调试阶段,可以直接php server.php命令激活程序,这样的话在运行过程中出错,能在cmd界面查看报错内容
③:如果您是部署阶段,可以使用nohup php server.php >>/dev/null 2>&1 &命令,后台守护进程运行。

完整的案例DEMO,可以直接到我的开源栏目中进行下载:Swoole聊天室Demo之一


最后推荐大家可以用下我开源的一个基于Swoole4.5+研发的PHP框架。该框架基于注解实现了很多好玩的功能,很适合新人快速上手Swoole扩展。

SW-X框架-专注高性能便捷开发而生的PHP-SwooleX框架​www.sw-x.cn
6fd6791a0c1d8f8a3239123ab2d13227.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值