วิธีปรับแต่งโปรโตคอล

จริงๆ แล้วการสร้างโปรโตคอลของตัวเองเป็นสิ่งที่ค่อนข้างง่าย โปรโตคอลที่ง่ายมักประกอบด้วยสองส่วน:

  • ตัวระบุที่แยกขอบเขตข้อมูล
  • การกำหนดรูปแบบข้อมูล

ตัวอย่างหนึ่ง

การระบุโปรโตคอล

ที่นี่สมมติว่าตัวระบุที่แยกขอบเขตข้อมูลคืออักขระขึ้นบรรทัดใหม่ "\n" (โปรดทราบว่าข้อมูลในคำร้องไม่สามารถมีอักขระขึ้นบรรทัดใหม่ภายในได้) และรูปแบบข้อมูลคือ Json เช่น ต่อไปนี้คือตัวอย่างของแพ็กเกจคำร้องที่เป็นไปตามกฎนี้

{"type":"message","content":"hello"}

โปรดทราบว่าจุดสิ้นสุดของข้อมูลคำร้องข้างต้นมีอักขระขึ้นบรรทัดใหม่ (ใน PHP ใช้ สตริงในเครื่องหมายคำพูดคู่ "\n" แทน) หมายถึงการสิ้นสุดของคำร้อง

ขั้นตอนการดำเนินการ

ใน Workerman หากต้องการดำเนินการตามโปรโตคอลข้างต้น สมมติว่าชื่อโปรโตคอลคือ JsonNL และโปรเจ็กต์คือ MyApp จึงต้องดำเนินการตามขั้นตอนดังต่อไปนี้

  1. ไฟล์โปรโตคอลควรอยู่ในโฟลเดอร์ Protocols ของโปรเจ็กต์ เช่น ไฟล์ MyApp/Protocols/JsonNL.php

  2. สร้างคลาส JsonNL โดยใช้ namespace Protocols; เป็น namespace และต้องสร้างสาม static methods ได้แก่ input, encode, decode

โปรดทราบ: Workerman จะเรียกใช้สาม static methods นี้โดยอัตโนมัติ เพื่อทำการแยกแพ็กเกจ แพ็กเกจ และการบีบอัด รายละเอียดกระบวนการดูจากคำอธิบายด้านล่าง

ขั้นตอนการโต้ตอบระหว่าง Workerman และคลาสโปรโตคอล

  1. สมมติว่าลูกค้าได้ส่งแพ็กเกจข้อมูลไปยังเซิร์ฟเวอร์ เมื่อเซิร์ฟเวอร์ได้รับข้อมูล (อาจเป็นข้อมูลบางส่วน) จะแม่นยำจะเรียกใช้ method input ของโปรโตคอลเพื่อทำการตรวจสอบความยาวของแพ็กเกจ โดย method input จะส่งค่าความยาว $length กลับไปยัง framework Workerman
  2. framework Workerman จะตรวจสอบว่าค่าความยาว $length ได้รับภายใน buffer หรือไม่ หากยังไม่ได้รับ จะรอสัญญาณข้อมูลต่อไปจนกว่าความยาวข้อมูลใน buffer จะไม่น้อยกว่า $length
  3. เมื่อความยาวข้อมูลใน buffer เพียงพอแล้ว Workerman จะตัดข้อมูลช่วงความยาว $length ออกจาก buffer (ซึ่งเรียกว่า การแยกแพ็กเกจ) และจะเรียกใช้ method decode ของโปรโตคอลเพื่อทำการ บีบอัด ข้อมูลที่บีบอัดแล้วจะเป็น $data
  4. หลังจากบีบอัดแล้ว Workerman จะส่งข้อมูล $data ให้กับธุรกิจอย่างฟังก์ชัน callback onMessage($connection, $data) ซึ่งธุรกิจสามารถใช้ตัวแปร $data ใน onMessage เพื่อจัดการข้อมูลที่ส่งมาจากลูกค้าและได้ข้อมูลที่ครบถ้วนและทำการบีบอัดแล้ว
  5. เมื่อธุรกิจใน onMessage เรียกใช้ method $connection->send($buffer) เพื่อส่งข้อมูลกลับไปยังลูกค้า Workerman จะเรียกใช้ method encode ของโปรโตคอลโดยอัตโนมัติเพื่อทำการบีบอัด $buffer ก่อนส่งกลับไปยังลูกค้า

การดำเนินการเฉพาะ

การดำเนินการ MyApp/Protocols/JsonNL.php

namespace Protocols;
class JsonNL
{
    /**
     * ตรวจสอบความสมบูรณ์ของแพ็กเกจ
     * หากสามารถได้รับความยาวของแพ็กเกจ จะส่งกลับความยาวของแพ็กเกจใน buffer มิฉะนั้นจะส่งกลับ 0 เพื่อรอสัญญาณข้อมูลต่อไป
     * หากโปรโตคอลมีปัญหาจะสามารถส่งกลับ -1 และการเชื่อมต่อของลูกค้าจะถูกตัดขาด
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // รับตำแหน่งของอักขระขึ้นบรรทัดใหม่ "\n"
        $pos = strpos($buffer, "\n");
        // ไม่มีอักขระขึ้นบรรทัดใหม่ ไม่สามารถทราบความยาวแพ็กเกจได้ ส่งกลับ 0 เพื่อรอสัญญาณข้อมูลต่อไป
        if($pos === false)
        {
            return 0;
        }
        // มีอักขระขึ้นบรรทัดใหม่ ส่งกลับความยาวของแพ็กเกจในปัจจุบัน (รวมอักขระขึ้นบรรทัดใหม่)
        return $pos+1;
    }

    /**
     * บีบอัด เมื่อส่งข้อมูลไปยังลูกค้าจะถูกเรียกใช้โดยอัตโนมัติ
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // ทำ json serialization และเพิ่มอักขระขึ้นบรรทัดใหม่เป็นสัญลักษณ์ของการสิ้นสุดคำร้อง
        return json_encode($buffer)."\n";
    }

    /**
     * บีบอัด เมื่อจำนวนไบต์ที่ได้รับเท่ากับค่าที่ส่งกลับจาก input (ค่ามากกว่า 0) จะเรียกใช้โดยอัตโนมัติ
     * และส่งไปยังพารามิเตอร์ $data ของฟังก์ชัน callback onMessage
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // ตัดอักขระขึ้นบรรทัดใหม่ และคืนค่าเป็นอาร์เรย์
        return json_decode(trim($buffer), true);
    }
}

ถึงตอนนี้ การดำเนินการโปรโตคอล JsonNL เสร็จสมบูรณ์ สามารถใช้ในโปรเจ็กต์ MyApp ได้ วิธีการใช้งานเช่นตัวอย่างด้านล่าง

ไฟล์: MyApp\start.php

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

$json_worker = new Worker('JsonNL://0.0.0.0:1234');
$json_worker->onMessage = function(TcpConnection $connection, $data) {

    // $data คือข้อมูลที่ถูกส่งมาจากลูกค้า ข้อมูลผ่านการจัดการจาก JsonNL::decode
    echo $data;

    // ข้อมูลที่ส่งไปยัง $connection->send จะเรียกใช้ method JsonNL::encode โดยอัตโนมัติเพื่อบีบอัดแล้วส่งไปยังลูกค้า
    $connection->send(array('code'=>0, 'msg'=>'ok'));

};
Worker::runAll();
...

คำแนะนำ
Workerman จะพยายามโหลดโปรโตคอลภายใต้ namespace Protocols เช่น new Worker('JsonNL://0.0.0.0:1234') จะพยายามโหลดโปรโตคอล Protocols\JsonNL
หากเกิดข้อผิดพลาด Class 'Protocols\JsonNL' not found โปรดดูที่ การโหลดอัตโนมัติ

คำอธิบายอินเทอร์เฟซโปรโตคอล

คลาสโปรโตคอลที่พัฒนาใน Workerman ต้องมีการ implement สาม static methods ได้แก่ input, encode, decode คำอธิบายอินเทอร์เฟซโปรโตคอลดูได้ที่ Workerman/Protocols/ProtocolInterface.php โดยระบุไว้ดังนี้:

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * โปรโตคอลอินเทอร์เฟซ
 * @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * ใช้ในการแยกแพ็กเกจใน recv_buffer ที่ได้รับ
     *
     * หากสามารถทราบความยาวของแพ็กเกจใน $recv_buffer จะส่งกลับความยาวทั้งหมดของแพ็กเกจ
     * หากไม่เช่นนั้นจะส่งกลับ 0 หมายความว่าต้องการข้อมูลเพิ่มเติมเพื่อทราบความยาวแพ็กเกจ
     * หากส่งกลับ -1 จะหมายถึงคำร้องที่ไม่ถูกต้อง ซึ่งจะทำให้การเชื่อมต่อถูกตัดออก
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * ใช้ในการบีบอัดคำร้อง
     *
     * เมื่อค่าที่ส่งกลับจาก input มากกว่า 0 และ Workerman ได้รับข้อมูลมากพอแล้ว จะเรียกใช้ decode โดยอัตโนมัติ
     * จากนั้นจะทำการเรียกใช้ callback onMessage และจะส่งข้อมูลที่ถูก decode ไปยังพารามิเตอร์ที่สองของฟังก์ชัน callback onMessage
     * กล่าวคือเมื่อได้รับคำร้องจากลูกค้าอย่างครบถ้วน จะเรียกใช้ decode โดยอัตโนมัติ ไม่ต้องเรียกใช้ด้วยตนเองในโค้ดทางธุรกิจ
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * ใช้ในการบีบอัดคำร้อง
     *
     * เมื่อจำเป็นต้องส่งข้อมูลไปยังลูกค้าโดยการเรียกใช้ $connection->send($data); 
     * จะทำการบีบอัด $data โดยใช้ encode one ครั้งหนึ่ง ทำให้เป็นรูปแบบข้อมูลตามโปรโตคอล และส่งไปยังลูกค้า 
     * กล่าวคือ ข้อมูลที่ส่งไปยังลูกค้าจะบีบอัดด้วย encode โดยอัตโนมัติ ไม่ต้องเรียกใช้ด้วยตนเองในโค้ดทางธุรกิจ
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

หมายเหตุ:

ใน Workerman ไม่มีข้อกำหนดอย่างเข้มงวดว่าคลาสโปรโตคอลจะต้อง implement ProtocolInterface จริงๆ แล้ว คลาสโปรโตคอลเพียงมี static methods input, encode, decode ทั้งสามเท่านั้น