初學PHP時,就想著用PHP做個聊天室,在同學面前裝一波<尷尬>。那個時候只會使用簡單的ajax。於是就用了輪訓,那時候就在想為啥伺服器就不能推送,想一想我發一條訊息伺服器直接推送給其他線上的同學多方便【此處省略一百萬字】。
WebSocket 協議在2008年誕生,2011年成為國際標準,所有瀏覽器都已經支援了。所以直接在瀏覽器下寫js就可以和後臺進行互動了。
它的最大特點就是,伺服器可以主動向客戶端推送資訊,客戶端也可以主動向伺服器傳送資訊,是真正的雙向平等對話,屬於伺服器推送技術的一種。
簡單使用示例
<?php
/**
* websocket 簡單控制多人對話
* 使用session方案,根據與不同人的、組的談話設定session問題
* 並根據session進行迭代推送
* 更好的替代方案是使用redis
*/
//建立websocket伺服器物件,監聽0.0.0.0:9502埠
$ws = new swoole_websocket_server("0.0.0.0", 9502);
//監聽WebSocket連線開啟事件
$ws->on('open', function ($ws, $request) {
var_dump($request->fd, $request->get, $request->server);
$ws->push($request->fd, "hello, welcome\n");
});
/**
* 監聽WebSocket訊息事件
* 每次當js push資訊的時候,就會觸發這個函式
*/
$ws->on('message', function ($ws, $frame) {
echo "Message: {$frame->data}\n";
// $ws->push($frame->fd, "server: {$frame->data}");
// var_dump($ws->connections);
// 獲取所有的連結$ws->connections
foreach($ws->connections as $fd)
{
$ws->push($fd, "hello{$fd}\n");
}
});
/**
* 監聽WebSocket連線關閉事件
*/
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
});
/**
* 執行指令碼
*/
$ws->start();
複製程式碼
由上面的程式碼可以看出swoole中websocket分為5個步驟
- 首先要建立swoole_websocket_sever例項,配置好需要監聽的地址和埠等
- socket監聽程式碼$ws->on()
其中配置了多個事件 open【監聽初次連線】、message【監聽使用者推送】、close【監聽關閉事件】等,詳細的事件請查閱相關資料T_T!
- 啟動websocket,$ws->start();
碰到的坑:
- webSocket關於屬性connections報錯
系統缺少pcre組建,而connections迭代器需要依賴這個庫
- 安裝pcre-dev(或者pcre-devel)
- 重新編譯安裝swoole
- 當突然斷網的時候,來不及關閉連結怎麼辦<有待測試>
設定心跳檢測時間
array( 'heartbeat_idle_time' => 600, 'heartbeat_check_interval' => 60 ); 複製程式碼
- 安全規範
WebSocket 不應當用於混合的上下文環境;也就是說,不應該在用HTTPS載入的頁面中開啟非安全版本的WebSocket,反之亦然。而實際上一些瀏覽器也明確禁止這一行為,包括 Firefox 8 及更高版本。
- 關於客戶端連線ID:fd
官方文件解釋:
- $fd是TCP客戶端連線的識別符號,在Server程式中是唯一的
- fd 是一個自增數字,範圍是1 ~ 1600萬,fd超過1600萬後會自動從1開始進行復用
- $fd是複用的,當連線關閉後fd會被新進入的連線複用【ps:沒有的互用就給他關閉】
- 正在維持的TCP連線fd不會被複用
- fd好像不支援修改滴
swoole之程式碼熱更新
疑問
- 客戶端請求之後swoole是怎樣執行的
首先:
一個最基礎的Swoole Server,需要有3個程式,分別是Master程式、Manager程式和Worker程式。
其次:
事實上,一個多程式模式下的Swoole Server中,有且只有一個Master程式;有且只有一個Manager程式;卻可以有n個Worker程式。
首先,Master程式是一個多執行緒程式,其中有一組非常重要的執行緒,叫做Reactor執行緒(組),每當一個客戶端連線上伺服器的時候,都會由Master程式從已有的Reactor執行緒中,根據一定規則挑選一個,專門負責向這個客戶端提供維持連結、處理網路IO與收發資料等服務。
如圖:
閱讀這篇Swoole的程式模型你可能會對本疑問解惑
小demo[可以根據自己的需要改的更好點]
WebSocket.php
<?php
class WebSocket
{
public $server;
public function __construct()
{
$this->server = new swoole_websocket_server("0.0.0.0", 9502);
$this->server->on('open', array($this, 'onOpen'));
$this->server->on('message', array($this, 'onMessage'));
$this->server->on('close', [$this, 'onClose']);
$this->server->start();
}
/**
* Open the connection
*
* @param swoole_websocket_server $server
* @param [type] $request
* @return void
*/
public function onOpen(swoole_websocket_server $server, $request)
{
//
}
/**
* Receive messages
* 當客戶端呼叫send的時候觸發message回掉函式
* @param swoole_websocket_server $server
* @param [type] $frame
* @return void
*/
public function onMessage(swoole_websocket_server $server, $frame)
{
$result = json_decode($frame->data);
switch ($result->type) {
case "connect":
$data = "Welcome {$result->text} to join us";
$this->iteration($server->connections, $data, "connect");
break;
case "message":
$this->iteration($server->connections, $result->text, "message");
break;
}
}
/**
* disconnect
*
* @param [type] $ser
* @param [type] $fd
* @return void
*/
public function onClose($ser, $fd)
{
echo "the cline {$fd} is close";
}
/**
* Radio
*
* @param [type] $connect
* @param [type] $data
* @param [type] $status
* @return void
*/
public function iteration($connect, $data, $status)
{
foreach ($connect as $fd) {
// console.log();
$this->server->push($fd, $this->JSON(array(
"type" => $status,
"data" => $data
)));
}
}
}
$server = new WebSocket();
複製程式碼
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- <title>精美CSS3聊天視窗DEMO演示</title> -->
<title>精美CSS3聊天視窗</title>
<link rel="stylesheet" href="css/style.css" media="screen" type="text/css" />
<style>
</style>
</head>
<body>
<div id="convo" data-from="Sonu Joshi">
<ul class="chat-thread">
<li class="left">Are we meeting today?</li>
<li class="right">yes, what time suits you?</li>
</ul>
<div class="inputButton">
<div>
<input class="input" type="text" placeholder="Search for...">
<input class="submit" type="button" value="Submit">
</div>
</div>
</div>
<div style="text-align:center;clear:both">
</div>
<div class="credits">
<a href="http://codepen.io/clintioo/pen/HAkjq">Original Pen</a> by
<a href="http://codepen.io/clintioo/pen/HAkjq">clintioo</a>
</div>
<script>
var dialog = document.querySelector('ul');
var infoInput = document.querySelectorAll("input");
var wsServer = 'ws://127.0.0.1:9502';
var websocket = new WebSocket(wsServer);
websocket.onopen = (evt) => {
console.log(evt);
websocket.send(JSON.stringify({
type: "connect",
text: "I‘m fine,Think you",
// "id":"",
// date: ""
}));
};
websocket.onmessage = (evt) => {
infoInput[0].value = "";
console.log(evt);
var info = eval("(" + evt.data + ")");
var createDom = document.createElement("li");
createDom.innerHTML = info['data'];
createDom.className = "left";
dialog.append(createDom);
}
websocket.onerror = function (evt, e) {
console.log('Error occured: ' + evt.data);
};
infoInput[1].addEventListener("click", () => {
if (infoInput[0].value) {
websocket.send(JSON.stringify({
type: "message",
text: infoInput[0].value,
}));
} else {
alert("傳送資訊不允許為空不允許為空");
}
});
document.onkeydown = (event) => {
var e = event || window.event || arguments.callee.caller.arguments[0];
if(e && e.keyCode==13){
infoInput[1].click();
}
};
</script>
</body>
</html>
複製程式碼
效果圖
使用的是swoole擴充套件 時:2018-06-07