文章來自微信公眾號:PHP自學中心
學習實時訊息推送之前,先了解以下的這些概念,這些也是你必須要懂的。
TCP/IPTCP/IP
是個協議組,可分為三個層次:網路層、傳輸層和應用層。
在網路層有IP
協議、ICMP
協議、ARP
協議、RARP
協議和BOOTP
協議。
在傳輸層中有TCP
協議與UDP
協議。
在應用層有:TCP
包括FTP
、HTTP
、TELNET
、SMTP
等協議UDP
包括DNS
、TFTP
等協議
短連線
連線->傳輸資料->關閉連線HTTP
是無狀態的,瀏覽器和伺服器每進行一次HTTP
操作,就建立一次連線,但任務結束就中斷連線。
也可以這樣說:短連線是指SOCKET
連線後傳送後接收完資料後馬上斷開連線。
長連線
連線->傳輸資料->保持連線 -> 傳輸資料-> 。。。->關閉連線。
長連線指建立SOCKET
連線後不管是否使用都保持連線,但安全性較差。
http的長連線HTTP
也可以建立長連線的,使用Connection:keep-alive,HTTP 1.1
預設進行持久連線。HTTP1.1
和HTTP1.0
相比較而言,最大的區別就是增加了持久連線支援(貌似最新的http1.0
可以顯示的指定keep-alive
),但還是無狀態的,或者說是不可以信任的。
什麼時候用長連線,短連線?
長連線多用於操作頻繁,點對點的通訊,而且連線數不能太多情況,。每個TCP連線都需要三步握手,這需要時間,如果每個操作都是先連線,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接傳送資料包就OK了,不用建立TCP
連線。例如:資料庫的連線用長連線, 如果用短連線頻繁的通訊會造成socket
錯誤,而且頻繁的socket
建立也是對資源的浪費。
而像WEB網站的http
服務一般都用短連結,因為長連線對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連線用短連線會更省一些資源,如果用長連線,而且同時有成千上萬的使用者,如果每個使用者都佔用一個連線的話,那可想而知吧。所以併發量大,但每個使用者無需頻繁操作情況下需用短連好。
workerman是啥?Workerman
是一款純PHP
開發的開源高效能的PHP socket
伺服器框架。被廣泛的用於手機app
、移動通訊,微信小程式,手遊服務端、網路遊戲、PHP聊天室、硬體通訊、智慧家居、車聯網、物聯網等領域的開發。支援TCP
長連線,支援Websocket
、HTTP
等協議,支援自定義協議。擁有非同步Mysql
、非同步Redis
、非同步Http
、非同步訊息佇列等眾多高效能元件。
開始步入正題:為了達到實時通訊,很多時候我們採用了ajax
輪詢機制,後面可以採用workerman
方式來實現.本文與ThinkPHP
與Workerman
為例):
1、ThinkPHP
與Workerman
是兩個獨立的系統,獨立部署(可部署在不同伺服器),互不干擾。
2、ThinkPHP
以HTTP
協議提供網頁頁面在瀏覽器渲染展示。
3、ThinkPHP
提供的頁面的js
發起websocket
連線,連線workerman
4、連線後給Workerman
傳送一個資料包(包含使用者名稱密碼或者某種token
串)用於驗證websocket
連線屬於哪個使用者。
5、僅在ThinkPHP
需要向瀏覽器推送資料時,才呼叫workerman
的socket
介面推送資料。
6、其餘請求還是按照原本ThinkPHP
的HTTP
方式呼叫處理。
總結:
把Workerman
作為一個可以向瀏覽器推送的通道,僅僅在需要向瀏覽器推送資料時才呼叫Workerman
介面完成推送。業務邏輯全部在ThinkPHP中完成。
ok,到這裡,把workerman
容器跑起來,注意這裡是CLI
模式執行
然後再我們專案接收資訊中這麼寫,附上程式碼
<script>
// 連線服務端
var socket = io('http://127.0.0.1:2120');
// uid可以是自己網站的使用者id,以便針對uid推送
uid = 123;
// socket連線後以uid登入
socket.on('connect', function(){
socket.emit('login', uid);
});
// 後端推送來訊息時
socket.on('new_msg', function(msg){
console.log("收到訊息:"+msg); //自己業務邏輯處理
});
</script>
接著,我們在使用者向使用者傳送資訊的時候新增
// 指明給誰推送,為空表示向所有線上使用者推送
$to_uid = "123";
// 推送的url地址
$push_api_url = "http://127.0.0.1:2121/";
$post_data = array(
"type" => "publish",
"content" => "資料",
"to" => $to_uid,
);
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $push_api_url );
curl_setopt ( $ch, CURLOPT_POST, 1 );
curl_setopt ( $ch, CURLOPT_HEADER, 0 );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $post_data );
curl_setopt ($ch, CURLOPT_HTTPHEADER, array("Expect:"));
$return = curl_exec ( $ch );
curl_close ( $ch );
var_export($return);
其中,workerman
裡面的推送核心程式碼實現
// 全域性陣列儲存uid線上資料
$uidConnectionMap = array();
// 記錄最後一次廣播的線上使用者數
$last_online_count = 0;
// PHPSocketIO服務
$sender_io = new SocketIO(2120);
// 客戶端發起連線事件時,設定連線socket的各種事件回撥
// 當$sender_io啟動後監聽一個http埠,透過這個埠可以給任意uid或者所有uid推送資料
$sender_io->on('workerStart', function(){
// 監聽一個http埠
$inner_http_worker = new Worker('http://0.0.0.0:2121');
// 當http客戶端發來資料時觸發
$inner_http_worker->onMessage = function($http_connection, $data){
global $uidConnectionMap;
$_POST = $_POST ? $_POST : $_GET;
// 推送資料的url格式 type=publish&to=uid&content=xxxx
switch(@$_POST['type']){
case 'publish':
global $sender_io;
$to = @$_POST['to'];
$_POST['content'] = htmlspecialchars(@$_POST['content']);
// 有指定uid則向uid所在socket組傳送資料
if($to){
$sender_io->to($to)->emit('new_msg', $_POST['content']);
// 否則向所有uid推送資料
}else{
$sender_io->emit('new_msg', @$_POST['content']);
}
// http介面返回,如果使用者離線socket返回fail
if($to && !isset($uidConnectionMap[$to])){
return $http_connection->send('offline');
}else{
return $http_connection->send('ok');
}
}
return $http_connection->send('fail');
};
});
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
以上核心程式碼已經提供,大家可以試著去實踐一下
本作品採用《CC 協議》,轉載必須註明作者和本文連結