WebSocket--php實(shí)現(xiàn)

準(zhǔn)備工作

  • 1、由于實(shí)在 window 環(huán)境下,首先需要在系統(tǒng)環(huán)境變量里添加 php 運(yùn)行地址;


    image.png
  • 2、cd 到 socket.php 所在目錄下運(yùn)行 php 命令,如果未成功可能需要安裝 php 擴(kuò)展
image.png

服務(wù)器端示例代碼

// socket.php
<?php
/**
 * 2019-5-8
 * 為小程序的即時(shí)通信提供服務(wù)器端的 php 實(shí)現(xiàn) demo
 * websocket 是由客戶端發(fā)起的長連接:
 * 1、客戶端攜帶 Sec-WebSocket-Key 向服務(wù)器發(fā)起請求
 * 2、服務(wù)器接收到 Sec-WebSocket-Key 后,通過加密算法生成 Sec-WebSocket-Accept 并返回客戶端
 * 3、客戶端驗(yàn)證 Sec-WebSocket-Accept,通過后雙方建立長連接
 */
class WebSocket {
    var $master;
    var $sockets = array(); // 所有連接進(jìn)來的客戶端
    var $users = []; // 所有用戶

    function __construct($address, $port){
        // 創(chuàng)建 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;
        // 循環(huán)監(jiān)聽 socket
        while(true)
        {
            $socketArr = $this->sockets;
            $write = NULL;
            $except = NULL;
            // 獲取所有 socket 連接
            socket_select($socketArr, $write, $except, NULL);  //自動選擇來消息的 socket 如果是握手 自動選擇主機(jī)
            foreach ($socketArr as $socket)
            {
                // 判斷新連接進(jìn)來的客戶端
                if ($socket == $this->master)
                {
                    // 接收新的客戶端連接
                    $client = socket_accept($this->master);
                    // 小于 0 時(shí)連接失敗
                    if ($client < 0){
                        continue;
                    } 
                    else
                    {
                        // 將新的連接放入連接池
                        $this->sockets[] = $client;
                        // 生成唯一 uuid 標(biāo)記用戶
                        $key = uniqid();
                        // echo 'uuid:' . $key;
                        $this->say($key);
                        $this->users[$key] = [
                            'socket'=>$client,  // 記錄新連接進(jìn)來 client 的 socket 信息
                            'hand'=>false       // 判斷此個(gè)連接是否進(jìn)行了握手
                        ];
                    }
                }
                else
                {
                    // 從已連接的 socket 接收數(shù)據(jù)
                    // $buffer 保存客戶端提交過來的數(shù)據(jù)
                    // 2048 數(shù)據(jù)的最大長度
                    $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));
    }
    /**
     * 關(guān)閉 socket 連接
     */
    private function disConnect($socket)
    {
        // 捕捉錯(cuò)誤
        echo socket_strerror(socket_last_error());
        $index = array_search($socket, $this->sockets);
        socket_close($socket);
        if ($index >= 0)
        {
            array_splice($this->sockets, $index, 1); 
        }
    }
    /**
     * 握手,相應(yīng)客戶端的請求,返回 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";  //必須以兩個(gè)回車結(jié)尾
        $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);
    }

    /**
     * 生成服務(wù)器響應(yīng)的 accept
     */
    private function getAccept($key)
    {
        //基于websocket version 13
        $accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        return $accept;
    }

    /**
     * 解析數(shù)據(jù)幀
     */
    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;
    }

    /**
     * 編碼數(shù)據(jù)幀
     */
    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("數(shù)據(jù)已發(fā)給服務(wù)器")
      }
    })
    wx.onSocketMessage(function (res) {
      that.setData({
        callback: res.data
      })
    })
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡介】: Python 是一個(gè)...
    _小老虎_閱讀 6,335評論 0 10
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,334評論 0 17
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,817評論 1 45
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,662評論 1 32
  • 今天在手機(jī)上看到一篇標(biāo)題是《如果你越來越沉默,越來越不想說……》的文章,我很喜歡里面這樣一句話:“真正成熟...
    三兒青閱讀 549評論 2 4

友情鏈接更多精彩內(nèi)容