Cách tùy chỉnh giao thức

Trên thực tế, việc xây dựng giao thức của riêng bạn là một việc khá đơn giản. Một giao thức đơn giản thường bao gồm hai phần:

  • Định danh phân biệt ranh giới dữ liệu
  • Định nghĩa định dạng dữ liệu

Một ví dụ

Định nghĩa giao thức

Giả sử định danh phân biệt ranh giới dữ liệu là ký tự xuống dòng "\n" (lưu ý dữ liệu yêu cầu không được chứa ký tự xuống dòng bên trong), định dạng dữ liệu là Json, ví dụ dưới đây là một gói yêu cầu tuân thủ quy tắc này.

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

Lưu ý rằng dữ liệu yêu cầu ở trên có một ký tự xuống dòng ở cuối (trong PHP được biểu diễn bằng chuỗi dấu nháy kép "\n"), đánh dấu kết thúc một yêu cầu.

Các bước thực hiện

Trong Workerman, nếu bạn muốn thực hiện giao thức trên, giả sử tên giao thức là JsonNL và dự án là MyApp, bạn cần thực hiện các bước sau

  1. Đặt tệp giao thức vào thư mục Protocols của dự án, ví dụ tệp MyApp/Protocols/JsonNL.php

  2. Thực hiện lớp JsonNL với namespace Protocols; làm không gian tên, cần phải thực hiện ba phương thức tĩnh là input, encode, decode

Lưu ý: workerman sẽ tự động gọi ba phương thức tĩnh này để thực hiện phân gói, giải gói và đóng gói. Quy trình cụ thể xem hướng dẫn quy trình thực hiện bên dưới.

Quy trình tương tác giữa workerman và lớp giao thức

  1. Giả sử client gửi một gói dữ liệu đến server, server nhận dữ liệu (có thể là một phần dữ liệu) và sẽ ngay lập tức gọi phương thức input của giao thức để kiểm tra độ dài của gói này, phương thức input sẽ trả về giá trị độ dài $length cho framework workerman.
  2. Framework workerman sẽ nhận giá trị $length này và kiểm tra xem liệu trong bộ đệm dữ liệu hiện tại đã nhận đủ dữ liệu dài $length hay chưa, nếu chưa thì sẽ tiếp tục chờ dữ liệu cho đến khi độ dài dữ liệu trong bộ đệm lớn hơn hoặc bằng $length.
  3. Khi độ dài dữ liệu trong bộ đệm đủ, workerman sẽ trích xuất $length dữ liệu từ bộ đệm (tức là phân gói) và gọi phương thức decode của giao thức để giải gói, dữ liệu đã được giải gói sẽ là $data.
  4. Sau khi giải gói, workerman sẽ truyền dữ liệu $data cho callback onMessage($connection, $data) để xử lý, trong các business, bạn có thể sử dụng biến $data để nhận được dữ liệu đã được giải gói hoàn chỉnh từ client.
  5. Khi trong callback onMessage cần gọi phương thức $connection->send($buffer) để gửi dữ liệu cho client, workerman sẽ tự động gọi phương thức encode của giao thức để đóng gói dữ liệu $buffer rồi gửi cho client.

Thực hiện cụ thể

Thực hiện MyApp/Protocols/JsonNL.php

namespace Protocols;
class JsonNL
{
    /**
     * Kiểm tra tính toàn vẹn của gói
     * Nếu có thể xác định được độ dài của gói, trả về độ dài của gói trong buffer, nếu không trả về 0 để tiếp tục chờ dữ liệu
     * Nếu có vấn đề với giao thức, có thể trả về -1, kết nối client hiện tại sẽ bị ngắt
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // Lấy vị trí của ký tự xuống dòng "\n"
        $pos = strpos($buffer, "\n");
        // Không có ký tự xuống dòng, không thể xác định độ dài gói, trả về 0 để tiếp tục chờ dữ liệu
        if($pos === false)
        {
            return 0;
        }
        // Có ký tự xuống dòng, trả về độ dài của gói hiện tại (bao gồm ký tự xuống dòng)
        return $pos+1;
    }

    /**
     * Đóng gói, sẽ tự động được gọi khi gửi dữ liệu tới client
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // Chuyển đổi thành json và thêm ký tự xuống dòng làm dấu hiệu kết thúc yêu cầu
        return json_encode($buffer)."\n";
    }

    /**
     * Giải gói, sẽ tự động gọi khi số byte dữ liệu nhận được bằng với giá trị trả về của input (lớn hơn 0)
     * và được truyền cho tham số $data của hàm callback onMessage
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // Bỏ ký tự xuống dòng, khôi phục thành mảng
        return json_decode(trim($buffer), true);
    }
}

Đến đây, giao thức JsonNL đã hoàn tất và có thể sử dụng trong dự án MyApp, phương pháp sử dụng như sau

Tệp: 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 là dữ liệu do client gửi tới, dữ liệu đã qua xử lý JsonNL::decode
    echo $data;

    // Dữ liệu gửi qua $connection->send sẽ tự động gọi phương thức JsonNL::encode để đóng gói, sau đó gửi tới client
    $connection->send(array('code'=>0, 'msg'=>'ok'));

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

Lưu ý
Workerman sẽ cố gắng tải các giao thức trong không gian tên Protocols, ví dụ new Worker('JsonNL://0.0.0.0:1234') sẽ cố gắng tải giao thức Protocols\JsonNL.
Nếu báo lỗi Class 'Protocols\JsonNL' not found, hãy tham khảo Tải tự động để thực hiện tải tự động.

Giải thích giao thức

Trong Workerman, các lớp giao thức phát triển phải thực hiện ba phương thức tĩnh: input, encode, decode. Giải thích giao thức xem trong Workerman/Protocols/ProtocolInterface.php, được định nghĩa như sau:

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * Giao thức interface
* @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * Dùng để phân gói trong recv_buffer đã nhận
     *
     * Nếu có thể nhận được độ dài gói yêu cầu trong $recv_buffer thì trả về độ dài của toàn bộ gói
     * Nếu không thì trả về 0, có nghĩa là cần thêm dữ liệu để nhận được độ dài của gói yêu cầu hiện tại
     * Nếu trả về -1, có nghĩa là yêu cầu không hợp lệ, thì kết nối sẽ bị ngắt
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * Dùng để giải gói yêu cầu
     *
     * Khi giá trị trả về của input lớn hơn 0 và Workerman đã nhận đủ dữ liệu, sẽ tự động gọi decode
     * Sau đó kích hoạt callback onMessage và truyền dữ liệu đã giải mã từ decode cho tham số thứ hai của callback onMessage
     * Điều này có nghĩa là khi nhận được gói yêu cầu hoàn chỉnh từ client, sẽ tự động gọi decode để giải mã mà không cần gọi thủ công trong mã business
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * Dùng để đóng gói yêu cầu
     *
     * Khi cần gửi dữ liệu tới client tức là gọi $connection->send($data); 
     * Sẽ tự động đóng gói $data bằng encode, biến nó thành định dạng dữ liệu phù hợp với giao thức và sau đó gửi tới client
     * Điều này có nghĩa là dữ liệu gửi cho client sẽ tự động được đóng gói bởi encode, mà không cần gọi thủ công trong mã business
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

Lưu ý:

Không có yêu cầu nghiêm ngặt rằng lớp giao thức trong Workerman phải triển khai ProtocolInterface, thực tế là chỉ cần lớp giao thức có ba phương thức tĩnh là input, encode, decode là đủ.