用php socket+ websocket实现了一个小型的聊天室

<?php
/**
 * author: NickBai
 * createTime: 2016/12/9 0009 下午 4:17
 */
namespace NickBai;

class SocketChat
{
    private $timeout = 60;  //超时时间
    private $handShake = False; //默认未牵手
    private $master = 1;  //主进程
    private $port = 2000;  //监听端口
    private static $connectPool = [];  //连接池
    private static $maxConnectNum = 1024; //最大连接数
    private static $chatUser = [];  //参与聊天的用户


    public function __construct( $port = 0 )
    {
        !empty( $port ) && $this->port = $port;
        $this->startServer();
    }

    //开始服务器
    public function startServer()
    {
        $this->master = socket_create_listen( $this->port );
        if( !$this->master ) throw new \Exception('listen $this->port fail !');

        $this->runLog("Server Started : ".date('Y-m-d H:i:s'));
        $this->runLog("Listening on   : 127.0.0.1 port " . $this->port);
        $this->runLog("Master socket  : ".$this->master."\n");

        self::$connectPool[] = $this->master;

        while( true ){
            $readFds = self::$connectPool;
            //阻塞接收客户端链接
            @socket_select( $readFds, $writeFds, $e = null, $this->timeout );

            foreach( $readFds as $socket ){
                //当前链接 是主进程
                if( $this->master == $socket ){

                    $client = socket_accept( $this->master );  //接收新的链接
                    $this->handShake = False;

                    if ($client < 0){
                        $this->log('clinet connect false!');
                        continue;
                    } else{
                        //超过最大连接数
                        if( count( self::$connectPool ) > self::$maxConnectNum )
                            continue;

                        //加入连接池
                        $this->connect( $client );
                    }

                }else{
                    //不是主进程,开始接收数据
                    $bytes = @socket_recv($socket, $buffer, 2048, 0);
                    //未读取到数据
                    if( $bytes == 0 ){
                        $this->disConnect( $socket );
                    }else{
                        //未握手 先握手
                        if( !$this->handShake ){

                            $this->doHandShake( $socket, $buffer );
                        }else{

                            //如果是已经握完手的数据,广播其发送的消息
                            $buffer = $this->decode( $buffer );
                            $this->parseMessage( $buffer, $socket );
                        }
                    }

                }
            }

        }
    }

    //解析发送的数据
    public function parseMessage( $message, $socket )
    {
        //msg type  1 初始化  2 通知  3 一般聊天  4 断开链接  5 获取在线用户 6 通知下线
        $message = json_decode( $message, true );
        switch( $message['type'] ){

            case 1:
                $this->bind( $socket, $message );
                //通知其他客户端,当前用户上线
                $msg = [
                    'type' => "2",
                    'msg' => 'online',
                    'avar' => $message['avar']
                ];
                $this->sendToAll( $socket,  $msg );
                //更新在线用户
                $this->freshOnlineUser();

                break;
            case 3:
                $this->sendToAll( $socket, $message );
                break;
            case 4:
                //通知用户离线
                $msgOutline = [
                    'type' => '6',
                    'user' => self::$chatUser[(int)$socket]['user']
                ];
                $this->tellOnlineInfo( $msgOutline );
                //断开 要离线的用户
                $this->disConnect( $socket );
                //更新在线用户
                $this->freshOnlineUser();

                break;
            default:
                break;
        }
    }

    //用户--链接 绑定
    public function bind( $socket, $user )
    {
        self::$chatUser[(int) $socket] = [
            'user' => $user['user'],
            'avar' => $user['avar']
        ];
    }

    //用户--链接 解绑
    public function unBind( $socket )
    {
        unset( self::$chatUser[(int) $socket] );
    }

    //获取在线用户
    public function getOnlineUser()
    {
        return self::$chatUser;
    }

    //更新在线用户
    public function freshOnlineUser()
    {
        $msgOnlie = [
            'type' => "5",
            'msg' => 'online user',
            'info' => self::$chatUser
        ];
        $this->tellOnlineInfo( $msgOnlie );
    }

    //广播所有的客户端(排除自己和master)
    public function sendToAll( $client, $mess )
    {
        //拼装发送者的名称
        $mess['user'] = self::$chatUser[(int) $client]['user'];
        $mess['stime'] = date('Y-m-d H:i:s');

        foreach( self::$connectPool as $socket ){
            if( $socket != $this->master && $socket != $client  ){
                $this->send( $socket, $mess );
            }
        }
    }

    //广播客户端在线用户信息
    public function tellOnlineInfo( $mess )
    {
        foreach( self::$connectPool as $socket ){
            if( $socket != $this->master ){
                $this->send( $socket, $mess );
            }
        }
    }

    //处理发送信息
   public function send( $client, $msg )
    {
        $msg = $this->frame( json_encode( $msg ) );
        socket_write( $client, $msg, strlen($msg) );
    }

    //握手协议
    function doHandShake($socket, $buffer)
    {
        list($resource, $host, $origin, $key) = $this->getHeaders($buffer);
        $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept: " . $this->calcKey($key) . "\r\n\r\n";  //必须以两个回车结尾

        socket_write($socket, $upgrade, strlen($upgrade));
        $this->handShake = true;
        return true;
    }

    //获取请求头
    function getHeaders( $req )
    {
        $r = $h = $o = $key = null;
        if (preg_match("/GET (.*) HTTP/"              , $req, $match)) { $r = $match[1]; }
        if (preg_match("/Host: (.*)\r\n/"             , $req, $match)) { $h = $match[1]; }
        if (preg_match("/Origin: (.*)\r\n/"           , $req, $match)) { $o = $match[1]; }
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) { $key = $match[1]; }
        return [$r, $h, $o, $key];
    }

    //验证socket
    function calcKey( $key )
    {
        //基于websocket version 13
        $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        return $accept;
    }


    //打包函数 返回帧处理
    public function frame( $buffer )
    {
        $len = strlen($buffer);
        if ($len <= 125) {

            return "\x81" . chr($len) . $buffer;
        } else if ($len <= 65535) {

            return "\x81" . chr(126) . pack("n", $len) . $buffer;
        } else {

            return "\x81" . char(127) . pack("xxxxN", $len) . $buffer;
        }
    }

    //解码 解析数据帧
    function decode( $buffer )
    {
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;

        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        }
        else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        }
        else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return $decoded;
    }

    //客户端链接处理函数
    function connect( $socket )
    {
        array_push( self::$connectPool, $socket );
        $this->runLog("\n" . $socket . " CONNECTED!");
        $this->runLog(date("Y-n-d H:i:s"));
    }

    //客户端断开链接函数
    function disConnect( $socket )
    {
        $index = array_search( $socket, self::$connectPool );
        socket_close( $socket );

        $this->unBind( $socket );
        $this->runLog( $socket . " DISCONNECTED!" );
        if ($index >= 0){
            array_splice( self::$connectPool, $index, 1 );
        }
    }

    //打印运行信息
    public function runLog( $mess = '' )
    {
        echo $mess . PHP_EOL;
    }

    //系统日志
    public function log( $mess = '' )
    {
        @file_put_contents( './' . date("Y-m-d") . ".log", date('Y-m-d H:i:s') . "  " . $mess . PHP_EOL, FILE_APPEND );
    }
}
项目已经放入 本人github,需要了解的请 关注 : https://github.com/nick-bai/HappyChat
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值