搜尋
首頁php教程php手册php+websocket搭建簡易聊天室實踐

1、前言

  公司遊戲裡面有個簡單的聊天室,了解了之後才知道是node+websocket做的,想想php也來做個簡單的聊天室。於是蒐集各種資料看文件、找實例自己也寫了個簡單的聊天室。

  http連接分為短連接和長連接。短連線一般可以用ajax實現,長連線就是websocket。短連線實作起來比較簡單,但太過於消耗資源。 websocket高效不過相容存在點問題。 websocket是html5的資源

  如果想詳細了解websocket長連接的原理請看https://www.zhihu.com/question/20215561。

  本文主要介紹websocket簡易聊天室的實現步驟具體部分知識點的深入會給出連結或麻煩讀者自己蒐集資料。

2、前端

  前端實作websocket很簡單直接

  //連接websocket

      var ws = new WebSocket("ws://127.0.0.1:8000");

  //成功連接websoc的時候

  ws.onopen = function(){}

  //成功取得服務端輸出的訊息

  ws.onmessage = function(e){}

     //連接錯誤的時候
  ws.onerror = function(){}

    //傳送資料至服務端

  ws.send();

3、後台

    websocket的困難主要在後台

  3.1websocket連接過程

  websocket 通訊圖解 這是一個簡易的客戶端和服務端的通訊圖解,php主要就做的就是接受加密key  並回傳 其中完成套接字的創建和握手操作

  

    下圖是一張詳細的服務端處理websocket的流程圖

  

     

3.2 代碼實踐

  服務端做的流程大致是:

    ①、掛起一個socket套接字進程等待連接

    ②、有socket連接之後遍歷套接字數組

    ③、沒有握手的進行握手操作,如果已經握手則接收資料解析並寫入緩衝區進行輸出

  下面是範例程式碼(我寫的是一個類別所以程式碼是根據函數分段的),文底給出github位址以及自己遇到的一些坑

     1、先建立套接字

<span style="color: #008000;">//</span><span style="color: #008000;">建立套接字</span>
        <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> createSocket(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">)
        {
            </span><span style="color: #008000;">//</span><span style="color: #008000;">创建一个套接字</span>
            <span style="color: #800080;">$socket</span>= socket_create(AF_INET, SOCK_STREAM,<span style="color: #000000;"> SOL_TCP);
            </span><span style="color: #008000;">//</span><span style="color: #008000;">设置套接字选项</span>
            socket_set_option(<span style="color: #800080;">$socket</span>, SOL_SOCKET, SO_REUSEADDR, 1<span style="color: #000000;">);
            </span><span style="color: #008000;">//</span><span style="color: #008000;">绑定IP地址和端口</span>
            socket_bind(<span style="color: #800080;">$socket</span>,<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">);
            </span><span style="color: #008000;">//</span><span style="color: #008000;">监听套接字</span>
            socket_listen(<span style="color: #800080;">$socket</span><span style="color: #000000;">);
            </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$socket</span><span style="color: #000000;">;
        }</span>

  2、將套接字放入陣列

<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span>  __construct(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">)
        {
            </span><span style="color: #008000;">//</span><span style="color: #008000;">建立套接字</span>
            <span style="color: #800080;">$this</span>->soc=<span style="color: #800080;">$this</span>->createSocket(<span style="color: #800080;">$address</span>,<span style="color: #800080;">$port</span><span style="color: #000000;">);
            </span><span style="color: #800080;">$this</span>->socs=<span style="color: #0000ff;">array</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">soc);

        }</span>

 

3、掛起進程遍歷套接字數組,主要操作都是在這裡面完成的

<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> run(){
            </span><span style="color: #008000;">//</span><span style="color: #008000;">挂起进程</span>
            <span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){
                </span><span style="color: #800080;">$arr</span>=<span style="color: #800080;">$this</span>-><span style="color: #000000;">socs;
                </span><span style="color: #800080;">$write</span>=<span style="color: #800080;">$except</span>=<span style="color: #0000ff;">NULL</span><span style="color: #000000;">;
                </span><span style="color: #008000;">//</span><span style="color: #008000;">接收套接字数字 监听他们的状态</span>
                socket_select(<span style="color: #800080;">$arr</span>,<span style="color: #800080;">$write</span>,<span style="color: #800080;">$except</span>, <span style="color: #0000ff;">NULL</span><span style="color: #000000;">);
                </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历套接字数组</span>
                <span style="color: #0000ff;">foreach</span>(<span style="color: #800080;">$arr</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$k</span>=><span style="color: #800080;">$v</span><span style="color: #000000;">){
                    </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是新建立的套接字返回一个有效的 套接字资源</span>
                    <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->soc == <span style="color: #800080;">$v</span><span style="color: #000000;">){
                        </span><span style="color: #800080;">$client</span>=socket_accept(<span style="color: #800080;">$this</span>-><span style="color: #000000;">soc);
                        </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$client</span> ){
                            <span style="color: #0000ff;">echo</span> "socket_accept() failed"<span style="color: #000000;">;
                        }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{
                            </span><span style="color: #008000;">//</span><span style="color: #008000;"> array_push($this->socs,$client);
                            // unset($this[]);
                            //将有效的套接字资源放到套接字数组</span>
                            <span style="color: #800080;">$this</span>->socs[]=<span style="color: #800080;">$client</span><span style="color: #000000;">;
                        }
                    }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{
                        </span><span style="color: #008000;">//</span><span style="color: #008000;">从已连接的socket接收数据  返回的是从socket中接收的字节数</span>
                        <span style="color: #800080;">$byte</span>=socket_recv(<span style="color: #800080;">$v</span>, <span style="color: #800080;">$buff</span>,20480, 0<span style="color: #000000;">);
                        </span><span style="color: #008000;">//</span><span style="color: #008000;">如果接收的字节是0</span>
                        <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$byte</span>)
                            <span style="color: #0000ff;">continue</span><span style="color: #000000;">;
                        </span><span style="color: #008000;">//</span><span style="color: #008000;">判断有没有握手没有握手则进行握手,如果握手了 则进行处理</span>
                        <span style="color: #0000ff;">if</span>(!<span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$client</span><span style="color: #000000;">]){
                            </span><span style="color: #008000;">//</span><span style="color: #008000;">进行握手操作</span>
                            <span style="color: #800080;">$this</span>->hands(<span style="color: #800080;">$client</span>,<span style="color: #800080;">$buff</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">);
                        }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{
                            </span><span style="color: #008000;">//</span><span style="color: #008000;">处理数据操作</span>
                            <span style="color: #800080;">$mess</span>=<span style="color: #800080;">$this</span>->decodeData(<span style="color: #800080;">$buff</span><span style="color: #000000;">);
                               </span><span style="color: #008000;">//</span><span style="color: #008000;">发送数据</span>
                            <span style="color: #800080;">$this</span>->send(<span style="color: #800080;">$mess</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">);
                        }
                    }
                }
            }
        }</span>

 

4、進行握手 流程是接收websocket內容從Sec-WebSocket-Key:中獲取key並透過加密演算法寫入緩衝區客戶端會進行驗證(自動驗證不需要我們處理)

<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> hands(<span style="color: #800080;">$client</span>,<span style="color: #800080;">$buff</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">)
        {
            </span><span style="color: #008000;">//</span><span style="color: #008000;">提取websocket传的key并进行加密  (这是固定的握手机制获取Sec-WebSocket-Key:里面的key)</span>
            <span style="color: #800080;">$buf</span>  = <span style="color: #008080;">substr</span>(<span style="color: #800080;">$buff</span>,<span style="color: #008080;">strpos</span>(<span style="color: #800080;">$buff</span>,'Sec-WebSocket-Key:')+18<span style="color: #000000;">);
            </span><span style="color: #008000;">//</span><span style="color: #008000;">去除换行空格字符</span>
            <span style="color: #800080;">$key</span>  = <span style="color: #008080;">trim</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$buf</span>,0,<span style="color: #008080;">strpos</span>(<span style="color: #800080;">$buf</span>,"\r\n"<span style="color: #000000;">)));
             </span><span style="color: #008000;">//</span><span style="color: #008000;">固定的加密算法</span>
            <span style="color: #800080;">$new_key</span> = <span style="color: #008080;">base64_encode</span>(<span style="color: #008080;">sha1</span>(<span style="color: #800080;">$key</span>."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",<span style="color: #0000ff;">true</span><span style="color: #000000;">));
            </span><span style="color: #800080;">$new_message</span> = "HTTP/1.1 101 Switching Protocols\r\n"<span style="color: #000000;">;
            </span><span style="color: #800080;">$new_message</span> .= "Upgrade: websocket\r\n"<span style="color: #000000;">;
            </span><span style="color: #800080;">$new_message</span> .= "Sec-WebSocket-Version: 13\r\n"<span style="color: #000000;">;
            </span><span style="color: #800080;">$new_message</span> .= "Connection: Upgrade\r\n"<span style="color: #000000;">;
            </span><span style="color: #800080;">$new_message</span> .= "Sec-WebSocket-Accept: " . <span style="color: #800080;">$new_key</span> . "\r\n\r\n"<span style="color: #000000;">;
            </span><span style="color: #008000;">//</span><span style="color: #008000;">将套接字写入缓冲区</span>
            socket_write(<span style="color: #800080;">$v</span>,<span style="color: #800080;">$new_message</span>,<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$new_message</span><span style="color: #000000;">));
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> socket_write(socket,$upgrade.chr(0), strlen($upgrade.chr(0)));
            //标记此套接字握手成功</span>
            <span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$client</span>]=<span style="color: #0000ff;">true</span><span style="color: #000000;">;
        }</span>

5、解析客戶端的資料(我這裡沒有進行加密,如果有需要也可以自己加密 )

<span style="color: #008000;">//</span><span style="color: #008000;">解析数据</span>
        <span style="color: #0000ff;">public</span>  <span style="color: #0000ff;">function</span>  decodeData(<span style="color: #800080;">$buff</span><span style="color: #000000;">)
        {
            </span><span style="color: #008000;">//</span><span style="color: #008000;">$buff  解析数据帧</span>
            <span style="color: #800080;">$mask</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">();  
            </span><span style="color: #800080;">$data</span> = ''<span style="color: #000000;">;  
            </span><span style="color: #800080;">$msg</span> = <span style="color: #008080;">unpack</span>('H*',<span style="color: #800080;">$buff</span>);  <span style="color: #008000;">//</span><span style="color: #008000;">用unpack函数从二进制将数据解码</span>
            <span style="color: #800080;">$head</span> = <span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],0,2<span style="color: #000000;">);  
            </span><span style="color: #0000ff;">if</span> (<span style="color: #008080;">hexdec</span>(<span style="color: #800080;">$head</span>{1}) === 8<span style="color: #000000;">) {  
                </span><span style="color: #800080;">$data</span> = <span style="color: #0000ff;">false</span><span style="color: #000000;">;  
            }</span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> (<span style="color: #008080;">hexdec</span>(<span style="color: #800080;">$head</span>{1}) === 1<span style="color: #000000;">){  
                </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],4,2<span style="color: #000000;">));  
                </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],6,2<span style="color: #000000;">));  
                </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],8,2<span style="color: #000000;">));  
                </span><span style="color: #800080;">$mask</span>[] = <span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],10,2<span style="color: #000000;">));  
                   </span><span style="color: #008000;">//</span><span style="color: #008000;">遇到的问题  刚连接的时候就发送数据  显示 state connecting</span>
                <span style="color: #800080;">$s</span> = 12<span style="color: #000000;">;  
                </span><span style="color: #800080;">$e</span> = <span style="color: #008080;">strlen</span>(<span style="color: #800080;">$msg</span>[1])-2<span style="color: #000000;">;  
                </span><span style="color: #800080;">$n</span> = 0<span style="color: #000000;">;  
                </span><span style="color: #0000ff;">for</span> (<span style="color: #800080;">$i</span>=<span style="color: #800080;">$s</span>; <span style="color: #800080;">$i</span>$e; <span style="color: #800080;">$i</span>+= 2<span style="color: #000000;">) {  
                    </span><span style="color: #800080;">$data</span> .= <span style="color: #008080;">chr</span>(<span style="color: #800080;">$mask</span>[<span style="color: #800080;">$n</span>%4]^<span style="color: #008080;">hexdec</span>(<span style="color: #008080;">substr</span>(<span style="color: #800080;">$msg</span>[1],<span style="color: #800080;">$i</span>,2<span style="color: #000000;">)));  
                    </span><span style="color: #800080;">$n</span>++<span style="color: #000000;">;  
                }
                </span><span style="color: #008000;">//</span><span style="color: #008000;">发送数据到客户端
                   //如果长度大于125 将数据分块</span>
                   <span style="color: #800080;">$block</span>=<span style="color: #008080;">str_split</span>(<span style="color: #800080;">$data</span>,125<span style="color: #000000;">);
                   </span><span style="color: #800080;">$mess</span>=<span style="color: #0000ff;">array</span><span style="color: #000000;">(
                       </span>'mess'=><span style="color: #800080;">$block</span>[0],<span style="color: #000000;">
                       );
                </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$mess</span><span style="color: #000000;">;                   
            }</span>

 

6、將套接字寫入緩衝區

<span style="color: #008000;">//</span><span style="color: #008000;">发送数据</span>
        <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> send(<span style="color: #800080;">$mess</span>,<span style="color: #800080;">$v</span><span style="color: #000000;">)
        {
            </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历套接字数组 成功握手的  进行数据群发</span>
            <span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$this</span>->socs <span style="color: #0000ff;">as</span> <span style="color: #800080;">$keys</span> => <span style="color: #800080;">$values</span><span style="color: #000000;">) {
                </span><span style="color: #008000;">//</span><span style="color: #008000;">用系统分配的套接字资源id作为用户昵称</span>
                   <span style="color: #800080;">$mess</span>['name']="Tourist's socket:{<span style="color: #800080;">$v</span>}"<span style="color: #000000;">;
                   </span><span style="color: #800080;">$str</span>=json_encode(<span style="color: #800080;">$mess</span><span style="color: #000000;">);
                   </span><span style="color: #800080;">$writes</span> ="\x81".<span style="color: #008080;">chr</span>(<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$str</span>)).<span style="color: #800080;">$str</span><span style="color: #000000;">;
                   </span><span style="color: #008000;">//</span><span style="color: #008000;"> ob_flush();
                   // flush();
                   // sleep(3);</span>
                   <span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->hand[(int)<span style="color: #800080;">$values</span><span style="color: #000000;">])
                       socket_write(</span><span style="color: #800080;">$values</span>,<span style="color: #800080;">$writes</span>,<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$writes</span><span style="color: #000000;">));
               }
        }</span>

 

7、运行方法

github地址git@github.com:rsaLive/websocket.git

①最好在控制台运行server.php

转到server.php脚本目录(可以先php -v 看下有没有配置php如果没有Linux配置下bash windows 配置下path)

php -f server.php

如果有错误会提示

②通过服务器访问html文件

 

 8、踩过的坑,打开调试工作方便查看错误

server.php 挂起的进程中可以打印输出的,如果出现问题可以在代码中加入打印来调试 

可以在各个判断里面做标记在控制台查看代码运行在哪个区间

不过每次修改完代码之后需要重新运行脚本 php server.php

如果出现这种错误可能是

  1、在与服务器初始套接字的时候发送数据 (在第一次与服务器验证握手的时候不能发送内容)

  2、如果已经验证过了但是客户端没有发送或者发送的消息为空也会出现这样的情况

    所以要检验已连接的套接字的数据

③可能浏览器不支持或者服务端没有开启socket开始之前最好验证下

<span style="color: #000000;">if (window.WebSocket){
    console.log("This browser supports WebSocket!");
} else {
    console.log("This browser does not support WebSocket.");
}</span>

 

如有不正欢迎指出

 

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。