本次教程需要理解的內容:
- 什么是WebSocket?
- WebSocket可以用來干什么?
- 什么是WebSocket握手?
- php使用WebSocket的流程?
- php中WebSocket相關函數的作用?
(一)什么是WebSocket?
WebSocket是一種在單個TCP連接上進行全雙工通信的協議。WebSocket通信協議於2011年被IETF定為標准RFC 6455,並由RFC7936補充規范。WebSocket API也被W3C定為標准。WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
(二)WebSocket的作用?
WebSock其實在平常使用,我們是時常見到的,用於實時通訊,例如我們常用的實時聊天、服務端向客戶端消息推送、也可以實現踢用戶下線功能。實時彈幕功能等等。
(三)什么是握手?
為了創建Websocket連接,需要通過瀏覽器發出請求,之后服務器進行回應,這個過程通常稱為“握手”(handshaking)。
這是比較正式的理解,在接下來使用方式中會在介紹到握手的實際含義。
(四)php使用WebSocket的流程及相關函數的意義
這里代碼注釋都會進行逐一解釋,所以就直接上代碼,有什么不懂歡迎提出來。
<?php
//設置應該報告何種 PHP 錯誤
error_reporting(E_ALL^E_NOTICE);
//設置腳本最大執行時間,0則為不限制
set_time_limit(0);
//打開或關閉絕對(隱式)刷送
ob_implicit_flush();
//設置創建socket服務器的ip
$address="127.0.0.1";
//設置socket監聽的端口
$port=10000;
//socket的resource,即前期初始化socket時返回的socket資源
$master;
//用來存儲連接進來的用戶信息的數組
$users;
//socket的連接池,即client連接進來的socket標志,一個數組
$sockets;
/**
* 以下socket_?()方法都為創建一個socket必須的,且順序不能亂,缺一不可
*/
//創建一個socket
$master=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//設置socket選項,1表示接受所有的數據包
socket_set_option($master,SOL_SOCKET,SO_REUSEADDR,1);
//綁定socket到指定ip與端口
socket_bind($master,$address,$port);
//監聽已連接的socket
socket_listen($master);
//初始化sockets連接池
$sockets=array($master);
//對一些必要信息的輸出記錄
echo "socket已連接,時間:".date("Y-m-d H:i:s")."\n";
echo "監聽中:".$address.":".$port."\n";
//設置循環使腳本持續運行處理消息
while(true){
//用來檢測是否有變化的數組(就是有新消息到或者有客戶端連接/斷開)
$changes=$sockets;
$write=NULL;
$except=NULL;
/**
* 對於個人理解,這個函數的作用為阻塞程序往下執行,它會不停的檢驗$changes是否有變化,沒有變化就將阻斷程序往下執行。
* 只有出現$changes出現變化(有新消息到或者有客戶端連接/斷開)才會對繼續執行程序。
* 很重要的一個函數。有的說法是同時接受多個連接的關鍵
* @param array $write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。
* @param array $except是$sockets里面要被排除的元素,傳入NULL是”監聽”全部。
* @param int 最后一個參數是超時時間
* 如果為0:則立即結束
* 如果為n>1: 則最多在n秒后結束,如遇某一個連接有新動態,則提前返回
* 如果為null:如遇某一個連接有新動態,則返回
*/
socket_select($changes,$write,$except,NULL);
//這里遍歷檢測出出現何種變化,然后進行處理
foreach($changes as $sock){
//當下列條件的滿足時,表示有新用戶連接進來
if($sock==$master){
//接受該用戶的連接
$client=socket_accept($master);
//給這個用戶生成一個獨一無二的id,用與獲取該用戶的信息的唯一標識。
$key=uniqid();
//將新用戶存入socke連接池
$sockets[]=$client;
//記錄用戶連接的信息,為了方便能對指定用戶發送消息。其中handshake代表服務器與客戶端握手與否,socket的另外一個重要的操作
$users[$key]=array(
"socket"=>$client,
"handshake"=>false,
);
echo "分配id為".$key."的用戶連接\n";
}
// 剩下的為用戶斷開連接或者用戶向服務端發送信息
else{
$len=0;//收到數據的長度
$buffer='';//收到的數據
/**
* socket_recv( resource $socket, string &$buf, int $len, int $flags) : int
* 函數 socket_recv() 從 socket 中接受長度為 最大為$len 字節的數據,並保存在 buf 中,$l返回的為實際讀取數據的長度。
* socket_recv() 用於從已連接的socket中接收數據。除此之外,可以設定一個或多個 flags 來控制函數的具體行為。
*/
//通過循環的方式讀取全部數據$len可根據自身設置
do{
$l=socket_recv($sock,$buf,1000,0);
$len+=$l;
$buffer.=$buf;
}while($l==1000);
$tmpk;//獲取操作用戶的key,即一開始分配的唯一標識id
foreach($users as $k=>$v){//$k為鍵,$v為值
if($sock==$v['socket']){
//獲取連接的用戶數組users,當users里存在有只返回該用戶被分配的唯一id
$tmpk=$k;
}
}
// 如果數據長度小於7為斷開連接
if($len<7){
socket_close($users[$k]['socket']);//關閉該用戶連接,可以寫成socket_close($sock),這種寫法是封裝后的寫法,為了容易看懂不進行封裝;
unset($users[$tmpk]);//銷毀指定的users的某個用戶信息
$sockets=array($master);//可以理解為初始化sockets連接池
//遍歷users數組,將連接的信息存入$sockets中
foreach($users as $v){
$sockets[]=$v['socket'];
}
echo "id為".$tmpk."用戶斷開連接\n";
continue;
}
//服務端與用戶握手
//如果沒有與客戶端握手,數據交換都會錯誤。
//一旦服務器發送了以下頭文件,握手就完成了,我們就可以交換數據了,可以理解為檢驗身份差不多的意思
if(!$users[$tmpk]['handshake']){
//截取客戶端請求時發送給服務端Sec-WebSocket-Key的值並加密,其中$key后面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串應該是固定的
$buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);
$key=trim(substr($buf,0,strpos($buf,"\r\n")));//前兩步可以直接替換為trim(substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+16))
$new_key=base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
//向客戶端返回該信息,也就是所說的握手。
$hand_message="HTTP/1.1 101 Switching Protocols\r\n"
."Upgrade: websocket\r\n"
."Sec-Websocket-Version: 13\r\n"
."Connection: Upgrade\r\n"
."Sec-Websocket-Accept: ".$new_key."\r\n\r\n";
/**
* writes to the socket from the given buffer
* 向指定的socket發送信息
* 這里向用戶發送握手信息
*/
$status=socket_write($users[$tmpk]['socket'],$hand_message,strlen($hand_message));
if($status){
echo "與用戶id".$tmpk."握手成功\n";
echo $hand_message."\n";
}
}
// 最后剩下的就為用戶發送消息,做接收操作,由於需要包含二進制數據的轉換,需了解websocket的數據收發協議,下一篇將更新接下來數據的處理
else{
//接收數據處理操作
}
}
}
}
?>
結語:由於接下來數據的接收與發送,會涉及到數據的解碼與編碼,下一篇內容將會介紹數據的發送與接收,對各個操作都詳細的解釋。
自己學習過程中沒看到叫詳細的教程,就寫個專題關於WebSocket的使用,當然也可以使用workman等開源通訊框架,少去很多麻煩,在這里也是為了構造自己的通訊方式,自己編寫。
————————————————
原文鏈接:https://blog.csdn.net/Vae_sun/article/details/90318326
