Some Examples

Example One

Protocol Definition

  • The header is a fixed length of 10 bytes to store the total length of the data packet, padded with 0 if insufficient bits
  • The data format is xml

Sample Packet

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

Here, 0000000121 represents the total length of the data packet, followed immediately by the contents of the body in xml format.

Protocol Implementation

namespace Protocols;
class XmlProtocol
{
    public static function input($recv_buffer)
    {
        if(strlen($recv_buffer) < 10)
        {
            // Less than 10 bytes, return 0 and continue waiting for data
            return 0;
        }
        // Return packet length, which includes header length + body length
        $total_len = base_convert(substr($recv_buffer, 0, 10), 10, 10);
        return $total_len;
    }

    public static function decode($recv_buffer)
    {
        // Request body
        $body = substr($recv_buffer, 10);
        return simplexml_load_string($body);
    }

    public static function encode($xml_string)
    {
        // Length of body + length of header
        $total_length = strlen($xml_string)+10;
        // Pad the length part to 10 bytes, filling with 0 if insufficient bits
        $total_length_str = str_pad($total_length, 10, '0', STR_PAD_LEFT);
        // Return data
        return $total_length_str . $xml_string;
    }
}

Example Two

Protocol Definition

  • The header is a 4-byte unsigned int in network byte order, marking the length of the entire packet
  • The data part is a Json string

Sample Packet

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

Here, the first four bytes of * are an unsigned int in network byte order, represented as non-visible characters, followed by the body data in Json format.

Protocol Implementation

namespace Protocols;
class JsonInt
{
    public static function input($recv_buffer)
    {
        // Received data is not enough for 4 bytes, unable to determine the packet length, return 0 and continue waiting for data
        if(strlen($recv_buffer)<4)
        {
            return 0;
        }
        // Use the unpack function to convert the first 4 bytes of the header into a number, which is the total length of the entire data packet
        $unpack_data = unpack('Ntotal_length', $recv_buffer);
        return $unpack_data['total_length'];
    }

    public static function decode($recv_buffer)
    {
        // Remove the first 4 bytes to get the body Json data
        $body_json_str = substr($recv_buffer, 4);
        // Decode json
        return json_decode($body_json_str, true);
    }

    public static function encode($data)
    {
        // Json encoding to get the body
        $body_json_str = json_encode($data);
        // Calculate the total length of the package, header length of 4 bytes + body byte count
        $total_length = 4 + strlen($body_json_str);
        // Return the packed data
        return pack('N',$total_length) . $body_json_str;
    }
}

Example Three (Using Binary Protocol to Upload Files)

Protocol Definition

struct
{
  unsigned int total_len;  // Total length of the packet, big-endian network byte order
  char         name_len;   // Length of the file name
  char         name[name_len]; // File name
  char         file[total_len - BinaryTransfer::PACKAGE_HEAD_LEN - name_len]; // File data
}

Protocol Sample

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

Here, the first four bytes of represent an unsigned int in network byte order, represented as non-visible characters. The fifth is used to store the length of the file name as one byte, immediately followed by the file name, and then the original binary file data.

Protocol Implementation

namespace Protocols;
class BinaryTransfer
{
    // Protocol header length
    const PACKAGE_HEAD_LEN = 5;

    public static function input($recv_buffer)
    {
        // If it is less than the length of a protocol header, continue waiting
        if(strlen($recv_buffer) < self::PACKAGE_HEAD_LEN)
        {
            return 0;
        }
        // Unpack
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // Return packet length
        return $package_data['total_len'];
    }

    public static function decode($recv_buffer)
    {
        // Unpack
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // File name length
        $name_len = $package_data['name_len'];
        // Extract the file name from the data stream
        $file_name = substr($recv_buffer, self::PACKAGE_HEAD_LEN, $name_len);
        // Extract the binary file data from the data stream
        $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)
    {
        // You can encode the data sent to the client as needed; here it is just returned as plain text
        return $data;
    }
}

Server Protocol Usage Example

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

$worker = new Worker('BinaryTransfer://0.0.0.0:8333');
// Save file to 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();

Client File client.php (Simulating client upload with PHP)

<?php
/** File upload client **/
// Upload address
$address = "127.0.0.1:8333";
// Check upload file path parameter
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// File path to upload
$file_to_transfer = trim($argv[1]);
// The file to upload does not exist locally
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Establish socket connection
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
// Set to blocking
stream_set_blocking($client, 1);
// File name
$file_name = basename($file_to_transfer);
// Length of the file name
$name_len = strlen($file_name);
// File binary data
$file_data = file_get_contents($file_to_transfer);
// Protocol header length 4 bytes for packet length + 1 byte for file name length
$PACKAGE_HEAD_LEN = 5;
// Protocol packet
$package = pack('NC', $PACKAGE_HEAD_LEN  + strlen($file_name) + strlen($file_data), $name_len) . $file_name . $file_data;
// Execute upload
fwrite($client, $package);
// Print result
echo fread($client, 8192),"\n";

Client Usage Example

Run in the command line php client.php <file_path>

For example, php client.php abc.jpg

Example Four (Using Text Protocol to Upload Files)

Protocol Definition

json + newline, the json contains the file name and the base64_encode encoded (increases size by 1/3) file data

Protocol Sample

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

Note that the end is a newline character, represented in PHP with the double quote character "\n".

Protocol Implementation

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)
    {
        // Unpack
        $package_data = json_decode(trim($recv_buffer), true);
        // Extract file name
        $file_name = $package_data['file_name'];
        // Extract base64_encode file data
        $file_data = $package_data['file_data'];
        // base64_decode restore original binary file data
        $file_data = base64_decode($file_data);
        // Return data
        return array(
             'file_name' => $file_name,
             'file_data' => $file_data,
         );
    }

    public static function encode($data)
    {
        // You can encode the data sent to the client as needed; here it is just returned as plain text
        return $data;
    }
}

Server Protocol Usage Example

Note: The writing method is the same as the binary upload method, which allows switching protocols with almost no changes to any business code.

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

$worker = new Worker('TextTransfer://0.0.0.0:8333');
// Save file to 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();

Client File textclient.php (Simulating client upload with PHP)

<?php
/** File upload client **/
// Upload address
$address = "127.0.0.1:8333";
// Check upload file path parameter
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// File path to upload
$file_to_transfer = trim($argv[1]);
// The file to upload does not exist locally
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Establish socket connection
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
stream_set_blocking($client, 1);
// File name
$file_name = basename($file_to_transfer);
// File binary data
$file_data = file_get_contents($file_to_transfer);
// base64 encode
$file_data = base64_encode($file_data);
// Data packet
$package_data = array(
    'file_name' => $file_name,
    'file_data' => $file_data,
);
// Protocol packet json + newline
$package = json_encode($package_data)."\n";
// Execute upload
fwrite($client, $package);
// Print result
echo fread($client, 8192),"\n";

Client Usage Example

Run in the command line php textclient.php <file_path>

For example, php textclient.php abc.jpg