ホームページ > php教程 > php手册 > PHP を使用して WebSocket を実装するリアルタイム Web チャット

PHP を使用して WebSocket を実装するリアルタイム Web チャット

WBOY
リリース: 2016-11-30 23:59:33
オリジナル
1605 人が閲覧しました

前言

websocket は HTML5 里一新的特性として一直受人注意、它真の非常に厳しいため、http "请求-响应" の常规思维、实现了サービス务器から顧客への主動推送メッセージ、本文介绍 PHP の使用法およびJS 应用 websocket 实现一网页实時聊天室;

以前に書いた一篇の文章は、ajax 時間聊天のjs と jQuery を使用する方法を説明していますが、保留中のすべては無料です,ウェブソケット才新しい趾です。

最近艰难地「挤」出了一点時間,完善了很早之前做的websocket「请求原样返」服务器,用js完善了下客户端機能,ハンドル过程和思路分享给大家,顺便也普及一次websocket関連する知識は、当然のことながら、Web ソケットの承認に関する文章も特に多く、一部の合理的な意見は省略されており、参照文章に提供されています。

正文開始前,先贴一张聊天室的効果图意(请不要在CSS渣的页面):

然后当然出典:

我は出典链接 - github - 枕边书


ウェブソケット

はじめに

WebSocket はテクノロジーではなく、全く新しいプロトコルです。これは TCP の Socket (ソケット) を使用し、ネットワーク アプリケーションの新しい重要な機能、つまり全二重送信とクライアントとサーバー間の双方向通信を定義します。 Java アプレット、XMLHttpRequest、Adobe Flash、ActiveXObject、およびさまざまな Comet テクノロジに続いて、サーバーがクライアント メッセージをプッシュするのは新しいトレンドです。

httpとの関係

ネットワーク層の観点から見ると、websocket と http プロトコルはどちらも tcp トランスポート層に基づいていますが、websocket は接続を確立するときに http の 101 スイッチ プロトコルを借用してプロトコル変換 (アップグレード) を行います。 HTTP プロトコルから WebSocket 通信プロトコルに切り替えるこのアクション プロトコルは「ハンドシェイク」と呼ばれます。

ハンドシェイクが成功した後、WebSocket は独自のプロトコルで指定されたメソッドを使用して通信します。http とは何の関係もありません。

握手

これは私自身のブラウザによって送信された典型的なハンドシェイク http ヘッダーです:

サーバーはハンドシェイクリクエストを受信した後、リクエストヘッダーの「Sec-WebSocket-Key」フィールドを抽出し、固定文字列「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」を復元し、次に sha1 暗号化を実行し、最後にそれを変換します。 Base64 エンコーディングに変換され、キーとして使用され、「Sec-WebSocket-Accept」フィールドでクライアントに返されます。クライアントがこのキーと一致すると、接続が確立され、ハンドシェイクが完了します。

データ転送

Websocket にはフレームと呼ばれる独自のデータ送信形式があります。次の図はデータ フレームの構造です。単位はビットです。

リーリー

各フィールドの具体的な意味については、興味があればこの記事「WebSocket プロトコル 5」を参照してください。データ フレームはバイナリ演算においてあまり柔軟性がないと思われるため、データ フレームを解析するアルゴリズムを作成するのは困難ではありません。データ。次のデータ フレーム解析とカプセル化は両方とも使用されるオンライン アルゴリズムです。
ただし、私の仕事では支払いゲートウェイを作成するときにデータの 16 進演算がよく使用されます。これをよく調べて要約する必要があります。

PHPはWebSocketサーバーを実装します


PHP が WebSocket を実装する場合、主に PHP のソケット関数ライブラリを使用します:

PHP のソケット関数ライブラリは、C 言語のソケット関数に非常に似ているので、APUE を一度読んだことがあるので、非常に理解しやすいと思います。 PHP マニュアルのソケット関数を読めば、誰もが PHP でのソケット プログラミングについてもある程度理解できると思います。

使用される関数については、以下のコードで簡単にコメントします。

ファイル記述子

「ファイル記述子」という突然の言及に少し驚かれるかもしれません。

しかし、サーバーとしては、接続されたソケットを保存し、識別する必要があります。各ソケットはユーザーを表します。ユーザー情報とソケットの対応関係をどのように関連付けて照会するかが問題になります。ここでは、ファイル記述子に関するちょっとしたトリックが適用されます。

Linux は「すべてがファイル」であり、C 言語のソケット実装は「ファイル記述子」であることがわかっています。このファイル記述子は通常、ファイルが開かれた順に 0 から増加する int 値です。 (もちろんシステムには限界があります)。各ソケットはファイルに対応しており、読み書きソケットは対応するファイルに対して動作するため、ファイルシステムのように読み書き機能も適用できます。

ヒント: Linux では、標準入力はファイル記述子 0 に対応し、標準出力はファイル記述子 1 に対応し、標準エラーはファイル記述子 2 に対応するため、入力と出力をリダイレクトするために使用できます。

その後、C ソケットに似た PHP ソケットはこれを自然に継承し、作成されるソケットも値が 4 5 の int などのリソース型になります。 (int) または intval() 関数を使用してソケットを一意の ID に変換し、「クラス インデックス配列」を使用してソケット リソースと対応するユーザー情報を保存できます。

結果は次のようになります:

リーリー

サーバーソケットを作成する

以下はサーバーソケットを作成するコードです:

リーリー

这样,我们就得到一个服务器 socket,当有客户端连接到此 socket 上时,它将改变状态为可读,那就看接下来服务器的处理逻辑了。

服务器逻辑

这里着重讲一下 socket_select($read, $write, $except, $tv_sec [, $tv_usec]):

select 函数使用传统的 select 模型,可读、写、异常的 socket 会被分别放入 $socket, $write, $except 数组中,然后返回 状态改变的 socket 的数目,如果发生了错误,函数将会返回 false.

需要注意的是最后两个时间参数,它们只有单位不同,可以搭配使用,用来表示 socket_select 阻塞的时长,为0时此函数立即返回,可以用于轮询机制。 为 NULL 时,函数会一直阻塞下去, 这里我们置 $tv_sec 参数为null,让它一直阻塞,直到有可操作的 socket 返回。

下面是服务器的主要逻辑:

<code class="language-none">$write = $except = NULL;
$sockets = array_column($this->sockets, 'resource'); // 获取到全部的 socket 资源
$read_num = socket_select($sockets, $write, $except, NULL);

foreach ($sockets as $socket) {
        // 如果可读的是服务器 socket, 则处理连接逻辑;            
        if ($socket == $this->master) {
            socket_accept($this->master);
            // socket_accept() 接受 请求 “正在 listen 的 socket(像我们的服务器 socket )” 的连接, 并一个客户端 socket, 错误时返回 false;
             self::connect($client);
             continue;
            }
        // 如果可读的是其他已连接 socket ,则读取其数据,并处理应答逻辑
        } else {
            // 函数 socket_recv() 从 socket 中接受长度为 len 字节的数据,并保存在 $buffer 中。
            $bytes = @socket_recv($socket, $buffer, 2048, 0);

            if ($bytes < 9) {
                // 当客户端忽然中断时,服务器会接收到一个 8 字节长度的消息(由于其数据帧机制,8字节的消息我们认为它是客户端异常中断消息),服务器处理下线逻辑,并将其封装为消息广播出去
                $recv_msg = $this->disconnect($socket);
            } else {
                // 如果此客户端还未握手,执行握手逻辑
                if (!$this->sockets[(int)$socket]['handshake']) {
                    self::handShake($socket, $buffer);
                    continue;
                } else {
                    $recv_msg = self::parse($buffer);
                }
            }

            // 广播消息
            $this->broadcast($msg);
        }
    }
}</code>
ログイン後にコピー

这里只是服务器处理消息的基础代码,日志记录和异常处理都略过了,而且还有些数据帧解析和封装的方法,各位也不一定看爱,有兴趣的可以去 github 上支持一下我的源码~~

此外,为了便于服务器与客户端的交互,我自己定义了 json 类型的消息格式,形似:

<code class="language-none">$msg = [
    'type' => $msg_type, // 有普通消息,上下线消息,服务器消息
    'from' => $msg_resource, // 消息来源
    'content' => $msg_content, // 消息内容
    'user_list' => $uname_list, // 便于同步当前在线人数与姓名
    ];</code>
ログイン後にコピー

客户端

创建客户端

前端我们使用 js 调用 Websocket 方法很简单就能创建一个 websocket 连接,服务器会为帮我们完成连接、握手的操作,js 使用事件机制来处理浏览器与服务器的交互:

<code class="language-none">// 创建一个 websocket 连接
var ws = new WebSocket("ws://127.0.0.1:8080");

// websocket 创建成功事件
ws.onopen = function () {
};

// websocket 接收到消息事件
ws.onmessage = function (e) {
    var msg = JSON.parse(e.data);
}

// websocket 错误事件
ws.onerror = function () {
};</code>
ログイン後にコピー

发送消息也很简单,直接调用 ws.send(msg) 方法就行了。

页面功能

页面部分主要是让用户使用起来方便,这里给消息框 textarea 添加了一个键盘监控事件,当用户按下回车键时直接发送消息;

<code class="language-none">function confirm(event) {
    var key_num = event.keyCode;
    if (13 == key_num) {
        send();
    } else {
        return false;
    }
}</code>
ログイン後にコピー

还有用户打开客户端时生成一个默认唯一用户名;

然后是一些对数据的解析构造,对客户端页面的更新,这里就不再啰嗦了,感兴趣的可以看源码。

用户名异步处理

这里不得不提一下用户登陆时确定用户名时的一个小问题,我原来是想在客户端创建一个连接后直接发送用户名到服务器,可是控制台里报出了 “websocket 仍在连接中或已关闭” 的错误信息。

Uncaught DOMException: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.

考虑到连接可能还没处理好,我就实现了 sleep 方法等了一秒再发送用户名,可是错误仍然存在。

后来忽然想到 js 的单线程阻塞机制,才明白使用 sleep 一直阻塞也是没有用的,利用好 js 的事件机制才是正道:于是在服务器端添加逻辑,在握手成功后,向客户端发送握手已成功的消息;客户端先将用户名存入一个全局变量,接收到服务器的握手成功的提醒消息后再发送用户名,于是成功在第一时间更新用户名。


小结

聊天室扩展方向

简易聊天室已经完成,当然还要给它带有希望的美好未来,希望有人去实现:

  • 页面美化(信息添加颜色等)
  • 服务器识别 '@' 字符而只向某一个 socket 写数据实现聊天室的私聊;
  • 多进程(使用 redis 等缓存数据库来实现资源的共享),可参考我以前的一篇文章: 初探PHP多进程
  • 消息记录数据库持久化(log 日志还是不方便分析)
  • ...

总结

多读些经典书籍还是很有用的,有些东西真的是触类旁通,APUE/UNP 还是要再多翻几遍。此外互联网技术日新月异,挑一些自己喜欢的学习一下,跟大家分享一下也是挺舒服的(虽然程序和博客加一块用了至少10个小时...)。

参考:

websocket协议翻译

刨根问底 HTTP 和 WebSocket 协议(下)

学习WebSocket协议—从顶层到底层的实现原理(修订版)

嗯,持续更新。喜欢的可以点个推荐或关注,有错漏之处,请指正,谢谢。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート