PHP 用websocket实现客户端和服务器消息双向推送

PHP 实现websocket

html代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>发送弹幕</title>
  <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-1 col-sm-1 col-md-1 col-lg-1">
      </div>
      <div class="col-xs-10 col-sm-10 col-md-10 col-lg-10">
        <div class="form-group">
          <p style="height:30px"></p>
          <div class="col-sm-2">
          </div>
          <div class="col-sm-4">
            <input type="text" class="form-control" id="barrage" name="barrage" placeholder="弹幕" value="">
          </div>
          <div class="col-sm-4">
            <button type="button" class="btn btn-primary" id="send">发送弹幕</button>
          </div>
        </div>

        <!-- 弹幕内容 -->
        <div class="form-group">
          <p style="height:30px"></p>
          <textarea class="form-control" rows="20" id="content"></textarea>
        </div>
      </div>
      <div class="col-xs-1 col-sm-1 col-md-1 col-lg-1">
      </div>
    </div>
  </div>
</body>
<script>
  $(document).ready(function () {
    var ws = new WebSocket("ws://127.0.0.1:9777");
    ws.onopen = function () {
      console.log("握手成功");
    }
    ws.onmessage = function (e) {
      var content = e.data;
      $('#content').append(content + "\n");
      console.log(content);
    }
    ws.onerror = function () {
      console.log("error");
    }
    $('#send').click(function (e) {
      e.preventDefault();
      var barrage = $('#barrage').val();
      ws.send(barrage);
    });
    $('#barrage').bind('keypress', function (event) {
      if (event.keyCode == "13") {
        var barrage = $('#barrage').val();
        ws.send(barrage);
      }
    });
  });
</script>

</html>

PHP代码

<?php
 
 
class Socket
{
    const BIND_NUM = 20;

    private $master;
    private $sockets = [];
    private $handshake = false; // 握手

    public function __construct($address, $port)
    {
        try {
            // 创建
            $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            // 参数
            socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
            socket_set_nonblock($this->master);
            // 绑定
            socket_bind($this->master, $address, $port);
            // 监听
            socket_listen($this->master, static::BIND_NUM);

            $this->sockets[] = $this->master;


            $pid = posix_getpid();
            // 输出
            $this->say("Server Started : " . date('Y-m-d H:i:s'));
            $this->say("Listening on   : " . $address . " port " . $port);
            $this->say("Pid   : " . $pid);
            $this->say("Master socket  : " . $this->master . PHP_EOL);
        } catch (\Exception $e) {
            $this->error();
        }


        while (true) {
            try {
                // 慢点
                usleep(200000);
                $this->doServer();
            } catch (\Exception $e) {
                $this->error();
            }
        }

    }

    /**
     * 开始服务
     */
    public function doServer()
    {
        $write = $except = NULL;
        socket_select($this->sockets, $write, $except, NULL);  //自动选择来消息的socket 如果是握手 自动选择主机

        foreach ($this->sockets as $socket) {
            // 主机
            if ($this->master == $socket) {
                $client = socket_accept($this->master);
                if ($client < 0) {
                    $this->notice("socket_accept() failed");
                    continue;
                } else {
                    $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);
                        $buffer='server say:'.$buffer;
                        $this->send($socket, $buffer);
                    }
                }
            }
        }
    }

    /**
     * 连接
     *
     * @param $socket
     */
    public function connect($socket)
    {
        array_push($this->sockets, $socket);
        $this->say("\n" . $socket . " CONNECTED!");
        $this->say(date("Y-n-d H:i:s"));
    }

    /**
     * 断开连接
     *
     * @param $socket
     */
    public function disConnect($socket)
    {
        $index = array_search($socket, $this->sockets);
        socket_close($socket);
        $this->say($socket . " DISCONNECTED!");
        if ($index >= 0) {
            array_splice($this->sockets, $index, 1);
        }
    }

    /**
     * 握手
     *
     * @param $socket
     * @param $buffer
     * @return bool
     */
    function doHandShake($socket, $buffer)
    {
        $this->say("\nRequesting handshake...");
        $this->say($buffer);
        $key = '';
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) {
            $key = $match[1];
        }
        $this->say("Handshaking...");
        $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";  //必须以两个回车结尾
        $this->say($upgrade);
        socket_write($socket, $upgrade, strlen($upgrade));
        $this->handshake = true;
        $this->say($key);
        $this->say("Done handshaking...");
        return true;
    }

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

    /**
     * 解密
     *
     * @param $buffer
     * @return null|string
     */
    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;
    }

    /**
     * 发送消息
     *
     * @param $client
     * @param $msg
     */
    function send($client, $msg)
    {
        $this->say("> " . $msg);
        $msg = $this->frame($msg);
        socket_write($client, $msg, strlen($msg));
        $this->say("! " . strlen($msg));
    }

    /**
     * 数据帧
     *
     * @param $s
     * @return string
     */
    function frame($s)
    {
        $a = str_split($s, 125);
        if (count($a) == 1) {
            return "\x81" . chr(strlen($a[0])) . $a[0];
        }
        $ns = "";
        foreach ($a as $o) {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }
        return $ns;
    }

    /**
     * 标准输出
     *
     * @param string $msg
     */
    public function say($msg = "")
    {
        echo $msg . PHP_EOL;
    }

    /**
     * 异常错误输出
     */
    public function error()
    {
        $error = socket_last_error();
        $error_msg = socket_strerror($error);
        echo $error_msg . PHP_EOL;
    }

    /**
     * 普通错误输出
     *
     * @param string $notice
     */
    public function notice($notice = "")
    {
        echo $notice . PHP_EOL;
    }


}

new Socket('127.0.0.1', 9777);

 

 

PHP 用websocket实现客户端和服务器消息双向推送
标签:             

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*