How to Customize Protocols

In fact, it is relatively simple to create your own protocol. A simple protocol generally consists of two parts:

  • A delimiter to distinguish data boundaries
  • Data format definition

An Example

Protocol Definition

Here we assume that the delimiter to distinguish data boundaries is the newline character "\n" (note that the request data itself must not contain newline characters), and the data format is JSON. For example, below is a request packet that complies with this rule.

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

Note that there is a newline character at the end of the above request data (represented in PHP as the double-quoted string "\n"), indicating the end of a request.

Implementation Steps

To implement the above protocol in Workerman, assuming the protocol name is JsonNL and the project is MyApp, the following steps are required:

  1. Place the protocol file in the project's Protocols folder, for example, the file MyApp/Protocols/JsonNL.php.

  2. Implement the JsonNL class with namespace Protocols; as the namespace, and it must implement three static methods: input, encode, and decode.

Note: Workerman will automatically call these three static methods for unpacking, packing, and unpacking data. Refer to the execution process description below for specific details.

Workerman Interaction Process with Protocol Classes

  1. Assume the client sends a data packet to the server; after the server receives the data (which may be partial data), it will immediately call the protocol's input method to check the length of the packet. The input method returns the length value $length to the Workerman framework.
  2. After the Workerman framework receives this $length value, it checks whether the current data buffer has received data of length $length. If not, it will continue to wait for data until the length of the data in the buffer is at least $length.
  3. Once the buffer's data length is sufficient, Workerman will extract data of length $length from the buffer (i.e., unpack) and call the protocol's decode method to unpack it. The unpacked data will be stored in $data.
  4. After unpacking, Workerman will pass the unpacked data $data to the business logic in the form of a callback onMessage($connection, $data). The business logic can use the $data variable to retrieve the complete and unpacked data sent by the client within the onMessage method.
  5. When the business logic in onMessage needs to send data back to the client by calling the $connection->send($buffer) method, Workerman will automatically use the protocol's encode method to pack the $buffer before sending it to the client.

Specific Implementation

Implementation of MyApp/Protocols/JsonNL.php

namespace Protocols;
class JsonNL
{
    /**
     * Check the integrity of the packet
     * If the length of the packet can be determined, return the length of the packet in the buffer, 
     * otherwise return 0 to continue waiting for data
     * If there is a problem with the protocol, -1 can be returned, which will disconnect the current client connection
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // Get the position of the newline character "\n"
        $pos = strpos($buffer, "\n");
        // No newline character, unable to determine packet length, return 0 to continue waiting for data
        if($pos === false)
        {
            return 0;
        }
        // There is a newline character, return the current packet length (including the newline character)
        return $pos+1;
    }

    /**
     * Pack the data, automatically called when sending data to the client
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // JSON serialize and add a newline character as a marker for the end of the request
        return json_encode($buffer)."\n";
    }

    /**
     * Unpack the data, automatically called when the number of received data bytes equals the value returned by input (greater than 0)
     * and passed as the $data parameter to the onMessage callback function
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // Remove the newline character and restore it to an array
        return json_decode(trim($buffer), true);
    }
}

So far, the JsonNL protocol has been implemented and can be used in the MyApp project. The usage is as follows:

File: 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 is the data sent from the client, which has been processed by JsonNL::decode
    echo $data;

    // The $connection->send will automatically call the JsonNL::encode method to pack the data, then send it to the client
    $connection->send(array('code'=>0, 'msg'=>'ok'));

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

Note
Workerman will attempt to load protocols under the Protocols namespace, for example, new Worker('JsonNL://0.0.0.0:1234') will attempt to load the Protocols\JsonNL protocol.
If an error Class 'Protocols\JsonNL' not found occurs, please refer to Autoloading for implementing autoloading.

Protocol Interface Description

In Workerman, the protocol class developed must implement three static methods: input, encode, decode. The protocol interface description can be found in Workerman/Protocols/ProtocolInterface.php, defined as follows:

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * Protocol interface
 * @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * Used to unpack the received recv_buffer
     *
     * If the length of the request packet can be obtained from the $recv_buffer, return the total length of the packet
     * Otherwise, return 0 to indicate that more data is needed to obtain the current request packet length
     * If -1 is returned, it indicates an erroneous request, and the connection will be disconnected
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * Used for unpacking requests
     *
     * When the return value of input is greater than 0, and Workerman has received enough data, decode will be automatically called
     * Then trigger the onMessage callback and pass the data decoded by decode to the second parameter of the onMessage callback
     * That is to say, when a complete client request is received, decode will be automatically called for decoding without needing to call it manually in business code
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * Used for packing requests
     *
     * When it is necessary to send data to the client, i.e., when calling $connection->send($data); 
     * it will automatically pack the $data using encode, converting it into a data format that complies with the protocol, and then send it to the client
     * That is to say, the data sent to the client will be automatically encoded and packed without needing to call it manually in business code
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

Note:

There is no strict requirement in Workerman that the protocol class must implement ProtocolInterface. In fact, as long as the class contains the three static methods input, encode, and decode, it is acceptable.