Một số ví dụ

Ví dụ 1

Định nghĩa giao thức

  • Phần đầu cố định 10 byte dùng để lưu chiều dài toàn bộ gói dữ liệu, số bit không đủ sẽ được bổ sung bằng 0
  • Định dạng dữ liệu là xml

Mẫu gói dữ liệu

0000000121<?xml version="1.0" encoding="ISO-8859-1"?>
<request>
    <module>user</module>
    <action>getInfo</action>
</request>

Trong đó 0000000121 đại diện cho chiều dài toàn bộ gói dữ liệu, ngay sau đó là nội dung của gói dữ liệu định dạng xml.

Triển khai giao thức

namespace Protocols;
class XmlProtocol
{
    public static function input($recv_buffer)
    {
        if(strlen($recv_buffer) < 10)
        {
            // Không đủ 10 byte, trả về 0 để tiếp tục chờ dữ liệu
            return 0;
        }
        // Trả về chiều dài gói, chiều dài gói bao gồm chiều dài dữ liệu phần đầu + chiều dài nội dung gói
        $total_len = base_convert(substr($recv_buffer, 0, 10), 10, 10);
        return $total_len;
    }

    public static function decode($recv_buffer)
    {
        // Nội dung gói yêu cầu
        $body = substr($recv_buffer, 10);
        return simplexml_load_string($body);
    }

    public static function encode($xml_string)
    {
        // Chiều dài phần nội dung + chiều dài phần đầu
        $total_length = strlen($xml_string)+10;
        // Phần chiều dài đủ 10 byte, số bit không đủ sẽ được bổ sung bằng 0
        $total_length_str = str_pad($total_length, 10, '0', STR_PAD_LEFT);
        // Trả về dữ liệu
        return $total_length_str . $xml_string;
    }
}

Ví dụ 2

Định nghĩa giao thức

  • Phần đầu 4 byte theo thứ tự byte mạng unsigned int, đánh dấu chiều dài toàn bộ gói
  • Phần dữ liệu là chuỗi Json

Mẫu gói dữ liệu

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

Trong đó bốn byte đầu là ký tự * đại diện cho một dữ liệu unsigned int theo thứ tự byte mạng, là ký tự không thể nhìn thấy, ngay sau đó là dữ liệu gói theo định dạng Json.

Triển khai giao thức

namespace Protocols;
class JsonInt
{
    public static function input($recv_buffer)
    {
        // Dữ liệu nhận được chưa đủ 4 byte, không thể biết chiều dài gói, trả về 0 tiếp tục chờ dữ liệu
        if(strlen($recv_buffer)<4)
        {
            return 0;
        }
        // Sử dụng hàm unpack để chuyển đổi 4 byte đầu thành số, 4 byte đầu chính là chiều dài của toàn bộ gói dữ liệu
        $unpack_data = unpack('Ntotal_length', $recv_buffer);
        return $unpack_data['total_length'];
    }

    public static function decode($recv_buffer)
    {
        // Bỏ qua 4 byte đầu, lấy dữ liệu Json của gói
        $body_json_str = substr($recv_buffer, 4);
        // Giải mã json
        return json_decode($body_json_str, true);
    }

    public static function encode($data)
    {
        // Giải mã Json tạo ra nội dung gói
        $body_json_str = json_encode($data);
        // Tính chiều dài toàn bộ gói, 4 byte đầu + số byte của phần nội dung gói
        $total_length = 4 + strlen($body_json_str);
        // Trả về dữ liệu đã đóng gói
        return pack('N',$total_length) . $body_json_str;
    }
}

Ví dụ 3 (Sử dụng giao thức nhị phân để tải lên tệp)

Định nghĩa giao thức

struct
{
  unsigned int total_len;  // Chiều dài toàn bộ gói, thứ tự byte mạng
  char         name_len;   // Chiều dài tên tệp
  char         name[name_len]; // Tên tệp
  char         file[total_len - BinaryTransfer::PACKAGE_HEAD_LEN - name_len]; // Dữ liệu tệp
}

Mẫu giao thức

 *****logo.png****************** 

Trong đó bốn byte đầu ký hiệu đại diện cho một dữ liệu unsigned int theo thứ tự byte mạng, là ký tự không thể nhìn thấy, ký tự thứ 5 là một byte lưu chiều dài tên tệp, ngay sau đó là tên tệp, tiếp theo là dữ liệu tệp nhị phân gốc.

Triển khai giao thức

namespace Protocols;
class BinaryTransfer
{
    // Chiều dài phần đầu giao thức
    const PACKAGE_HEAD_LEN = 5;

    public static function input($recv_buffer)
    {
        // Nếu không đủ chiều dài của một phần đầu giao thức, tiếp tục chờ
        if(strlen($recv_buffer) < self::PACKAGE_HEAD_LEN)
        {
            return 0;
        }
        // Giải nén
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // Trả về chiều dài gói
        return $package_data['total_len'];
    }

    public static function decode($recv_buffer)
    {
        // Giải nén
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // Chiều dài tên tệp
        $name_len = $package_data['name_len'];
        // Cắt tên tệp từ luồng dữ liệu
        $file_name = substr($recv_buffer, self::PACKAGE_HEAD_LEN, $name_len);
        // Cắt dữ liệu nhị phân tệp từ luồng dữ liệu
        $file_data = substr($recv_buffer, self::PACKAGE_HEAD_LEN + $name_len);
         return array(
             'file_name' => $file_name,
             'file_data' => $file_data,
         );
    }

    public static function encode($data)
    {
        // Có thể mã hóa dữ liệu gửi đến khách hàng theo yêu cầu của bạn, ở đây chỉ trả về như văn bản
        return $data;
    }
}

Ví dụ sử dụng giao thức trên máy chủ

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

$worker = new Worker('BinaryTransfer://0.0.0.0:8333');
// Lưu tệp vào tmp
$worker->onMessage = function(TcpConnection $connection, $data)
{
    $save_path = '/tmp/'.$data['file_name'];
    file_put_contents($save_path, $data['file_data']);
    $connection->send("upload success. save path $save_path");
};

Worker::runAll();

Tệp khách hàng client.php (ở đây sử dụng php mô phỏng tải lên từ khách hàng)

<?php
/** Tải lên khách hàng **/
// Địa chỉ tải lên
$address = "127.0.0.1:8333";
// Kiểm tra tham số đường dẫn tệp tải lên
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// Đường dẫn tệp tải lên
$file_to_transfer = trim($argv[1]);
// Tệp tải lên không tồn tại tại địa phương
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Thiết lập kết nối socket
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
// Thiết lập chế độ chặn
stream_set_blocking($client, 1);
// Tên tệp
$file_name = basename($file_to_transfer);
// Chiều dài tên tệp
$name_len = strlen($file_name);
// Dữ liệu nhị phân tệp
$file_data = file_get_contents($file_to_transfer);
// Chiều dài phần đầu giao thức 4 byte chiều dài gói + 1 byte chiều dài tên tệp
$PACKAGE_HEAD_LEN = 5;
// Gói giao thức
$package = pack('NC', $PACKAGE_HEAD_LEN  + strlen($file_name) + strlen($file_data), $name_len) . $file_name . $file_data;
// Thực hiện tải lên
fwrite($client, $package);
// In kết quả
echo fread($client, 8192),"\n";

Ví dụ sử dụng từ khách hàng

Chạy trên dòng lệnh php client.php <đường_dẫn_tệp>

Ví dụ php client.php abc.jpg

Ví dụ 4 (Sử dụng giao thức văn bản tải lên tệp)

Định nghĩa giao thức

json + ký tự xuống dòng, json có chứa tên tệp và dữ liệu tệp đã được mã hóa bằng base64_encode (sẽ tăng 1/3 thể tích của nó)

Mẫu giao thức

{"file_name":"logo.png","file_data":"PD9waHAKLyo......"}\n

Lưu ý rằng phần cuối có một ký tự xuống dòng, trong PHP được biểu thị bằng ký tự nháy đôi "\n".

Triển khai giao thức

namespace Protocols;
class TextTransfer
{
    public static function input($recv_buffer)
    {
        $recv_len = strlen($recv_buffer);
        if($recv_buffer[$recv_len-1] !== "\n")
        {
            return 0;
        }
        return strlen($recv_buffer);
    }

    public static function decode($recv_buffer)
    {
        // Giải nén
        $package_data = json_decode(trim($recv_buffer), true);
        // Lấy tên tệp
        $file_name = $package_data['file_name'];
        // Lấy dữ liệu tệp đã được mã hóa bằng base64_encode
        $file_data = $package_data['file_data'];
        // Giải mã lại thành dữ liệu nhị phân tệp gốc
        $file_data = base64_decode($file_data);
        // Trả về dữ liệu
        return array(
             'file_name' => $file_name,
             'file_data' => $file_data,
         );
    }

    public static function encode($data)
    {
        // Có thể mã hóa dữ liệu gửi đến khách hàng theo nhu cầu của bạn, ở đây chỉ trả về như văn bản
        return $data;
    }
}

Ví dụ sử dụng giao thức trên máy chủ

Lưu ý: Cách viết giống như cách tải lên nhị phân, cho phép chuyển đổi giao thức gần như không cần thay đổi bất kỳ mã nghiệp vụ nào.

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

$worker = new Worker('TextTransfer://0.0.0.0:8333');
// Lưu tệp vào tmp
$worker->onMessage = function(TcpConnection $connection, $data)
{
    $save_path = '/tmp/'.$data['file_name'];
    file_put_contents($save_path, $data['file_data']);
    $connection->send("upload success. save path $save_path");
};

Worker::runAll();

Tệp khách hàng textclient.php (ở đây sử dụng php mô phỏng tải lên từ khách hàng)

<?php
/** Tải lên khách hàng **/
// Địa chỉ tải lên
$address = "127.0.0.1:8333";
// Kiểm tra tham số đường dẫn tệp tải lên
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// Đường dẫn tệp tải lên
$file_to_transfer = trim($argv[1]);
// Tệp tải lên không tồn tại tại địa phương
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Thiết lập kết nối socket
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
stream_set_blocking($client, 1);
// Tên tệp
$file_name = basename($file_to_transfer);
// Dữ liệu nhị phân tệp
$file_data = file_get_contents($file_to_transfer);
// Mã hóa base64
$file_data = base64_encode($file_data);
// Dữ liệu gói
$package_data = array(
    'file_name' => $file_name,
    'file_data' => $file_data,
);
// Gói giao thức json + ký tự xuống dòng
$package = json_encode($package_data)."\n";
// Thực hiện tải lên
fwrite($client, $package);
// In kết quả
echo fread($client, 8192),"\n";

Ví dụ sử dụng từ khách hàng

Chạy trên dòng lệnh php textclient.php <đường_dẫn_tệp>

Ví dụ php textclient.php abc.jpg