listen
void Worker::listen(void)
ใช้เพื่อดำเนินการฟังหลังจากที่สร้างตัวอย่าง Worker แล้ว
วิธีนี้ใช้เพื่อสร้าง Worker ตัวใหม่แบบไดนามิกหลังจากที่กระบวนการ Worker เริ่มทำงาน ซึ่งสามารถทำให้กระบวนการเดียวฟังหลายพอร์ตได้และรองรับหลายโปรโตคอล ควรสังเกตว่าวิธีนี้เพียงแค่เพิ่มการฟังในกระบวนการปัจจุบัน โดยไม่สร้างกระบวนการใหม่แบบไดนามิก และจะไม่เรียกใช้วิธี onWorkerStart
ตัวอย่างเช่น หาก Worker HTTP เริ่มทำงานและสร้าง Worker websocket ขึ้นมา กระบวนการนี้จะสามารถเข้าถึงได้ผ่านโปรโตคอล HTTP และยังสามารถเข้าถึงได้ผ่านโปรโตคอล websocket ในเมื่อ Worker websocket และ Worker HTTP อยู่ในกระบวนการเดียวกัน พวกเขาสามารถเข้าถึงตัวแปรหน่วยความจำร่วมกันและแชร์การเชื่อมต่อ socket ทั้งหมด สามารถรับคำขอ HTTP จากนั้นจัดการกับคลients websocket เพื่อทำการส่งข้อมูลไปยังลูกค้า
หมายเหตุ:
ถ้า PHP เวอร์ชัน <= 7.0 จะไม่รองรับการสร้าง Worker ที่ฟังพอร์ตเดียวกันในหลาย subprocess ตัวอย่างเช่น หากกระบวนการ A สร้าง Worker ที่ฟังพอร์ต 2016 แล้ว กระบวนการ B จะไม่สามารถสร้าง Worker ที่ฟังพอร์ต 2016 ได้อีก มิฉะนั้นจะเกิดข้อผิดพลาด Address already in use ตัวอย่างเช่น โค้ดด้านล่างนี้คือการ ไม่สามารถ ทำงานได้
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker();
// 4 กระบวนการ
$worker->count = 4;
// หลังจากเริ่มกระบวนการแต่ละตัวให้เพิ่ม Worker ที่ฟังในกระบวนการปัจจุบัน
$worker->onWorkerStart = function($worker)
{
/**
* สร้าง Worker ฟังพอร์ต 2016 ขึ้นเมื่อเริ่มกระบวนการ 4 ตัว
* เมื่อทำการเรียก worker->listen() จะเกิดข้อผิดพลาด Address already in use
* หาก worker->count=1 จะไม่เกิดข้อผิดพลาด
*/
$inner_worker = new Worker('http://0.0.0.0:2016');
$inner_worker->onMessage = 'on_message';
// ทำการฟัง ที่นี่จะเกิดข้อผิดพลาด Address already in use
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message(TcpConnection $connection, $data)
{
$connection->send("hello\n");
}
// เรียกใช้งาน worker
Worker::runAll();
หากเวอร์ชัน PHP ของคุณ >= 7.0 คุณสามารถตั้งค่า Worker->reusePort=true ซึ่งสามารถทำให้ subprocess หลายตัวสร้าง Worker ที่ฟังพอร์ตเดียวกันได้ ดูตัวอย่างด้านล่าง:
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$worker = new Worker('text://0.0.0.0:2015');
// 4 กระบวนการ
$worker->count = 4;
// หลังจากเริ่มกระบวนการแต่ละตัวให้เพิ่ม Worker ที่ฟังในกระบวนการปัจจุบัน
$worker->onWorkerStart = function($worker)
{
$inner_worker = new Worker('http://0.0.0.0:2016');
// ตั้งค่าการใช้งานพอร์ตซ้ำ สามารถสร้าง Worker ที่ฟังพอร์ตเดียวกันได้ (ต้องการ PHP >= 7.0)
$inner_worker->reusePort = true;
$inner_worker->onMessage = 'on_message';
// ทำการฟัง ที่นี่จะไม่มีข้อผิดพลาด
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message(TcpConnection $connection, $data)
{
$connection->send("hello\n");
}
// เรียกใช้งาน worker ทั้งหมด
Worker::runAll();
ตัวอย่าง php backend ที่ส่งข้อความไปยัง client ทันที
หลักการ:
- สร้าง Worker websocket เพื่อรักษาการเชื่อมต่อยาวนานกับ client
- สร้าง Worker text ภายใน Worker websocket
- Worker websocket และ Worker text อยู่ในกระบวนการเดียวกัน สามารถแชร์การเชื่อมต่อของ client ได้อย่างสะดวก
- ระบบ php backend อิสระติดต่อกับ Worker text ผ่านโปรโตคอล text
- Worker text ทำการจัดการการเชื่อมต่อ websocket เพื่อส่งข้อมูล
โค้ดและขั้นตอน
push.php
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// เริ่มต้น Worker container ฟังพอร์ต 1234
$worker = new Worker('websocket://0.0.0.0:1234');
/*
* หมายเหตุที่นี่จำนวนกระบวนการต้องตั้งค่าเป็น 1
*/
$worker->count = 1;
// เมื่อตัว Worker ทำงานแล้วให้สร้าง Worker text เพื่อเปิดพอร์ตการสื่อสารภายใน
$worker->onWorkerStart = function($worker)
{
// เปิดพอร์ตภายในเพื่อง่ายต่อการส่งข้อมูลจากระบบภายใน รูปแบบโปรโตคอล Text เป็นข้อความ + ตัวขึ้นบรรทัดใหม่
$inner_text_worker = new Worker('text://0.0.0.0:5678');
$inner_text_worker->onMessage = function(TcpConnection $connection, $buffer)
{
// รูปแบบอาร์เรย์ $data ที่ประกอบด้วย uid แสดงให้เห็นว่าจะส่งข้อมูลไปยัง uid ไหน
$data = json_decode($buffer, true);
$uid = $data['uid'];
// ส่งข้อความไปยัง uid ของหน้าเว็บผ่าน workerman
$ret = sendMessageByUid($uid, $buffer);
// ส่งกลับผลการส่ง
$connection->send($ret ? 'ok' : 'fail');
};
// ## ทำการฟัง ##
$inner_text_worker->listen();
};
// เพิ่มคุณสมบัติใหม่เพื่อเก็บการแมพ uid กับ connection
$worker->uidConnections = array();
// ฟังก์ชัน callback ที่เรียกใช้งานเมื่อมี client ส่งข้อความ
$worker->onMessage = function(TcpConnection $connection, $data)
{
global $worker;
// ตรวจสอบว่า client ปัจจุบันได้ทำการยืนยันแล้วหรือไม่ คือได้ตั้งค่า uid หรือไม่
if(!isset($connection->uid))
{
// หากยังไม่ได้ยืนยันนับว่าแพ็กเกจแรกเป็น uid (เพื่อความสะดวกในการแสดงตัวอย่าง ไม่มีการยืนยันที่แท้จริง)
$connection->uid = $data;
/* เก็บ uid ไปยังการแมพ connection ด้วยวิธีนี้สามารถค้นหา connection ผ่าน uid ได้อย่างง่ายดาย
* ทำให้สามารถส่งข้อมูลไปยัง uid ที่เฉพาะเจาะจง
*/
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
// เมื่อ client เชื่อมต่อหลุด
$worker->onClose = function(TcpConnection $connection)
{
global $worker;
if(isset($connection->uid))
{
// เมื่อต่อเชื่อมหลุดให้ลบการแมพ
unset($worker->uidConnections[$connection->uid]);
}
};
// ส่งข้อมูลไปยังผู้ใช้ที่ยืนยันทั้งหมด
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// ส่งข้อมูลเฉพาะ uid
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
// เรียกใช้งาน worker ทั้งหมด
Worker::runAll();
เริ่มบริการ backend
php push.php start -d
โค้ด js ที่ใช้รับการส่งข้อความจาก frontend
var ws = new WebSocket('ws://127.0.0.1:1234');
ws.onopen = function(){
var uid = 'uid1';
ws.send(uid);
};
ws.onmessage = function(e){
alert(e.data);
};
โค้ดในการส่งข้อความจาก backend
// สร้างการเชื่อมต่อ socket กับพอร์ตการส่งข้อมูลภายใน
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// ข้อมูลที่ส่ง กระทู้ uid ประกอบอยู่เพื่อแสดงว่ามีการส่งให้ uid นี้
$data = array('uid'=>'uid1', 'percent'=>'88%');
// ส่งข้อมูล โดยพอร์ต 5678 เป็นพอร์ตสำหรับโปรโตคอล Text ข้อมูลต้องมีบรรทัดใหม่ที่ท้าย
fwrite($client, json_encode($data)."\n");
// อ่านผลการส่ง
echo fread($client, 8192);