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
      })
    })
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

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