2018年11月4日 星期日

《面試官別再問》PHP實現Websocket

WebSocket是一個持久化的協議

http1.0的生命週期是以request作為界定的,也就是一個request,一個response,對於http來說,本次client與server的會話到此結束;而在http1.1中,稍微有所改進,即添加了keep-alive,也就是在一個http連接中可以進行多個request請求和多個response接受操作。然而在實時通信中,並沒有多大的作用,http只能由client發起請求,server才能返回信息,即server不能主動向client推送信息,無法滿足實時通信的要求。而WebSocket可以進行持久化連接,即client只需進行一次握手,成功後即可持續進行數據通信,值得關注的是WebSocket實現client與server之間全雙工通信,即server端有數據更新時可以主動推送給client端。

client與server建立socket時握手的會話內容,即request與response

a、client建立WebSocket時向服務器端請求的信息

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket //告訴服務器現在發送的是WebSocket協議

Connection: Upgrade

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一個Base64 encode的值,這個是瀏覽器隨機生成的,用於驗證服務器端返回數據是否是WebSocket助理

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

b、服務器獲取到client請求的信息後,根據WebSocket協議對數據進行處理並返回,其中要對Sec-WebSocket-Key進行加密等操作

HTTP/1.1 101 Switching Protocols

Upgrade: websocket //依然是固定的,告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket

Connection: Upgrade

Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //這個則是經過服務器確認,並且加密過後的Sec-WebSocket-Key,也就是client要求建立WebSocket驗證的憑證

PHP中建立socket的過程講解


1.client與server之間建立socket通訊,首先在PHP中建立socket並監聽Port

//傳相應的IP與端口進行創建socket操作
function WebSocket($address,$port){
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的數據包
socket_bind($server, $address, $port);
socket_listen($server);
return $server;
}

2.設計一個迴圈掛起WebSocket通道,進行資料的接收

//對建立的socket迴圈進行監聽,處理數據
function run(){
//死循環,直到socket斷開
while(true){
$changes=$this->sockets;
$write=NULL;
$except=NULL;
         
/*
//這個函數是同時接受多個連接的關鍵,我的理解它是為了阻塞程序繼續往下執行。
socket_select ($sockets, $write = NULL, $except = NULL, NULL);
$sockets可以理解為一個數組,這個數組中存放的是文件描述符。當它有變化(就是有新消息到或者有客戶端連接/斷開)時,socket_select函數才會返回,繼續往下執行。
$write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。
$except是$sockets裡面要被排除的元素,傳入NULL是”監聽”全部。
最後一個參數是超時時間
如果為0:則立即結束
如果為n>1: 則最多在n秒後結束,如遇某一個連接有新動態,則提前返回
如果為null:如遇某一個連接有新動態,則返回
*/
socket_select($changes,$write,$except,NULL);
foreach($changes as $sock){
             
//如果有新的client連接進來,則
if($sock==$this->master){

//接受一個socket連接
$client=socket_accept($this->master);

//給新連接進來的socket一個唯一的ID
$key=uniqid();
$this->sockets[]=$client; //將新連接進來的socket存進連接池
$this->users[$key]=array(
'socket'=>$client, //記錄新連接進來client的socket信息
'shou'=>false //標誌該socket資源沒有完成握手
);
//否則1.為client斷開socket連接,2.client發送信息
}else{
$len=0;
$buffer='';
//讀取該socket的信息,注意:第二個參數是引用傳參即接收數據,第三個參數是接收數據的長度
do{
$l=socket_recv($sock,$buf,1000,0);
$len+=$l;
$buffer.=$buf;
}while($l==1000);

//根據socket在user池裡面查找相應的$k,即健ID
$k=$this->search($sock);

//如果接收的信息長度小於7,則該client的socket為斷開連接
if($len<7){
//給該client的socket進行斷開操作,並在$this->sockets和$this->users裡面進行刪除
$this->send2($k);
continue;
}
//判斷該socket是否已經握手
if(!$this->users[$k]['shou']){
//如果沒有握手,則進行握手處理
$this->woshou($k,$buffer);
}else{
//走到這裡就是該client發送信息了,對接受到的信息進行uncode處理
$buffer = $this->uncode($buffer,$k);
if($buffer==false){
continue;
}
//如果不為空,則進行消息推送操作
$this->send($k,$buffer);
}
}
}

}

3.伺服器端完成的WebSocket的前期工作後,就等著client連接進行,client建立WebSocket很簡單

var ws = new WebSocket("ws://IP:端口");
//握手監聽函數
ws.onopen=function(){
//狀態為1證明握手成功,然後把client自定義的名字發送過去
if(so.readyState==1){
//握手成功後對服務器發送信息
so.send('type=add&ming='+n);
}
}
//錯誤返回信息函數
ws.onerror = function(){
    console.log("error");
};
//監聽服務器端推送的消息
ws.onmessage = function (msg){
    console.log(msg);
}
//斷開WebSocket連接
ws.onclose = function(){
    ws = false;
}

沒有留言:

張貼留言