與常規web開發不同,使用socket開發可以擺脫http的限制。可自定義協議,使用長連接、PHP代碼常駐內存等。學習資料來源於workerman官方視頻與文檔.
通常創建一個socket服務包括這幾個簡單的步驟:
1.創建一個socket套接字,監聽在某協議的某個端口,如:tcp的9865端口,為了是外網可以訪問,地址為0.0.0.0,監聽地址應為這種格式tcp://0.0.0.0:9865
2.將監聽socket設置為非阻塞,若不設置,程序會在客戶端連接沒有發消息時阻塞。
3.程序阻塞在I/0復用函數stream_select,一旦有讀取到的新事件則進行處理。
4.處理客戶端發送的數據。若讀取的socket連接為監聽socket表示有新的連接,否則為當前連接發送了數據。若讀取到空或返回了false,則表示客戶端斷開。
一個簡單的demo:
<?php
class Worker{
//監聽socket
protected $socket = NULL;
//所有的socket連接
protected $allSockets = array();
//連接事件回調
public $onConnect = NULL;
//斷線事件回調
public $onClose = NULL;
//接收消息事件回調
public $onMessage = NULL;
public function __construct($socket_address) {
//創建一個socket監聽
$this->socket = stream_socket_server($socket_address);
//設置為非阻塞
stream_set_blocking($this->socket, 0);
//將socket監聽加入allSockets
$this->allSockets[(int)$this->socket] = $this->socket;
}
public function run() {
while(true) {
//不監聽可寫事件與帶外數據事件
$write = $except = array();
//監聽所有的socket事件
$read = $this->allSockets;
//整個進程阻塞在這里,持續監聽可讀事件
//此處參數均為引用傳遞,在函數中會改變傳值
stream_select($read, $write, $except, 60);
//處理所有可讀事件
foreach ($read as $index => $socket) {
//如果是監聽socket,此處表示有新的連接
if ($socket === $this->socket) {
//通過stream_socket_accept獲取新的連接
$new_conn_socket = stream_socket_accept($socket);
if ($this->onConnect) {
//觸發連接事件的回調,並將當前連接傳遞給回掉函數
call_user_func($this->onConnect, $socket);
}
//記錄此socket連接,以便於sream_select監聽可讀事件
$this->allSockets[(int)$new_conn_socket] = $new_conn_socket;
} else
//如果可讀事件不為監聽socket,則表示對應客戶端有數據發過來
{
//從連接中讀取數據
$buffer = fread($socket, 65535);
//如果數據為空,表示客戶端已經斷開連接
if ('' === $buffer || false === $buffer) {
//嘗試觸發onClose回調
if ($this->onClose) {
call_user_func($this->onClose, $socket);
}
fclose($socket);
//關閉socket連接並從allSockets中刪除
unset($this->allSockets[(int)$socket]);
continue;
}
//表示一個正常的連接,已經讀取到消息,交給回掉函數處理
if ($this->onMessage) {
call_user_func($this->onMessage, $socket, $buffer);
}
}
}
}
}
}
$worker = new Worker('tcp://0.0.0.0:9865');
$worker->onConnect = function ($conn) {
echo '新的連接來了';
};
$worker->onClose = function ($conn) {
echo '連接斷開了';
};
$worker->onMessage = function ($conn, $message) {
$http_resonse = "HTTP/1.1 200 OK\r\n";
$http_resonse .= "Connection: keep-alive\r\n";
$http_resonse .= "Server: php socket server\r\n";
$http_resonse .= "Content-length: 11\r\n\r\n";
$http_resonse .= "hello world";
fwrite($conn, $http_resonse);
};
$worker->run();
在cli環境下運行腳本:$ php worker.php
,
然后使用瀏覽器訪問本地的9865端口即可看到我們的hello world
