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

栏目: PHP · 发布时间: 6年前

内容简介: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);

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

成为乔布斯

成为乔布斯

[美] 布伦特·施兰德、[美] 里克·特策利 / 陶亮 / 中信出版集团 / 2016-10 / 69.00元

本书描绘了一位多姿多彩的人物将与生俱来的激情与成熟的管理方式相结合,打造出史上最有价值、最受消费者追捧的公司,这本书将彻底改变我们看待乔布斯的方式。 本书推翻了关于史蒂夫·乔布斯的传说和陈词滥调,比如他是天才和混蛋的结合体,暴躁易怒、自私自利,怠慢朋友与家人。本书揭示了这位苹果联合创始人和CEO的家庭生活与职业生涯,并回答了一个关键问题:为什么如此轻狂傲慢、以至于被赶出苹果的年轻人能成为史上......一起来看看 《成为乔布斯》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具