PHP websocket 应用

因为公司有这个需求,记录一下

创建两个数组来记录连接的客户端以及握手状态

    private $master = null; //服务端
    private $connectPool = []; //客户端连接池
    private $handShakePool = []; //握手连接池

运行服务的方法

public function startServer($ip, $port) {
        $this->connectPool[] = $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);//创建服务端
        socket_bind($this->master, $ip, $port);//绑定服务端ip,端口
        socket_listen($this->master, 1000);//监听,最多同时监听1000个客户端
        while(true) {
            $sockets = $this->connectPool;
            $write = $excpet = null;
            socket_select($sockets, $write, $excpet, 60);//阻塞模式

            foreach($sockets as $socket) {//监听消息
                if($socket == $this->master) {//添加新客户端
                    $this->connectPool[] = $client = socket_accept($this->master);
                    $keyArr = array_keys($this->connectPool, $client);
                    $key = end($keyArr);
                    $this->handShakePool[$key] = false;
                } else {
                    $length = socket_recv($socket, $buffer, 1024, 0);
                    if($length<1) {//当长度小于7,说明当前客户端连接已断开
                        $this->close($socket);//服务端关闭断开连接的客户端
                    } else {
                        $key = array_search($this->master, $this->connectPool);
                        if($this->handShakePool[$key] == false) {//进行握手
                            $this->handShake($socket, $buffer, $key);
                        } else {//发送消息,可根据具体业务进行调整
                            $msg = $this->deFrame($buffer);
                            $msg = $this->enFrame($msg);
                            $this->send($socket, $msg);
                        }
                    }
                }
            }
        }
    }

以下是某些必需的操作

关闭连接

// 客户端断开连接
    public function close($socket) {
        $key = array_search($socket, $this->connectPool);
        unset($this->connectPool[$key]);
        unset($this->handShakePool[$key]);
        socket_close($socket);
        echo("有客户端断开连接\n");
    }

握手操作

//握手
    public function handShake($socket ,$buffer, $key) {
        if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) {
            $responseKey = base64_encode(sha1($match[1].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
            $upgrade = "HTTP/1.1 101 Switching Protocol\r\n".
                        "Upgrade: websocket\r\n".
                        "Connection: Upgrade\r\n".
                        "Sec-WebSocket-Accept: ".$responseKey."\r\n\r\n";
            socket_write($socket, $upgrade, strlen($upgrade));
            $this->handShakePool[$key] = true;
        }
        echo("新建握手,当前客户端数量=>".(sizeof($this->handShakePool)-1)."\n");
    }

数据的加密与解密,以及发送信息

//数据封帧
    public function enFrame($msg) {
        $len = strlen($msg);
        if($len<=125) {
            return "\x81".chr($len).$msg;
        } else if($len <= 65535) {
            return "\x81".chr(126).pack("n",$len).$msg;
        } else {
            return "\x81".chr(127).pack("xxxxN",$len).$msg;
        }
    }

    //数据解帧
    public function deFrame($buffer) {
        $len = $masks = $data = $decode = 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++) {
            $decode .= $data[$index]^$masks[$index*4];
        }
        return $decode;
    }

    //发送信息
    public function send($socket, $msg) {
        socket_write($socket, $msg);
        echo("给{$socket}发送{$msg}\n");
    }

写完以上代码,运行 startServer 方法即可

本作品采用《CC 协议》,转载必须注明作者和本文链接
如有错误,欢迎大佬指点
讨论数量: 5

单进程非阻塞的 select 模型,偏内存消耗,默认最大连接数 1024
socket_listen 后可以指定为非阻塞

socket_set_nonblock($this->master)

connectPoolkey 值其实可以

$client = socket_accept($this->master);
$this->connectPool[(int)$client]=$client;//(int)$client即fd描述符

关闭时

unset($this->connectPool[(int)$sock]);

这里 false 是出错,但也有可能长度是0吧,我也不确定

if($length<1){..}

是否可以这样

if ($length === false || !is_resource($socket) || feof($socket)){
   $this->close($socket);
}

PHP

2年前 评论
无知的小王 (楼主) 2年前
php_yt (作者) 2年前

swoole 他不香么?

2年前 评论
无知的小王 (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!