Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Siri PHP+Socket untuk melaksanakan bilik sembang websocket

Siri PHP+Socket untuk melaksanakan bilik sembang websocket

藏色散人
藏色散人ke hadapan
2023-02-02 16:39:514088semak imbas

Artikel ini membawakan anda pengetahuan yang berkaitan tentang php+socket Ia terutamanya memperkenalkan cara menggunakan soket asli php untuk melaksanakan bilik sembang web yang ringkas. Rakan-rakan yang berminat boleh lihat di bawah ini semoga bermanfaat untuk semua.

Soket asli PHP melaksanakan bilik sembang websocket

Kata Pengantar

Artikel ini melaksanakan bilik sembang web ringkas menggunakan soket asli PHP bahagian bawah artikel.

Jika tidak ada yang lain, ini sepatutnya menjadi artikel terakhir dalam siri artikel ini Apabila saya menulis artikel bersiri ini, saya fikir ia akan menjadi perkara yang sangat mudah, tetapi selepas menulis beberapa artikel ini, saya hampir membaca. melalui kod Workerman. Jadi jangan terlalu bercita-cita tinggi dan mempunyai cita-cita yang terlalu kecil Anda mesti mencubanya sendiri. Adalah lebih baik untuk menulisnya untuk membuktikan bahawa anda benar-benar memahami sesuatu

Pengenalan kepada websocket<.>

protokol webSocket Ia adalah protokol komunikasi rangkaian yang dilahirkan pada tahun 2008 dan menjadi standard antarabangsa pada tahun 2011.

RFC6455 mentakrifkan standard komunikasinya. webSocket ialah protokol untuk komunikasi dupleks penuh pada sambungan TCP tunggal yang HTML5 mula sediakan Pelayan boleh menolak mesej secara aktif kepada klien, dan klien juga boleh menghantar mesej secara aktif kepada pelayan. WebSocket menetapkan spesifikasi protokol komunikasi Melalui mekanisme jabat tangan, sambungan seperti tcp boleh diwujudkan antara klien (pelayar) dan pelayan (pelayan web), dengan itu memudahkan komunikasi cs.

Mengapa websocket diperlukan

Protokol HTTP ialah protokol lapisan aplikasi tanpa kewarganegaraan, tanpa sambungan, sehala. Ia menggunakan model

Permintaan komunikasi hanya boleh dimulakan oleh pelanggan, dan pelayan bertindak balas kepada permintaan Model komunikasi ini mempunyai kelemahan: adalah mustahil bagi pelayan untuk memulakan mesej secara aktif kepada pelanggan. Keupayaan serentak permintaan HTTP tradisional dicapai dengan memulakan berbilang sambungan TCP untuk mengakses pelayan pada masa yang sama membolehkan kami mengeluarkan berbilang permintaan pada sambungan ws secara serentak, iaitu, selepas permintaan A dihantar belum tiba lagi. Anda boleh terus mengeluarkan permintaan B. Disebabkan ciri permulaan TCP yang perlahan dan kehilangan sambungan itu sendiri, ciri protokol soket web ini telah meningkatkan kecekapan dengan banyak. 请求 => 响应

Siri PHP+Socket untuk melaksanakan bilik sembang websocket

Ciri

  • Dibina pada protokol TCP, bahagian pelayan agak mudah untuk dilaksanakan

  • Ia mempunyai keserasian yang baik dengan protokol HTTP Port lalai juga adalah 80 dan 443, dan protokol HTTP digunakan dalam fasa jabat tangan, jadi ia tidak mudah disekat semasa berjabat tangan dan boleh melepasi pelbagai pelayan proksi HTTP.

  • Format data agak ringan, dengan overhed prestasi rendah dan komunikasi yang cekap.

  • Anda boleh menghantar teks atau data binari.

  • Tiada sekatan asal dan pelanggan boleh berkomunikasi dengan mana-mana pelayan.

  • Pengecam protokol ialah ws (atau wss jika disulitkan) dan alamat perkhidmatan ialah URL.

PHP melaksanakan soket web

Jabatan tangan pelanggan dan pelayan

Protokol websocket memerlukan jabat tangan sebelum sambungan [^2] , biasanya terdapat kaedah jabat tangan berikut:

  • Protokol jabat tangan berasaskan kilat (tidak disyorkan)

  • Protokol jabat tangan berasaskan penyulitan MD5

    Kaedah jabat tangan yang terdahulu, dengan dua kekunci, menggunakan penyulitan md5

  • Protokol jabat tangan berdasarkan kaedah penyulitan sha1

    Protokol jabat tangan utama semasa, Artikel ini akan memfokuskan pada protokol ini

    • untuk mendapatkan

      Sec-WebSocket-key

    • penyambungan

      + key258EAFA5-E914-47DA-95CA-C5AB0DC85B11 yang dilaporkan oleh pelanggan

    • melakukan pengiraan

      pada rentetan, kemudian menyulitkan hasil melalui SHA1, dan akhirnya mengembalikannya kepada klien base64

Maklumat permintaan pelanggan adalah seperti berikut:

GET /chat HTTP/1.1Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Pelanggan perlu mengembalikan data berikut:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Version: 13Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Kami melaksanakannya melalui PHP mengikut protokol ini:

<?php

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, true);
socket_bind($socket, 0, 8888);
socket_listen($socket);

while (true) {
    $conn_sock = socket_accept($socket);
    $request = socket_read($conn_sock, 102400);

    $new_key = getShaKey($request);

    $response = "HTTP/1.1 101 Switching Protocols\r\n";
    $response .= "Upgrade: websocket\r\n";
    $response .= "Sec-WebSocket-Version: 13\r\n";
    $response .= "Connection: Upgrade\r\n";
    $response .= "Sec-WebSocket-Accept: {$new_key}\r\n\r\n";

    socket_write($conn_sock, $response);
}

function getShaKey($request)
{
    // 获取 Sec-WebSocket-key
    preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $match);

    // 拼接 key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    $new_key = trim($match[1]) . &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;;

    // 对字符串做 `SHA1` 计算,再把得到的结果通过 `base64` 加密
    return base64_encode(sha1($new_key, true));
}
Penjelasan tatabahasa yang berkaitan boleh didapati dalam

artikel terdahulu Artikel ini tidak akan memperkenalkannya secara terperinci.

Gunakan ujian bahagian hadapan, buka mana-mana konsol penyemak imbas kami (konsol) dan masukkan kandungan berikut Keadaan sedia bagi objek soket web yang dikembalikan ialah 1, yang bermaksud jabat tangan ini berjaya. kandungan akhir. Artikel ini tidak akan memperkenalkannya secara terperinci Anda boleh merujuk kepada

Tutorial Rookie:

console.log(new WebSocket('ws://192.162.2.166:8888'));
// 运行后返回:
WebSocket {
    binaryType: "blob"
    bufferedAmount: 0
    extensions: ""
    onclose: null
    onerror: null
    onmessage: null
    onopen: null
    protocol: ""
    readyState: 1
    url: "ws://192.162.2.166:8888/"}

Menghantar dan Menerima Data

使用 websocket 协议传输协议需要遵循特定的格式规范,详情请参考 datatracker.ietf.org/doc/html/rfc6...

Siri PHP+Socket untuk melaksanakan bilik sembang websocket

为了方便,这里直接贴出加解密代码,以下代码借鉴与 workermansrc/Protocols/Websocket.php 文件:

// 解码客户端发送的消息
function decode($buffer)
{
    $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);
        }
    }
    $dataLength = \strlen($data);
    $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
    return $data ^ $masks;
}

// 编码发送给客户端的消息
function encode($buffer)
{
    if (!is_scalar($buffer)) {
        throw new \Exception("You can&#39;t send(" . \gettype($buffer) . ") to client, you need to convert it to a string. ");
    }
    $len = \strlen($buffer);

    $first_byte = "\x81";

    if ($len <= 125) {
        $encode_buffer = $first_byte . \chr($len) . $buffer;
    } else {
        if ($len <= 65535) {
            $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer;
        } else {
            $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer;
        }
    }

    return $encode_buffer;
}

我们修改刚才 客户端与服务端握手 阶段的代码,修改后全代码全文如下,该段代码实现了将客户端发送的消息转为大写后返回给客户端(当然只是为了演示):

<?php

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, true);
socket_bind($socket, 0, 8888);
socket_listen($socket);

while (true) {
    $conn_sock = socket_accept($socket);
    $request = socket_read($conn_sock, 102400);

    $new_key = getShaKey($request);

    $response = "HTTP/1.1 101 Switching Protocols\r\n";
    $response .= "Upgrade: websocket\r\n";
    $response .= "Sec-WebSocket-Version: 13\r\n";
    $response .= "Connection: Upgrade\r\n";
    $response .= "Sec-WebSocket-Accept: {$new_key}\r\n\r\n";

    // 发送握手数据
    socket_write($conn_sock, $response);

    // 新增内容,获取客户端发送的消息并转为大写还给客户端
    $msg = socket_read($conn_sock, 102400);
    socket_write($conn_sock, encode(strtoupper(decode($msg))));
}

function getShaKey($request)
{
    // 获取 Sec-WebSocket-key
    preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $match);

    // 拼接 key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    $new_key = trim($match[1]) . &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;;

    // 对字符串做 `SHA1` 计算,再把得到的结果通过 `base64` 加密
    return base64_encode(sha1($new_key, true));
}

function decode($buffer)
{
    $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);
        }
    }
    $dataLength = \strlen($data);
    $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
    return $data ^ $masks;
}

function encode($buffer)
{
    if (!is_scalar($buffer)) {
        throw new \Exception("You can&#39;t send(" . \gettype($buffer) . ") to client, you need to convert it to a string. ");
    }
    $len = \strlen($buffer);

    $first_byte = "\x81";

    if ($len <= 125) {
        $encode_buffer = $first_byte . \chr($len) . $buffer;
    } else {
        if ($len <= 65535) {
            $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer;
        } else {
            $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer;
        }
    }

    return $encode_buffer;
}

使用 在线测试工具 进行测试,可以看到消息已经可以正常发送接收,接下来的文章将继续优化代码,实现简易聊天室,敬请关注:

Siri PHP+Socket untuk melaksanakan bilik sembang websocket

实现web聊天室

我们紧接着上文的代码继续优化,以实现简易的web聊天室

多路复用

其实就是加一下 socket_select() 函数 Siri PHP+Socket untuk melaksanakan bilik sembang websocket ,本文就不写原理与语法了,详情可参考 之前的文章,以下代码修改自前文 Siri PHP+Socket untuk melaksanakan bilik sembang websocket

...

socket_listen($socket);

+$sockets[] = $socket;
+$user = [];
while (true) {
+   $tmp_sockets = $sockets;
+   socket_select($tmp_sockets, $write, $except, null);

+   foreach ($tmp_sockets as $sock) {
+       if ($sock == $socket) {
+           $sockets[] = socket_accept($socket);
+           $user[] = [&#39;socket&#39; => $socket, &#39;handshake&#39; => false];
+       } else {
+           $curr_user = $user[array_search($sock, $user)];
+           if ($curr_user[&#39;handshake&#39;]) { // 已握手
+               $msg = socket_read($sock, 102400);
+               echo &#39;客户端发来消息&#39; . decode($msg);
+               socket_write($sock, encode(&#39;这是来自服务端的消息&#39;));
+           } else {
+               // 握手
+           }
+       }
+   }

-   $conn_sock = socket_accept($socket);
-   $request = socket_read($conn_sock, 102400);

...

实现聊天室

最终成果演示

Siri PHP+Socket untuk melaksanakan bilik sembang websocket

我们将上述代码改造成类,并在类变量储存用户信息,添加消息处理等逻辑,最后贴出代码,建议保存下来自己尝试一下,也许会有全新的认知,后端代码:

<?php

new WebSocket();

class Websocket
{
    /**
     * @var resource
     */
    protected $socket;

    /**
     * @var array 用户列表
     */
    protected $user = [];

    /**
     * @var array 存放所有 socket 资源
     */
    protected $socket_list = [];

    public function __construct()
    {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, true);
        socket_bind($this->socket, 0, 8888);
        socket_listen($this->socket);

        // 将 socket 资源放入 socket_list
        $this->socket_list[] = $this->socket;

        while (true) {
            $tmp_sockets = $this->socket_list;
            socket_select($tmp_sockets, $write, $except, null);

            foreach ($tmp_sockets as $sock) {
                if ($sock == $this->socket) {
                    $conn_sock = socket_accept($sock);
                    $this->socket_list[] = $conn_sock;
                    $this->user[] = [&#39;socket&#39; => $conn_sock, &#39;handshake&#39; => false, &#39;name&#39; => &#39;无名氏&#39;];
                } else {
                    $request = socket_read($sock, 102400);
                    $k = $this->getUserIndex($sock);

                    if (!$request) {
                        continue;
                    }

                    // 用户端断开连接
                    if ((\ord($request[0]) & 0xf) == 0x8) {
                        $this->close($k);
                        continue;
                    }

                    if (!$this->user[$k][&#39;handshake&#39;]) {
                        // 握手
                        $this->handshake($k, $request);
                    } else {
                        // 已握手
                        $this->send($k, $request);
                    }
                }
            }
        }
    }

    /**
     * 关闭连接
     *
     * @param $k
     */
    protected function close($k)
    {
        $u_name = $this->user[$k][&#39;name&#39;] ?? &#39;无名氏&#39;;
        socket_close($this->user[$k][&#39;socket&#39;]);
        $socket_key = array_search($this->user[$k][&#39;socket&#39;], $this->socket_list);
        unset($this->socket_list[$socket_key]);
        unset($this->user[$k]);

        $user = [];
        foreach ($this->user as $v) {
            $user[] = $v[&#39;name&#39;];
        }
        $res = [
            &#39;type&#39; => &#39;close&#39;,
            &#39;users&#39; => $user,
            &#39;msg&#39; => $u_name . &#39;已退出&#39;,
            &#39;time&#39; => date(&#39;Y-m-d H:i:s&#39;)
        ];
        $this->sendAllUser($res);
    }

    /**
     * 获取用户索引
     *
     * @param $socket
     * @return int|string
     */
    protected function getUserIndex($socket)
    {
        foreach ($this->user as $k => $v) {
            if ($v[&#39;socket&#39;] == $socket) {
                return $k;
            }
        }
    }

    /**
     * 握手
     * @param $k
     * @param $request
     */
    protected function handshake($k, $request)
    {
        preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $match);
        $key = base64_encode(sha1($match[1] . &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;, true));

        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response .= "Upgrade: websocket\r\n";
        $response .= "Connection: Upgrade\r\n";
        $response .= "Sec-WebSocket-Accept: {$key}\r\n\r\n";
        socket_write($this->user[$k][&#39;socket&#39;], $response);
        $this->user[$k][&#39;handshake&#39;] = true;
    }

    /**
     * 接收并处理消息
     *
     * @param $k
     * @param $msg
     */
    public function send($k, $msg)
    {
        $msg = $this->decode($msg);
        $msg = json_decode($msg, true);

        if (!isset($msg[&#39;type&#39;])) {
            return;
        }

        switch ($msg[&#39;type&#39;]) {
            case &#39;login&#39;: // 登录
                $this->user[$k][&#39;name&#39;] = $msg[&#39;name&#39;] ?? &#39;无名氏&#39;;
                $users = [];
                foreach ($this->user as $v) {
                    $users[] = $v[&#39;name&#39;];
                }
                $res = [
                    &#39;type&#39; => &#39;login&#39;,
                    &#39;name&#39; => $this->user[$k][&#39;name&#39;],
                    &#39;msg&#39; => $this->user[$k][&#39;name&#39;] . &#39;: login success&#39;,
                    &#39;users&#39; => $users,
                ];
                $this->sendAllUser($res);
                break;
            case &#39;message&#39;: // 接收并发送消息
                $res = [
                    &#39;type&#39; => &#39;message&#39;,
                    &#39;name&#39; => $this->user[$k][&#39;name&#39;] ?? &#39;无名氏&#39;,
                    &#39;msg&#39; => $msg[&#39;msg&#39;],
                    &#39;time&#39; => date(&#39;H:i:s&#39;),
                ];
                $this->sendAllUser($res);
                break;
        }
    }

    /**
     * 发送给所有人
     *
     */
    protected function sendAllUser($msg)
    {
        if (is_array($msg)) {
            $msg = json_encode($msg);
        }

        $msg = $this->encode($msg);

        foreach ($this->user as $k => $v) {
            socket_write($v[&#39;socket&#39;], $msg, strlen($msg));
        }
    }

    /**
     * 解码
     *
     * @param $buffer
     * @return string
     */
    protected function decode($buffer)
    {
        $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);
            }
        }
        $dataLength = \strlen($data);
        $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
        return $data ^ $masks;
    }

    protected function encode($buffer)
    {
        if (!is_scalar($buffer)) {
            throw new \Exception("You can&#39;t send(" . \gettype($buffer) . ") to client, you need to convert it to a string. ");
        }
        $len = \strlen($buffer);

        $first_byte = "\x81";

        if ($len <= 125) {
            $encode_buffer = $first_byte . \chr($len) . $buffer;
        } else {
            if ($len <= 65535) {
                $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer;
            } else {
                $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer;
            }
        }

        return $encode_buffer;
    }
}

前端代码如下(前端内容不在本文讨论范围之内,具体可参考 菜鸟教程):

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<style>
    * {
        margin: 0;
        padding: 0;
    }
    h3 {
        display: flex;
        justify-content: center;
        margin: 30px auto;
    }
    .but-box {
        border-radius: 5px;
        display: flex;
        justify-content: center;
        align-items: center;
        margin-top: 10px;
    }
    #box {
        display: flex;
        margin: 5px auto;
        border-radius: 5px;
        border: 1px #ccc solid;
        height: 400px;
        width: 700px;
        overflow-y: auto;
        overflow-x: hidden;
        position: relative;
    }
    #msg-box {
        width: 480px;
        margin-right: 111px;
        height: 100%;
        overflow-y: auto;
        overflow-x: hidden;
    }
    #user-box {
        width: 110px;
        overflow-y: auto;
        overflow-x: hidden;
        float: left;
        border-left: 1px #ccc solid;
        height: 100%;
        background-color: #F1F1F1;
    }
    button {
        float: right;
        width: 80px;
        height: 35px;
        font-size: 18px;
    }
    input {
        width: 100%;
        height: 30px;
        padding: 2px;
        line-height: 20px;
        outline: none;
        border: solid 1px #CCC;
    }
    .but-box p {
        margin-right: 160px;
    }
</style>
<body>

<h3>这是一个php socket实现的web聊天室</h3>

<div id="box">
    <div id="msg-box"></div>
    <div id="user-box"></div>
</div>

<div>

    <p><textarea cols="60" rows="3" style="resize:none;pedding: 10px"    id="content"> </textarea></p>
    <button id="send">发送</button>
</div>
<script src="https://cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>
<script>
    let ws = new WebSocket(&#39;ws://124.222.85.67:8888&#39;);

    ws.onopen = function (event) {
        console.log(&#39;连接成功&#39;);

        var name = prompt(&#39;请输入用户名:&#39;);

        ws.send(JSON.stringify({
            type: &#39;login&#39;,
            name: name
        }));

        if (!name) {
            alert(&#39;好你个坏蛋,竟然没有输入用户名&#39;);
        }
    };
    ws.onmessage = function (event) {
        let data = JSON.parse(event.data);
        console.log(data);

        switch (data.type) {
            case &#39;close&#39;:
            case &#39;login&#39;:
                $("#user-box").html(&#39;&#39;);
                data.users.forEach(function (item) {
                    $("#user-box").append(`<p style="color: grey;">${item}</p>`);
                });
                if (data.msg) {
                    $("#msg-box").append(`<p style="color: grey;">${data.msg}</p>`);
                }
                break;
            case &#39;message&#39;:
                $("#msg-box").append(`<p><span style="color: #0A89FF">${data.time}</span><span style="color: red">${data.name}</span>${data.msg}</p>`);
                break;
        }
    };

    ws.onclose = function (event) {
        alert(&#39;连接关闭&#39;);
    };

    document.onkeydown = function (event) {
        if (event.keyCode == 13) {
            send();
        }
    }

    $("#send").click(function () {
        send();
    });

    function send() {
        let content = $("#content").val();
        $("#content").val(&#39;&#39;);
        if (!content) {
            return;
        }
        ws.send(JSON.stringify({
            type: &#39;message&#39;,
            msg: content
        }));
    }
</script>
</body>
</html>

[^1]:是通讯传输的一个术语。 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合
[^2]:  为了建立 websocket 连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(Handshaking)

推荐学习:《PHP视频教程》                                            

Atas ialah kandungan terperinci Siri PHP+Socket untuk melaksanakan bilik sembang websocket. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:learnku.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam