swoole+redis实现简单聊天室

实现一对一和一对多即时通讯,能查看聊天记录以及离线留言,新消息提醒。

Redis 实现每个连接websocket的服务都唯一绑定一个用户。通过 用户账号 = websocket fd 存到redis中。
将离线消息存在 Redis 队列里,由于是简单实现效果,没有使用数据库存数据,聊天记录暂时放在Local Storage里面,代码如下:

服务端:

<?php
use Redis;

class WebSocket extends Base
{
    #redis key
    protected static $redisKey = 'swoole_fd';
    protected static $historyKey = 'swoole_history:';
    # 过期时间
    protected static $historyKeyExpire = 3600*24;
    # redis
    protected static $redis;

    public function __construct()
    {
        # 初始化redis
        self::$redis = new Redis();  
        self::$redis->connect('120.0.0.1',6379);
        self::$redis->del(self::$redisKey);
    }
    public function handle()
    {
        try {

            # 开启swoole 
            $ws_server = new \swoole_websocket_server('0.0.0.0', 8888);

            $ws_server->set(array(
                'worker_num' => 8,
                'daemonize' => false,
                'max_request' => 10000,
                'dispatch_mode' => 2,
                'debug_mode' => 1
            ));

            # 监听WebSocket连接打开事件
            $ws_server->on('Open', array($this, 'onOpen'));
            # 监听WebSocket消息事件,onMessage回调必须被设置,未设置服务器将无法启动
            $ws_server->on('Message', array($this, 'onMessage'));
            # 监听WebSocket连接关闭事件
            $ws_server->on('Close', array($this, 'onClose'));

            $ws_server->start();

        } catch (\Exception $e) {

        }
    }

    public function onOpen($server, $req)
    {
        echo '已连接'.$req->fd . PHP_EOL;
    }

    public function onMessage($server, $frame)
    {
        $data = json_decode($frame->data,true);
        if(isset($data['type']) && $data['type'] == 'init')
        {   
            # 绑定fd
            self::$redis->hset(self::$redisKey,$data['id'], $frame->fd);

            # 获取离线的消息
            $historyList = self::$redis->lrange(self::$historyKey.$data['id'],0,-1);
            if($historyList){
                self::$redis->ltrim(self::$historyKey.$data['id'],count($historyList),-1);//删除取出的数据
                $out['type'] = 'list';
                $out['data'] = $historyList;
                $server->push($frame->fd, json_encode($out,256));
            }

        }elseif($data['to']['type'] == 'friend')
        {
            # 私聊
            $fd = self::$redis->hget(self::$redisKey,$data['to']['id']);

            if($fd){
                $server->push($fd,$frame->data);
            }else{
                # 添加消息队列
                self::$redis->rpush(self::$historyKey.$data['to']['id'],$frame->data);
                self::$redis->expire(self::$historyKey.$data['to']['id'],self::$historyKeyExpire);
            }

        }elseif($data['to']['type'] == 'group')
        {
            # 群聊
            $fds = self::$redis->hgetall(self::$redisKey);

            foreach ($fds as $k => $fd){
                if($data['mine']['id'] != $k){
                    $server->push($fd,$frame->data);
                }
            }
        }
    }

    public function onClose($server, $fd)
    {
        echo "关闭: " . $fd . PHP_EOL;
        $fds = self::$redis->hgetall(self::$redisKey);
        $fd = array_search($fd, $fds);
        self::$redis->hDel(self::$redisKey,$fd);
        self::out($fd . PHP_EOL);
    }

}

客户端:

<script>
    var webSocket = new WebSocket("ws://0.0.0.0:8888");

    webSocket.onopen = function (event) {
        console.log('onopen');
        //getLocal 生成随机数作为身份标识,存入Local Storage
        var uidRst = getLocal('身份标识');
        if(uidRst){
            var e = {
                'type':'init',
                'id':uidRst,
            };
            webSocket.send(JSON.stringify(e));
            return;
        }
        alert('uid 获取失败');
        setTimeout(function() {
            location.reload();
        }, 1000);

    },
    webSocket.onmessage = function (event) {

        var rst = JSON.parse(event.data);
        if(rst && rst.code === -1){
            // 失败 或者 登陆超时
            alert(rst.msg);return;

        }else if(rst && rst.type == 'list'){
            // 离线消息
            var list = rst.data;
            $(list).each(function(i,j){
                var e = JSON.parse(j);
                e = formaMsg(e);
                t.getMessage(e); 
            })

        }else if(rst && rst.to && rst.to.type){
            var e = formaMsg(rst);
            t.getMessage(e);
        }


    },
    formaMsg = function(rst){
        var e = {};
        if(rst.to.type == 'friend'){
            // 私聊
            e =  {
                username: rst.mine.username,
                avatar: rst.mine.avatar,
                id: rst.mine.id,
                type: rst.to.type,
                content: rst.mine.content
            };
        }else if(rst.to.type == 'group'){
            // 群聊
            e =  {
                username: rst.mine.username,
                avatar: rst.mine.avatar,
                id: rst.to.id,
                type: rst.to.type,
                content: rst.mine.content
            };
        }
        return e;
    }
<script>

演示地址:

注意: 同一台电脑上聊天体验,需要打开不同的浏览器进行通讯,因为同一浏览器只生成一个身份标识。
http://www.itselfstudy.cn/me/test

mac 安装

1:下载swoole源码,https://github.com/swoole/swoole-src/releases
2:tar -zxvf swoole.tgz
3:进入解压目录,输入:/usr/local/php/bin/phpize
4:./confiure —with-php-config=/usr/local/php/bin/php-config
5:make && make install
6:修改php.ini,extension=swoole.so

1000

GS

北京 | php攻城狮

创作 35 粉丝 2

fighting