资讯专栏INFORMATION COLUMN

实战 swoole【聊天室】

andycall / 2361人阅读

摘要:是一个请求对象,包含了客户端发来的握手请求信息事件函数中可以调用向客户端发送数据或者调用关闭连接事件回调是可选的当服务器收到来自客户端的数据帧时会回调此函数。

前言:了解概念之后就应该练练手啦,不然就是巨婴

有收获的话请加颗小星星,没有收获的话可以 反对 没有帮助 举报三连

代码仓库

实战swoole【聊天室】

在线体验

准备工作

需要先看初识swoole【上】,了解基本的服务端WebSocket使用

js WebSocket客户端简单使用

使用
# 命令行1
php src/websocket/run.php
# 命令行2
cd public && php -S localhost:8000
# 客户端,多开几个查看效果
访问http://localhost:8000/
WebSocket

官方示例

$server = new swoole_websocket_server("0.0.0.0", 9501);
$server->on("open", function (swoole_websocket_server $server, $request) {
        echo "server: handshake success with fd{$request->fd}
";
    });
$server->on("message", function (swoole_websocket_server $server, $frame) {
        echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
";
        $server->push($frame->fd, "this is server");
    });
$server->on("close", function ($ser, $fd) {
        echo "client {$fd} closed
";
    });
$server->on("request", function (swoole_http_request $request, swoole_http_response $response) {
        global $server;//调用外部的server
        // $server->connections 遍历所有websocket连接用户的fd,给所有用户推送
        foreach ($server->connections as $fd) {
            $server->push($fd, $request->get["message"]);
        }
    });
$server->start();

详解:

swoole_websocket_server 继承自 swoole_http_server

设置了onRequest回调,websocket服务器也可以同时作为http服务器

未设置onRequest回调,websocket服务器收到http请求后会返回http 400错误页面

如果想通过接收http触发所有websocket的推送,需要注意作用域的问题,面向过程请使用global对swoole_websocket_server进行引用,面向对象可以把swoole_websocket_server设置成一个成员属性

function onOpen(swoole_websocket_server $svr, swoole_http_request $req);

当WebSocket客户端与服务器建立连接并完成握手后会回调此函数。

$req 是一个Http请求对象,包含了客户端发来的握手请求信息

onOpen事件函数中可以调用push向客户端发送数据或者调用close关闭连接

onOpen事件回调是可选的

function onMessage(swoole_websocket_server $server, swoole_websocket_frame $frame)

当服务器收到来自客户端的数据帧时会回调此函数。

$frame 是swoole_websocket_frame对象,包含了客户端发来的数据帧信息

onMessage回调必须被设置,未设置服务器将无法启动

客户端发送的ping帧不会触发onMessage,底层会自动回复pong包

swoole_websocket_frame 属性

$frame->fd,客户端的socket id,使用$server->push推送数据时需要用到

$frame->data,数据内容,可以是文本内容也可以是二进制数据,可以通过opcode的值来判断

$frame->opcode,WebSocket的OpCode类型,可以参考WebSocket协议标准文档

$frame->finish, 表示数据帧是否完整,一个WebSocket请求可能会分成多个数据帧进行发送(底层已经实现了自动合并数据帧,现在不用担心接收到的数据帧不完整)

聊天室服务端示例

目录结构:

config

socket.php

src

websocket

Config.php

run.php

WebSocketServer.php 内存表版本

WsRedisServer.php redis版本

WebSocketServer.php 内存表版本

createTable();
        // 实例化配置
        $this->config = Config::getInstance();
    }

    public function run()
    {
        $this->server = new swoole_websocket_server(
            $this->config["socket"]["host"],
            $this->config["socket"]["port"]
        );

        $this->server->on("open", [$this, "open"]);
        $this->server->on("message", [$this, "message"]);
        $this->server->on("close", [$this, "close"]);

        $this->server->start();
    }

    public function open(swoole_websocket_server $server, swoole_http_request $request)
    {
        $user = [
            "fd" => $request->fd,
            "name" => $this->config["socket"]["name"][array_rand($this->config["socket"]["name"])] . $request->fd,
            "avatar" => $this->config["socket"]["avatar"][array_rand($this->config["socket"]["avatar"])]
        ];
        // 放入内存表
        $this->table->set($request->fd, $user);

        $server->push($request->fd, json_encode(
                array_merge(["user" => $user], ["all" => $this->allUser()], ["type" => "openSuccess"])
            )
        );
    }

    private function allUser()
    {
        $users = [];
        foreach ($this->table as $row) {
            $users[] = $row;
        }
        return $users;
    }

    public function message(swoole_websocket_server $server, swoole_websocket_frame $frame)
    {
        $this->pushMessage($server, $frame->data, "message", $frame->fd);
    }

    /**
     * 推送消息
     *
     * @param swoole_websocket_server $server
     * @param string $message
     * @param string $type
     * @param int $fd
     */
    private function pushMessage(swoole_websocket_server $server, string $message, string $type, int $fd)
    {
        $message = htmlspecialchars($message);
        $datetime = date("Y-m-d H:i:s", time());
        $user = $this->table->get($fd);

        foreach ($this->table as $item) {
            // 自己不用发送
            if ($item["fd"] == $fd) {
                continue;
            }

            $server->push($item["fd"], json_encode([
                "type" => $type,
                "message" => $message,
                "datetime" => $datetime,
                "user" => $user
            ]));
        }
    }

    /**
     * 客户端关闭的时候
     *
     * @param swoole_websocket_server $server
     * @param int $fd
     */
    public function close(swoole_websocket_server $server, int $fd)
    {
        $user = $this->table->get($fd);
        $this->pushMessage($server, "{$user["name"]}离开聊天室", "close", $fd);
        $this->table->del($fd);
    }

    /**
     * 创建内存表
     */
    private function createTable()
    {
        $this->table = new swoole_table(1024);
        $this->table->column("fd", swoole_table::TYPE_INT);
        $this->table->column("name", swoole_table::TYPE_STRING, 255);
        $this->table->column("avatar", swoole_table::TYPE_STRING, 255);
        $this->table->create();
    }
}

WsRedisServer.php redis版本

config = Config::getInstance();
        // redis
        $this->initRedis();
        // 初始化,主要是服务端自己关闭不会清空redis
        foreach ($this->allUser() as $item) {
            $this->client->hdel("{$this->key}:{$item["fd"]}", ["fd", "name", "avatar"]);
        }
    }

    public function run()
    {
        $this->server = new swoole_websocket_server(
            $this->config["socket"]["host"],
            $this->config["socket"]["port"]
        );

        $this->server->on("open", [$this, "open"]);
        $this->server->on("message", [$this, "message"]);
        $this->server->on("close", [$this, "close"]);

        $this->server->start();
    }

    public function open(swoole_websocket_server $server, swoole_http_request $request)
    {
        $user = [
            "fd" => $request->fd,
            "name" => $this->config["socket"]["name"][array_rand($this->config["socket"]["name"])] . $request->fd,
            "avatar" => $this->config["socket"]["avatar"][array_rand($this->config["socket"]["avatar"])]
        ];
        // 放入redis
        $this->client->hmset("{$this->key}:{$user["fd"]}", $user);

        // 给每个人推送,包括自己
        foreach ($this->allUser() as $item) {
            $server->push($item["fd"], json_encode([
                "user" => $user,
                "all" => $this->allUser(),
                "type" => "openSuccess"
            ]));
        }
    }

    private function allUser()
    {
        $users = [];
        $keys = $this->client->keys("{$this->key}:*");
        // 所有的key
        foreach ($keys as $k => $item) {
            $users[$k]["fd"] = $this->client->hget($item, "fd");
            $users[$k]["name"] = $this->client->hget($item, "name");
            $users[$k]["avatar"] = $this->client->hget($item, "avatar");
        }
        return $users;
    }

    public function message(swoole_websocket_server $server, swoole_websocket_frame $frame)
    {
        $this->pushMessage($server, $frame->data, "message", $frame->fd);
    }

    /**
     * 推送消息
     *
     * @param swoole_websocket_server $server
     * @param string $message
     * @param string $type
     * @param int $fd
     */
    private function pushMessage(swoole_websocket_server $server, string $message, string $type, int $fd)
    {
        $message = htmlspecialchars($message);
        $datetime = date("Y-m-d H:i:s", time());
        $user["fd"] = $this->client->hget("{$this->key}:{$fd}", "fd");
        $user["name"] = $this->client->hget("{$this->key}:{$fd}", "name");
        $user["avatar"] = $this->client->hget("{$this->key}:{$fd}", "avatar");

        foreach ($this->allUser() as $item) {
            // 自己不用发送
            if ($item["fd"] == $fd) {
                continue;
            }

            $is_push = $server->push($item["fd"], json_encode([
                "type" => $type,
                "message" => $message,
                "datetime" => $datetime,
                "user" => $user
            ]));
            // 删除失败的推送
            if (!$is_push) {
                $this->client->hdel("{$this->key}:{$item["fd"]}", ["fd", "name", "avatar"]);
            }
        }
    }

    /**
     * 客户端关闭的时候
     *
     * @param swoole_websocket_server $server
     * @param int $fd
     */
    public function close(swoole_websocket_server $server, int $fd)
    {
        $user["fd"] = $this->client->hget("{$this->key}:{$fd}", "fd");
        $user["name"] = $this->client->hget("{$this->key}:{$fd}", "name");
        $user["avatar"] = $this->client->hget("{$this->key}:{$fd}", "avatar");
        $this->pushMessage($server, "{$user["name"]}离开聊天室", "close", $fd);
        $this->client->hdel("{$this->key}:{$fd}", ["fd", "name", "avatar"]);
    }

    /**
     * 初始化redis
     */
    private function initRedis()
    {
        $this->client = new Client([
            "scheme" => $this->config["socket"]["redis"]["scheme"],
            "host" => $this->config["socket"]["redis"]["host"],
            "port" => $this->config["socket"]["redis"]["port"],
        ]);
    }
}

config.php

path = __DIR__ . "/../../config/";
    }

    // 单例模式
    public static function getInstance()
    {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function offsetSet($offset, $value)
    {
        // 阉割
    }

    public function offsetGet($offset)
    {
        if (empty($this->config)) {
            $this->config[$offset] = require $this->path . $offset . ".php";
        }
        return $this->config[$offset];
    }

    public function offsetExists($offset)
    {
        return isset($this->config[$offset]);
    }

    public function offsetUnset($offset)
    {
        // 阉割
    }

    // 禁止克隆
    final private function __clone(){}
}

config/socket.php

 "0.0.0.0",
    "port" => 9501,

    "redis" => [
        "scheme" => "tcp",
        "host" => "0.0.0.0",
        "port" => 6380
    ],

    "avatar" => [
        "./images/avatar/1.jpg",
        "./images/avatar/2.jpg",
        "./images/avatar/3.jpg",
        "./images/avatar/4.jpg",
        "./images/avatar/5.jpg",
        "./images/avatar/6.jpg"
    ],

    "name" => [
        "科比",
        "库里",
        "KD",
        "KG",
        "乔丹",
        "邓肯",
        "格林",
        "汤普森",
        "伊戈达拉",
        "麦迪",
        "艾弗森",
        "卡哇伊",
        "保罗"
    ]
];

run.php

run();
总结

完整示例:聊天室

学完后发现生活中所谓的聊天室其实也不过如此,当然这只是简单的demo,很多功能都没有实现,想进一步学习的话可以去github上找完整的项目进行深入学习

参考

swoole

PHP + Swoole 实现的简单聊天室

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

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

相关文章

  • swoole 超简单 构建天室.资辞 群聊,组聊,单聊.

    摘要:今天来做一个简单的聊天室支持换房间支持私信的写的代码有点渣里面有很多不是很好的地方毕竟我只是一个野生程序猿环境地址样子差不多是这个样子的我不想把代码发到我的服务器上因为这个项目太小了很垃圾而且怕被攻击这里有录的一个演示视频没有广告的你们可以 今天来做一个简单的聊天室,支持换房间,支持私信的. 写的代码有点渣,里面有很多不是很好的地方.毕竟我只是一个野生程序猿. 环境: php7.0...

    Towers 评论0 收藏0
  • swoole 服务端120行代码构建一个websocket 天室.

    摘要:的异步并行高性能网络通信引擎,使用纯语言编写,提供了语言的异步多线程服务器,异步网络客户端,异步,异步,数据库连接池,,消息队列,毫秒定时器,异步文件读写,异步查询。内置了服务器端客户端服务器端。 swoole :http://www.swoole.com/PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步M...

    韩冰 评论0 收藏0
  • swoole简单的天室demo(修正版)

    摘要:搜了一下,以前的很多类型文章,都是可能采集的,基本一样,错误都一样,所以自己写了个,发来共享一下。 搜了一下,以前的很多类型文章,都是可能采集的,基本一样,错误都一样,所以自己写了个,发来共享一下。 咱们可是抄袭文档还有互联网的,请确保你的服务器已经安装swoole1.7.7+版本扩展,9502端口未占用而且可以开启 swoole文档:https://wiki.swoole.com/w...

    Sourcelink 评论0 收藏0

发表评论

0条评论

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