WebSocket 是什么?
摘抄網(wǎng)上的一些解釋:
WebSocket 協(xié)議是基于 TCP 的一種新的網(wǎng)絡協(xié)議。它實現(xiàn)了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發(fā)送信息給客戶端。
WebSocket 通信協(xié)議于2011年被 IETF 定為標準 RFC 6455,并被 RFC7936 所補充規(guī)范。
—— 百度百科
WebSocket 是一個持久化的協(xié)議,這是相對于 http 非持久化來說的。
舉個簡單的例子,http1.0 的生命周期是以 request 作為界定的,也就是一個 request,一個 response,對于 http 來說,本次 client 與 server 的會話到此結束;而在 http1.1 中,稍微有所改進,即添加了 keep-alive,也就是在一個 http 連接中可以進行多個 request 請求和多個 response 接受操作。然而在實時通信中,并沒有多大的作用,http 只能由 client 發(fā)起請求,server 才能返回信息,即 server 不能主動向 client 推送信息,無法滿足實時通信的要求。而 WebSocket 可以進行持久化連接,即 client 只需進行一次握手,成功后即可持續(xù)進行數(shù)據(jù)通信,值得關注的是 WebSocket 實現(xiàn) client 與 server 之間全雙工通信,即 server 端有數(shù)據(jù)更新時可以主動推送給 client 端。
![](/d/20211017/ccc3b8511159b90ea2d85e1baa6fee65.gif)
上圖是一個演示client和server之間建立WebSocket連接時握手部分
client 建立 WebSocket 時向服務器端請求的信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket //告訴服務器現(xiàn)在發(fā)送的是WebSocket協(xié)議
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一個Base64 encode的值,這個是瀏覽器隨機生成的,用于驗證服務器端返回數(shù)據(jù)是否是WebSocket助理
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
服務器獲取到 client 請求的信息后,根據(jù) WebSocket 協(xié)議對數(shù)據(jù)進行處理并返回,其中要對 Sec-WebSocket-Key 進行加密等操作
HTTP/1.1 101 Switching Protocols
Upgrade: websocket //依然是固定的,告訴客戶端即將升級的是Websocket協(xié)議,而不是mozillasocket,lurnarsocket或者shitsocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //這個則是經(jīng)過服務器確認,并且加密過后的 Sec-WebSocket-Key,也就是client要求建立WebSocket驗證的憑證
Sec-WebSocket-Protocol: chat
PHP 服務端
?php
if(($socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) 0) {
echo "socket_create() 失敗的原因是:".socket_strerror($sock)."\n";
}
if(($ret = socket_bind($socket,'127.0.0.1','9090')) 0) {
echo "socket_bind() 失敗的原因是:".socket_strerror($ret)."\n";
}
if(($ret = socket_listen($socket,3)) 0) {
echo "socket_listen() 失敗的原因是:".socket_strerror($ret)."\n";
}
$all_sockets = [$socket]; // socket 集合
do {
$copy_sockets = $all_sockets; // 單獨拷貝一份
// 因為客戶端是長連接,如果客戶端非正常斷開,服務端會在 socket_accept 阻塞,現(xiàn)在使用 select 非阻塞模式 socket
if(socket_select($copy_sockets, $write, $except, 0) === false)
exit('sosket_select error!');
// 接收第一次 socket 連入,連入后移除服務端 socket
if(in_array($socket, $copy_sockets)) {
$client = socket_accept($socket);
if($client) {
$buf = socket_read($client, 1024);
echo $buf;
// 匹配 Sec-Websocket-Key 標識
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
// 需要將 Sec-WebSocket-Key 值累加字符串,并依次進行 SHA-1 加密和 base64 加密
$key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
// 拼湊響應內容
$res= "HTTP/1.1 101 Switching Protocol".PHP_EOL
."Upgrade: WebSocket".PHP_EOL
."Connection: Upgrade".PHP_EOL
."WebSocket-Location: ws://127.0.0.1:9090".PHP_EOL
."Sec-WebSocket-Accept: " . $key .PHP_EOL.PHP_EOL; // 注意這里,需要兩個換行
// 向客戶端應答 Sec-WebSocket-Accept
socket_write($client, $res, strlen($res));
// 向客戶端發(fā)送消息
socket_write($client, buildMsg('socket ok'), 1024);
// 加入客戶端 socket
$all_sockets[] = $client;
}
// 移除服務端 socket
$key = array_search($socket, $copy_sockets);
unset($copy_sockets[$key]);
// socket_close($client);
}
}
// 循環(huán)所有客戶端 sockets
foreach ($copy_sockets as $s) {
// 獲取客戶端發(fā)給服務端的內容
$buf = socket_read($s, 8024);
echo strlen($buf).'---'.PHP_EOL;
// 代表客戶端主動關閉
if(strlen($buf) 9) {
$key = array_search($s, $all_sockets);
unset($all_sockets[$key]);
socket_close($s);
continue;
}
// 輸出
echo getMsg($buf).PHP_EOL;
}
}while(true);
socket_close($socket);
// 編碼服務端向客戶端發(fā)送的內容
function buildMsg($msg) {
$frame = [];
$frame[0] = '81';
$len = strlen($msg);
if ($len 126) {
$frame[1] = $len 16 ? '0' . dechex($len) : dechex($len);
} else if ($len 65025) {
$s = dechex($len);
$frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
} else {
$s = dechex($len);
$frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
}
$data = '';
$l = strlen($msg);
for ($i = 0; $i $l; $i++) {
$data .= dechex(ord($msg{$i}));
}
$frame[2] = $data;
$data = implode('', $frame);
return pack("H*", $data);
}
// 解析客戶端向服務端發(fā)送的內容
function getMsg($buffer) {
$res = '';
$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++) {
$res .= $data[$index] ^ $masks[$index % 4];
}
return $res;
}
客戶端
!DOCTYPE html>
html lang="en">
head>
meta charset="UTF-8">
title>Title/title>
script>
// 創(chuàng)建一個Socket實例
var socket = new WebSocket('ws://localhost:9090');
// 打開Socket
socket.onopen = function(event) {
// 發(fā)送一個初始化消息
socket.send("init msg");
};
socket.onmessage = function(event) {
console.log('收到消息',event);
};
// 監(jiān)聽Socket的關閉
socket.onclose = function(event) {
console.log('關閉監(jiān)聽',event);
};
function send()
{
socket.send("client msg");
}
/script>
/head>
body>
button onclick="send()">發(fā)送消息/button>
/body>
/html>
運行測試:
Client
![](/d/20211017/c8c73e649fec657c3f1524bb0c2c4b4d.gif)
Server
![](/d/20211017/1df8c0182ecaae3c4b8965f3f0e128ab.gif)
到此這篇關于PHP實現(xiàn)WebSocket實例詳解的文章就介紹到這了,更多相關PHP實現(xiàn)WebSocket內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- php+websocket 實現(xiàn)的聊天室功能詳解
- PHP 實現(xiàn) WebSocket 協(xié)議原理與應用詳解
- PHP框架實現(xiàn)WebSocket在線聊天通訊系統(tǒng)
- PHP用swoole+websocket和redis實現(xiàn)web一對一聊天
- ThinkPHP5.0框架結合Swoole開發(fā)實現(xiàn)WebSocket在線聊天案例詳解
- PHP實現(xiàn)websocket通信的方法示例
- php redis 處理websocket聊天記錄的實例代碼