WebSocket--php实现

准备工作

  • 1、由于实在 window 环境下,首先需要在系统环境变量里添加 php 运行地址;


    image.png
  • 2、cd 到 socket.php 所在目录下运行 php 命令,如果未成功可能需要安装 php 扩展
image.png

服务器端示例代码

// socket.php
<?php
/**
 * 2019-5-8
 * 为小程序的即时通信提供服务器端的 php 实现 demo
 * websocket 是由客户端发起的长连接:
 * 1、客户端携带 Sec-WebSocket-Key 向服务器发起请求
 * 2、服务器接收到 Sec-WebSocket-Key 后,通过加密算法生成 Sec-WebSocket-Accept 并返回客户端
 * 3、客户端验证 Sec-WebSocket-Accept,通过后双方建立长连接
 */
class WebSocket {
    var $master;
    var $sockets = array(); // 所有连接进来的客户端
    var $users = []; // 所有用户

    function __construct($address, $port){
        // 创建 socket,写法基本固定
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");
        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)    or die("socket_option() failed");
        socket_bind($this->master, $address, $port)                      or die("socket_bind() failed");
        socket_listen($this->master, 20)                                 or die("socket_listen() failed");
        
        $this->sockets[] = $this->master;
        // 循环监听 socket
        while(true)
        {
            $socketArr = $this->sockets;
            $write = NULL;
            $except = NULL;
            // 获取所有 socket 连接
            socket_select($socketArr, $write, $except, NULL);  //自动选择来消息的 socket 如果是握手 自动选择主机
            foreach ($socketArr as $socket)
            {
                // 判断新连接进来的客户端
                if ($socket == $this->master)
                {
                    // 接收新的客户端连接
                    $client = socket_accept($this->master);
                    // 小于 0 时连接失败
                    if ($client < 0){
                        continue;
                    } 
                    else
                    {
                        // 将新的连接放入连接池
                        $this->sockets[] = $client;
                        // 生成唯一 uuid 标记用户
                        $key = uniqid();
                        // echo 'uuid:' . $key;
                        $this->say($key);
                        $this->users[$key] = [
                            'socket'=>$client,  // 记录新连接进来 client 的 socket 信息
                            'hand'=>false       // 判断此个连接是否进行了握手
                        ];
                    }
                }
                else
                {
                    // 从已连接的 socket 接收数据
                    // $buffer 保存客户端提交过来的数据
                    // 2048 数据的最大长度
                    $bytes = socket_recv($socket, $buffer, 2048, 0);
                    $k = $this->search($socket);
                    if ($bytes == 0)
                    {
                        $this->disConnect($socket);
                    }
                    else
                    {
                        if (!$this->users[$k]['hand'])
                        {
                            $this->doHandShake($this->users[$k]['socket'], $buffer);
                        }
                        else
                        {
                            $buffer = $this->decode($buffer);
                            $socket = isset($this->users[$buffer]['socket']) ? $this->users[$buffer]['socket'] : $socket; 
                            $this->send($socket, $buffer);
                        }
                    }
                }
            }
        }
    }
    private function search ($socket){
        foreach ($this->users as $k => $user)
        {
            if ($socket == $user['socket'])
            {
                return $k;
            }
        }
    }
    private function send($client, $msg)
    {
        $msg = $this->encode($msg);
        socket_write($client, $msg, strlen($msg));
    }
    /**
     * 关闭 socket 连接
     */
    private function disConnect($socket)
    {
        // 捕捉错误
        echo socket_strerror(socket_last_error());
        $index = array_search($socket, $this->sockets);
        socket_close($socket);
        if ($index >= 0)
        {
            array_splice($this->sockets, $index, 1); 
        }
    }
    /**
     * 握手,相应客户端的请求,返回 accept
     */
    private 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-Version: 13\r\n" . 
                    "Sec-WebSocket-Accept: " . $this->getAccept($key) . "\r\n\r\n";  //必须以两个回车结尾
        $sent = socket_write($socket, $upgrade, strlen($upgrade));
        $k = $this->search($socket);
        $this->users[$k]['hand'] = true;
        return true;
    }

    /**
     * 获取请求头的 key
     */
    private 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 array($r, $h, $o, $key);
    }

    /**
     * 生成服务器响应的 accept
     */
    private function getAccept($key)
    {
        //基于websocket version 13
        $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        return $accept;
    }

    /**
     * 解析数据帧
     */
    private 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;
    }

    /**
     * 编码数据帧
     */
    private function encode($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;
    }
}
    

new WebSocket('localhost', 4000);

参考文章

小程序代码

Page({
  data:{
    'msg': '',
    'callback': ''
  },
  getMsg: function(e) {
    this.setData({
      msg: e.detail.value
    })
  },
  onLoad:function() {
    wx.connectSocket({
      url: 'ws://localhost:4000/socket.php',
      success: function (res) {
        console.log('success connect')
      },
      complete: function (res) {
        console.log('fail connect')
      }
    })
    wx.onSocketOpen(function (res) {
      console.log('socket已连接')
    })
    
  },
  send: function() {
    var that = this

    wx.sendSocketMessage({
      data: that.data.msg,
      success: function (res) {
        console.log("数据已发给服务器")
      }
    })
    wx.onSocketMessage(function (res) {
      that.setData({
        callback: res.data
      })
    })
  }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 11,131评论 0 10
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 11,713评论 0 17
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,981评论 1 45
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 13,796评论 1 32
  • 今天在手机上看到一篇标题是《如果你越来越沉默,越来越不想说……》的文章,我很喜欢里面这样一句话:“真正成熟...
    三儿青阅读 3,352评论 2 4