ThinkPHP5.1 + Swoole 实现聊天室

下载TP5.1

composer create-project topthink/think tp5

按照Swoole官网说明安装swoole扩展,然后安装think-swoole扩展。

Swoole官网:https://www.swoole.com

在tp5.1的项目根目录下执行composer命令安装think-swoole:

composer require topthink/think-swoole
如果提示需要tp6就换成下面的
composer require topthink/think-swoole=2.0.*

如果提示: putenv() has been disabled for security reasons

意思是 putenv()函数被禁用

在宝塔PHP管理禁用函数里面找到该函数删除,没有提示跳过即可

安装成功后vendor/topthink会有think-swoole

安装完扩展后,你什么都不需要做,最简单的就是直接在命令行(应用根目录下面)下执行:

php think swoole

启动成功后会显示

Starting swoole http server...
Swoole http server started: <http://0.0.0.0:9501>
You can exit with `CTRL-C`

如果报错"SwooleTable::create(): unable to allocate memory"

重新在config/swoole.php里设置下cache_size这个值(没有就新建cache_size下标数组),cache_size默认是1024*1024,我设成1024后就能成功启动

1.在application新建swoole模块index控制器index方法加载聊天室页面
页面以及js/css文件在附件可以下载

<?php

namespace app\swoole\controller;

use think\Controller;

class Index extends Controller
{
    public function index()
    {
        return $this->fetch();
    }
}
<html><head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>workerman-chat PHP聊天室 Websocket(HTLM5/Flash)+PHP多进程socket实时推送技术</title>
    <script type="text/javascript">
        //WebSocket = null;
    </script>
    <link href="__PUBLIC__/chat/css/bootstrap.min.css" rel="stylesheet">
    <link href="__PUBLIC__/chat/css/style.css" rel="stylesheet">
    <!-- Include these three JS files: -->
    <script type="text/javascript" src="__PUBLIC__/chat/js/swfobject.js"></script>
    <script type="text/javascript" src="__PUBLIC__/chat/js/web_socket.js"></script>
    <script type="text/javascript" src="__PUBLIC__/chat/js/jquery.min.js"></script>

    <script type="text/javascript">
        if (typeof console == "undefined") {    this.console = { log: function (msg) {  } };}
        // 如果浏览器不支持websocket,会使用这个flash自动模拟websocket协议,此过程对开发者透明
        WEB_SOCKET_SWF_LOCATION = "__PUBLIC__/chat/swf/WebSocketMain.swf";
        // 开启flash的websocket debug
        WEB_SOCKET_DEBUG = true;

        var ws, name, client_list={};

        // 连接服务端
        function connect() {
            // 创建websocket
            ws = new WebSocket("ws://"+document.domain+":9501");
            // 当socket连接打开时,输入用户名
            ws.onopen = onopen;
            // 当有消息时根据消息类型显示不同信息
            ws.onmessage = onmessage;
            ws.onclose = function() {
                console.log("连接关闭,定时重连");
                connect();
            };
            ws.onerror = function() {
                console.log("出现错误");
            };
        }

        // 连接建立时发送登录信息
        function onopen()
        {
            if(!name)
            {
                show_prompt();
            }
            // 登录
            var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"<?php echo isset($_GET['room_id']) ? $_GET['room_id'] : 1?>"}';
            console.log("websocket握手成功,发送登录数据:"+login_data);
            ws.send(login_data);
        }

        // 服务端发来消息时
        function onmessage(e)
        {
            console.log(e.data);
            var data = eval("("+e.data+")");
            switch(data['type']){
                // 服务端ping客户端
                case 'ping':
                    ws.send('{"type":"pong"}');
                    break;;
                // 登录 更新用户列表
                case 'login':
                    //{"type":"login","client_id":xxx,"client_name":"xxx","client_list":"[...]","time":"xxx"}
                    say(data['client_id'], data['client_name'],  data['client_name']+' 加入了聊天室', data['time']);
                    if(data['client_list'])
                    {
                        client_list = data['client_list'];
                    }
                    else
                    {
                        client_list[data['client_id']] = data['client_name'];
                    }
                    flush_client_list();
                    console.log(data['client_name']+"登录成功");
                    break;
                // 发言
                case 'say':
                    //{"type":"say","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"}
                    say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);
                    break;
                // 用户退出 更新用户列表
                case 'logout':
                    //{"type":"logout","client_id":xxx,"time":"xxx"}
                    say(data['from_client_id'], data['from_client_name'], data['from_client_name']+' 退出了', data['time']);
                    delete client_list[data['from_client_id']];
                    flush_client_list();
            }
        }

        // 输入姓名
        function show_prompt(){
            name = prompt('输入你的名字:', '');
            if(!name || name=='null'){
                name = '游客';
            }
        }

        // 提交对话
        function onSubmit() {
            var input = document.getElementById("textarea");
            var to_client_id = $("#client_list option:selected").attr("value");
            var to_client_name = $("#client_list option:selected").text();
            ws.send('{"type":"say","to_client_id":"'+to_client_id+'","to_client_name":"'+to_client_name+'","content":"'+input.value.replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r')+'"}');
            input.value = "";
            input.focus();
        }

        // 刷新用户列表框
        function flush_client_list(){
            var userlist_window = $("#userlist");
            var client_list_slelect = $("#client_list");
            userlist_window.empty();
            client_list_slelect.empty();
            userlist_window.append('<h4>在线用户</h4><ul>');
            client_list_slelect.append('<option value="all" id="cli_all">所有人</option>');
            for(var p in client_list){
                userlist_window.append('<li id="'+p+'">'+client_list[p]+'</li>');
                client_list_slelect.append('<option value="'+p+'">'+client_list[p]+'</option>');
            }
            $("#client_list").val(select_client_id);
            userlist_window.append('</ul>');
        }

        // 发言
        function say(from_client_id, from_client_name, content, time){
            $("#dialog").append('<div class="speech_item"><img src="http://lorempixel.com/38/38/?'+from_client_id+'" class="user_icon" /> '+from_client_name+' <br> '+time+'<div style="clear:both;"></div><p class="triangle-isosceles top">'+content+'</p> </div>');
        }

        $(function(){
            select_client_id = 'all';
            $("#client_list").change(function(){
                select_client_id = $("#client_list option:selected").attr("value");
            });
        });
    </script>
</head>
<body onload="connect();">
<div class="container">
    <div class="row clearfix">
        <div class="col-md-1 column">
        </div>
        <div class="col-md-6 column">
            <div class="thumbnail">
                <div class="caption" id="dialog"></div>
            </div>
            <form onsubmit="onSubmit(); return false;">
                <select style="margin-bottom:8px" id="client_list">
                    <option value="all">所有人</option>
                </select>
                <textarea class="textarea thumbnail" id="textarea"></textarea>
                <div class="say-btn"><input type="submit" class="btn btn-default" value="发表" /></div>
            </form>
            <div>
                    <b>房间列表:</b>(当前在 房间<?php echo isset($_GET['room_id'])&&intval($_GET['room_id'])>0 ? intval($_GET['room_id']):1; ?>)<br>
                    <a href="/?room_id=1">房间1</a>    <a href="/?room_id=2">房间2</a>    <a href="/?room_id=3">房间3</a>    <a href="/?room_id=4">房间4</a>
                <br><br>
            </div>
            <p class="cp">PHP多进程+Websocket(HTML5/Flash)+PHP Socket实时推送技术    Powered by <a href="http://www.workerman.net/workerman-chat" target="_blank">workerman-chat</a></p>
        </div>
        <div class="col-md-3 column">
            <div class="thumbnail">
                <div class="caption" id="userlist"></div>
            </div>

        </div>
    </div>
</div>
<script type="text/javascript">var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F7b1919221e89d2aa5711e4deb935debd' type='text/javascript'%3E%3C/script%3E"));</script>
</body>
</html>

在浏览器访问http://你的域名/swoole/index/index 即可看到聊天室的页面效果

这时F12会显示WebSocket连接失败,因为我们还没写swoole逻辑代码
新建swoole控制器

<?php

namespace app\swoole\controller;

use think\Controller;
use swoole_websocket_server;
use think\facade\Cache;
use think\swoole\Session;
use think\swoole\Server;
use app\swoole\model\Swoole AS SwooleModel;


class Swoole extends Server
{
    protected $host = '0.0.0.0'; //监听所有地址
    protected $port = 9501; //监听9501端口
    protected $serverType = 'socket';
    protected $option = [
        'worker_num'               => 4, //设置启动的Worker进程数
        'daemonize'                => false, //守护进程化(上线改为true)
        'backlog'                  => 128, //Listen队列长度
        'dispatch_mode'            => 2, //固定模式,保证同一个连接发来的数据只会被同一个worker处理

        //心跳检测:每60秒遍历所有连接,强制关闭10分钟内没有向服务器发送任何数据的连接
        'heartbeat_check_interval' => 60,
        'heartbeat_idle_time'      => 600
    ];

    //建立连接时回调函数
    public function onOpen($server, $req)
    {
        $fd = $req->fd;//客户端标识
        //省略给用户绑定fd逻辑......
        echo "用户建立了连接,标识为{$fd}\n";
    }

    //接收数据时回调函数
    public function onMessage($server, $frame)
    {
        $fd = $frame->fd;
        // 客户端传递的是json数据
        $data = $frame->data;
        $data = json_decode($data, true);
        switch ($data['type']) {
            case 'pong':
                return;
                break;
            case 'login'://登录
                if (empty($data['room_id'])) return json_encode(['status' => 0, 'message' => '房间ID不能为空']);
                //把房间号昵称放到session中
                $room_id                 = $data['room_id'];
                $client_name             = htmlspecialchars($data['client_name']);
                $_SESSION['room_id']     = $room_id;
                $_SESSION['client_name'] = $client_name;
                //把用户加入房间
                SwooleModel::create(['room_id' => $room_id, 'client_name' => $client_name, 'fd' => $fd]);
                $roomData = SwooleModel::where(['room_id' => $room_id])->select();
                // 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx}
                $new_message = array('type' => $data['type'], 'client_id' => $fd, 'client_name' => $client_name, 'time' => date('Y-m-d H:i:s', time()));
                // 获取房间内所有用户列表
                $new_message['client_list'] = SwooleModel::getRoomUser($room_id);
                // 给当前用户发送用户列表
                foreach ($roomData as $key => $value) {
                    $server->push($value['fd'], json_encode($new_message));
                }
                break;
            case 'say':
                // 非法请求
                if (!isset($_SESSION['room_id'])) json_encode(['status' => 0, 'message' => '房间ID不能为空']);
                $room_id     = $_SESSION['room_id'];
                $client_name = $_SESSION['client_name'];

                // 私聊
                if ($data['to_client_id'] != 'all') {
                    $new_message = array(
                        'type'             => 'say',
                        'from_client_id'   => $fd,
                        'from_client_name' => $client_name,
                        'to_client_id'     => $data['to_client_id'],
                        'content'          => "<b>对你说: </b>" . nl2br(htmlspecialchars($data['content'])),
                        'time'             => date('Y-m-d H:i:s'),
                    );
                    $server->push($data['to_client_id'], json_encode($new_message));
                    $new_message['content'] = "<b>你对" . htmlspecialchars($data['to_client_name']) . "说: </b>" . nl2br(htmlspecialchars($data['content']));
                    $server->push($fd, json_encode($new_message));
                    return;
                }

                $new_message = array(
                    'type'             => 'say',
                    'from_client_id'   => $fd,
                    'from_client_name' => $client_name,
                    'to_client_id'     => 'all',
                    'content'          => nl2br(htmlspecialchars($data['content'])),
                    'time'             => date('Y-m-d H:i:s'),
                );
                $roomData    = SwooleModel::where(['room_id' => $room_id])->select();
                foreach ($roomData as $key => $value) {
                    $server->push($value['fd'], json_encode($new_message));
                }
            default:
                # code...
                break;
        }
    }

    //连接关闭时回调函数
    public function onClose($server, $fd)
    {
        SwooleModel::where(['fd' => $fd])->delete();

        $new_message = array('type'=>'logout', 'from_client_id'=>$fd, 'from_client_name'=>$_SESSION['client_name'], 'time'=>date('Y-m-d H:i:s'));
        $roomData = SwooleModel::where(['room_id' => $_SESSION['room_id']])->select();
        foreach ($roomData as $key => $value) {
            $server->push($value['fd'], json_encode($new_message));
        }
        echo "标识{$fd}关闭了连接\n";
    }
}

项目根目录下执行
php public/index.php swoole/Swoole/start

在浏览器访问http://你的域名/swoole/index/index

页面是盗用workman官方demo的,再次感谢workman 感谢cctv
完整代码在(包含数据库模型):
CODING:tp5集成swoole案例源码

相关文章推荐
ThinkPHP5.0结合Swoole开发WebSocket在线聊天

ThinkPHP5.1 Swoole上手指南

Pasa吴PHP博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论