使用PHP7 + Swoole + Redis开发简单的在线聊天室

Author Avatar
Harris Wong 2017年08月29日

Swoole官方是这样定义Swoole的:

PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。

除了异步IO的支持之外,Swoole为PHP多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大简化多进程并发编程的工作。其中包括了并发原子计数器,并发HashTable,Channel,Lock,进程间通信IPC等丰富的功能特性。

Swoole2.0支持了类似Go语言的协程,可以使用完全同步的代码实现异步程序。PHP代码无需额外增加任何关键词,底层自动进行协程调度,实现异步。

Swoole可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用PHP+Swoole作为网络通信框架,可以使企业IT研发团队的效率大大提升,更加专注于开发创新产品。

准备工作

Swoole环境依赖

  • 仅支持Linux,FreeBSD,MacOS,3类操作系统
  • Linux内核版本2.3.32以上
  • PHP-5.3.10以上版本,包括PHP7
  • gcc4.4以上版本或者clang
  • cmake2.4+,编译为libswoole.so作为C/C++库时需要使用cmake

不满足以上要求请升级或安装。

安装相应版本的Redis

安装Swoole

官方给出了两种安装方法,一是通过pecl安装,Swoole已经收入PHP官方扩展库。二是通过下载源码安装。这里使用的是第二种方法。

依次执行下列命令

wget https://github.com/swoole/swoole-src/archive/v1.9.18.tar.gz

tar xvzf v1.9.18.tar.gz

cd swoole-src-1.9.18

phpize 
//若提示not found 用 find / -name phpize 查找phpize的位置 然后将路径加上
//如 /www/wdlinux/phps/71/bin/phpize
./configure --with-php-config=/www/wdlinux/phps/71/bin/php-config   
//php-config 可用find的查找
make 
sudo make install

至此安装完成,然后在php.ini里加入swoole。

extension=swoole.so

phpinfo();或用 php -m查看是否安装成功。

使用Swoole开发聊天室

server.php

require_once '/www/web/o11o/public_html/chat/config/config.php';
$ws =  new  hans\config('0.0.0.0',9503);

$ws->set(array(
    'daemonize' => false,      // 是否是守护进程
    'max_request' => 10000,    // 最大连接数量
    'dispatch_mode' => 2,
    'debug_mode'=> 1,
    // 心跳检测的设置,自动踢掉掉线的fd
    'heartbeat_check_interval' => 5,
    'heartbeat_idle_time' => 600,
));
$redis = null;
$ws->on('WorkerStart', function ($ws, $worker_id) {
    global $redis;
    $redis = new \Redis();
    $redis->connect("127.0.0.1", 6379) || die("redis 连接失败");
    echo "进程{$worker_id}的redis 连接成功!\n";
});

$ws->on('open', function ($ws, $request) {
    global $redis;
    $ws->open($redis,$request);
});

$ws->on('message', function ($ws, $frame) {
    global $redis;
    $ws->send_message($redis, $frame);
});
$ws->on('close',function($ws,$fd){
    global $redis;
    $ws->user_close($redis,$fd);
});

$ws->start();

config.php

namespace hans;

class config extends \swoole_websocket_server{

    const SENDTOALL='SENDALL';

    const CLOSETOALLUSER='ALL';

    public function open(\Redis $redis,$frame){
        $name=$frame->get['name'];
        $redis->hMset($frame->fd,["name" => $name, "ip" => $frame->server['remote_addr']]);


        foreach($this->connections as $fd){
            $result[]=$redis->hGetAll($fd);
        }
        foreach($result as $key =>$users){
            if(count($users)>0){
                $user_list[$key]=$users;
            }
        }
        $user_info['user_list']=$user_list;
        $user_info['type']=0;
        $user_info['msg']=$name."进入了房间";
        $msg=json_encode($user_info);
        foreach($this->connections as $fd){
            $this->push($fd,$msg);
        }
    }
    public function send_message(\Redis $redis,$frame,$messgae_type=self::SENDTOALL){
        switch($messgae_type){
            case self::CLOSETOALLUSER:

                foreach($this->connections  as  $userid){
                    $user[]=$redis->hGetAll($userid);
                }
                $reslt['type']=1;
                $reslt['msg']=$frame.'离开了';
                foreach($user as $key =>$users){
                    if(count($users)>0){
                        $user_list[$key]=$users;
                    }
                }
                $reslt['user_list']=$user_list;
                var_dump($reslt);
                $msg=json_encode($reslt);
                foreach($this->connections as $fd){
                    $this->push($fd,$msg);
                }
            break;
            case self::SENDTOALL:
                $user_info=$redis->hGetAll($frame->fd);
                $send_msg = "{$user_info['name']}:{$frame->data}\n";
                $reslt['type']=2;
                $reslt['msg']=$send_msg;
                $msg=json_encode($reslt);
                var_dump($reslt);
                foreach($this->connections as $fd){
                    $this->push($fd,$msg);
                }
        }
    }

    public function user_close(\Redis $redis,$fd){

        echo $fd.'用户断开连接';
        $userinfo=$redis->hGetAll($fd);

        $redis->hDel($fd,'name','ip');

        $this->send_message($redis,$userinfo["name"],self::CLOSETOALLUSER);

        $redis->hDel($fd,'name','ip');

        $this->close($fd);
    }

}
        

room.php
js连接socket关键部分

<script type="text/javascript">
    var  $$ = mdui.JQ;
    if(window.WebSocket){
        <?php if($_POST){$username=$_POST['username'];}else{exit('非法访问');}?>
        var webSocket = new WebSocket("ws://你的ip地址:9503?name=<?php echo $username; ?>");
        webSocket.onopen = function (event) {

        };
        webSocket.onmessage = function (event) {
            var content = document.getElementById('content');
            var obj=JSON.parse(event.data);
            if(obj.type==0){
                var html="";
                console.log(obj);
                for(var i in obj.user_list){
                    html+=' <li class="mdui-list-item mdui-ripple">'+
                        '<div class="mdui-list-item-avatar"><i class="mdui-icon material-icons mdui-icon-dark"></i></div>'+
                        '<div class="mdui-list-item-content">'+obj.user_list[i].name+'</div>'+
                        '</li>'
                }
                $$('#user_list').empty();
              $$('#user_list').append(html);
                content.innerHTML = content.innerHTML.concat('<p style="margin-left:20px;height:20px;line-height:20px;">'+obj.msg+'</p>');

            }else if(obj.type==1){
                var html="";
                console.log(obj);
                for(var i in obj.user_list){
                    html+=' <li class="mdui-list-item mdui-ripple">'+
                        '<div class="mdui-list-item-avatar"><i class="mdui-icon material-icons mdui-icon-dark"></i></div>'+
                        '<div class="mdui-list-item-content">'+obj.user_list[i].name+'</div>'+
                        '</li>'
                }
                $$('#user_list').empty();
                $$('#user_list').append(html);
                content.innerHTML = content.innerHTML.concat('<p style="margin-left:20px;height:20px;line-height:20px;">'+obj.msg+'</p>');
            } if(obj.type==2){
                console.log(obj);
                content.innerHTML = content.innerHTML.concat('<p style="margin-left:20px;height:20px;line-height:20px;">'+obj.msg+'</p>');
            }

        }

        var sendMessage = function(){
            var data = document.getElementById('message').value;
            if(data.trim()==""){
                mdui.alert('不能发送空消息','错误');
                return false;
            }
            webSocket.send(data);
            $$('#message').val('');
        }


    }else{
        console.log("您的浏览器不支持WebSocket");
    }
</script>

cli模式下 运行 server.php 保证服务开启。

进入room.php就可以聊天啦。

效果截图:
写下用户名,区分用户。

Index

聊天界面。

room

源码地址