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 ทันที

หลักการ:

  1. สร้าง Worker websocket เพื่อรักษาการเชื่อมต่อยาวนานกับ client
  2. สร้าง Worker text ภายใน Worker websocket
  3. Worker websocket และ Worker text อยู่ในกระบวนการเดียวกัน สามารถแชร์การเชื่อมต่อของ client ได้อย่างสะดวก
  4. ระบบ php backend อิสระติดต่อกับ Worker text ผ่านโปรโตคอล text
  5. 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);