Algunos ejemplos

Ejemplo Uno

Definición del Protocolo

  • La cabecera tiene una longitud fija de 10 bytes para almacenar la longitud total del paquete, los bits que faltan se llenan con 0
  • El formato de datos es xml

Ejemplo de Paquete de Datos

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

Donde 0000000121 representa la longitud total del paquete, seguida inmediatamente por el contenido del cuerpo del paquete en formato xml.

Implementación del Protocolo

namespace Protocols;
class XmlProtocol
{
    public static function input($recv_buffer)
    {
        if(strlen($recv_buffer) < 10)
        {
            // No hay suficientes bytes, devuelve 0 para seguir esperando datos
            return 0;
        }
        // Devuelve la longitud del paquete, que incluye la longitud de la cabecera + longitud del cuerpo
        $total_len = base_convert(substr($recv_buffer, 0, 10), 10, 10);
        return $total_len;
    }

    public static function decode($recv_buffer)
    {
        // Cuerpo de la petición
        $body = substr($recv_buffer, 10);
        return simplexml_load_string($body);
    }

    public static function encode($xml_string)
    {
        // Longitud del cuerpo + longitud de la cabecera
        $total_length = strlen($xml_string)+10;
        // Parte de longitud rellenada a 10 bytes, los bits que faltan se llenan con 0
        $total_length_str = str_pad($total_length, 10, '0', STR_PAD_LEFT);
        // Devuelve los datos
        return $total_length_str . $xml_string;
    }
}

Ejemplo Dos

Definición del Protocolo

  • Cabecera de 4 bytes en orden de bytes de red unsigned int, que marca la longitud total del paquete
  • La parte de datos es una cadena Json

Ejemplo de Paquete de Datos

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

Donde la cabecera de cuatro bytes * representa un dato de unsigned int en orden de bytes de red, que son caracteres invisibles, seguida de los datos del cuerpo en formato Json.

Implementación del Protocolo

namespace Protocols;
class JsonInt
{
    public static function input($recv_buffer)
    {
        // Los datos recibidos no son suficientes para 4 bytes, no se puede determinar la longitud del paquete, devuelve 0 para seguir esperando datos
        if(strlen($recv_buffer)<4)
        {
            return 0;
        }
        // Utiliza la función unpack para convertir los primeros 4 bytes en un número, los primeros 4 bytes son la longitud total del paquete
        $unpack_data = unpack('Ntotal_length', $recv_buffer);
        return $unpack_data['total_length'];
    }

    public static function decode($recv_buffer)
    {
        // Quitar los primeros 4 bytes, obteniendo los datos Json del cuerpo
        $body_json_str = substr($recv_buffer, 4);
        // Decodificación json
        return json_decode($body_json_str, true);
    }

    public static function encode($data)
    {
        // Codificación Json para obtener el cuerpo
        $body_json_str = json_encode($data);
        // Calcular la longitud total del paquete, 4 bytes de cabecera + longitud del cuerpo
        $total_length = 4 + strlen($body_json_str);
        // Devuelve los datos empaquetados
        return pack('N',$total_length) . $body_json_str;
    }
}

Ejemplo Tres (Usando el Protocolo Binario para Subir Archivos)

Definición del Protocolo

struct
{
  unsigned int total_len;  // Longitud total del paquete, en orden de bytes de red
  char         name_len;   // Longitud del nombre del archivo
  char         name[name_len]; // Nombre del archivo
  char         file[total_len - BinaryTransfer::PACKAGE_HEAD_LEN - name_len]; // Datos del archivo
}

Ejemplo de Protocolo

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

Donde la cabecera de cuatro bytes representa un dato de unsigned int en orden de bytes de red, que son caracteres invisibles, el 5to es un byte que almacena la longitud del nombre del archivo, seguido del nombre del archivo, y luego los datos binarios originales del archivo.

Implementación del Protocolo

namespace Protocols;
class BinaryTransfer
{
    // Longitud de la cabecera del protocolo
    const PACKAGE_HEAD_LEN = 5;

    public static function input($recv_buffer)
    {
        // Si no hay suficiente longitud para la cabecera del protocolo, sigue esperando
        if(strlen($recv_buffer) < self::PACKAGE_HEAD_LEN)
        {
            return 0;
        }
        // Desempaquetar
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // Devuelve la longitud del paquete
        return $package_data['total_len'];
    }

    public static function decode($recv_buffer)
    {
        // Desempaquetar
        $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
        // Longitud del nombre del archivo
        $name_len = $package_data['name_len'];
        // Extraer el nombre del archivo del flujo de datos
        $file_name = substr($recv_buffer, self::PACKAGE_HEAD_LEN, $name_len);
        // Extraer los datos binarios del archivo del flujo de datos
        $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)
    {
        // Se puede codificar los datos que se enviarán al cliente según sea necesario, aquí simplemente se devuelven de la misma manera
        return $data;
    }
}

Ejemplo de Uso del Protocolo en el Servidor

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

$worker = new Worker('BinaryTransfer://0.0.0.0:8333');
// Guardar el archivo en 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();

Archivo del Cliente client.php (Simulando la carga de archivos desde PHP)

<?php
/** Cliente de subida de archivos **/
// Dirección de subida
$address = "127.0.0.1:8333";
// Comprobar el parámetro de ruta de archivo de subida
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// Ruta del archivo a subir
$file_to_transfer = trim($argv[1]);
// El archivo para subir no existe localmente
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Establecer conexión socket
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
// Configurar como bloqueante
stream_set_blocking($client, 1);
// Nombre del archivo
$file_name = basename($file_to_transfer);
// Datos binarios del archivo
$file_data = file_get_contents($file_to_transfer);
// Longitud de la cabecera del protocolo 4 bytes de longitud del paquete + 1 byte de longitud del nombre del archivo
$PACKAGE_HEAD_LEN = 5;
// Paquete del protocolo
$package = pack('NC', $PACKAGE_HEAD_LEN  + strlen($file_name) + strlen($file_data), $name_len) . $file_name . $file_data;
// Ejecutar la carga
fwrite($client, $package);
// Imprimir el resultado
echo fread($client, 8192),"\n";

Ejemplo de Uso del Cliente

Ejecutar en la línea de comandos php client.php <ruta del archivo>

Por ejemplo php client.php abc.jpg

Ejemplo Cuatro (Usando el Protocolo de Texto para Subir Archivos)

Definición del Protocolo

json+salto de línea, json que contiene el nombre del archivo y los datos del archivo codificados en base64 (que aumentará el tamaño en 1/3)

Ejemplo de Protocolo

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

Nota: el final es un salto de línea, que en PHP se representa con el carácter de comillas dobles "\n"

Implementación del Protocolo

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)
    {
        // Desempaquetar
        $package_data = json_decode(trim($recv_buffer), true);
        // Obtener el nombre del archivo
        $file_name = $package_data['file_name'];
        // Obtener los datos del archivo después de base64_encode
        $file_data = $package_data['file_data'];
        // Decodificar base64 para restaurar los datos binarios del archivo original
        $file_data = base64_decode($file_data);
        // Devuelve los datos
        return array(
             'file_name' => $file_name,
             'file_data' => $file_data,
         );
    }

    public static function encode($data)
    {
        // Se puede codificar los datos que se enviarán al cliente según sea necesario, aquí simplemente se devuelven de la misma manera
        return $data;
    }
}

Ejemplo de Uso del Protocolo en el Servidor

Nota: la forma de escribir es la misma que para la subida binaria, es decir, se puede cambiar de protocolo casi sin modificar ningún código de negocio.

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

$worker = new Worker('TextTransfer://0.0.0.0:8333');
// Guardar el archivo en 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();

Archivo del Cliente textclient.php (Simulando la carga de archivos desde PHP)

<?php
/** Cliente de subida de archivos **/
// Dirección de subida
$address = "127.0.0.1:8333";
// Comprobar el parámetro de ruta de archivo de subida
if(!isset($argv[1]))
{
   exit("use php client.php \$file_path\n");
}
// Ruta del archivo a subir
$file_to_transfer = trim($argv[1]);
// El archivo para subir no existe localmente
if(!is_file($file_to_transfer))
{
    exit("$file_to_transfer not exist\n");
}
// Establecer conexión socket
$client = stream_socket_client($address, $errno, $errmsg);
if(!$client)
{
    exit("$errmsg\n");
}
stream_set_blocking($client, 1);
// Nombre del archivo
$file_name = basename($file_to_transfer);
// Datos binarios del archivo
$file_data = file_get_contents($file_to_transfer);
// Codificación base64
$file_data = base64_encode($file_data);
// Paquete de datos
$package_data = array(
    'file_name' => $file_name,
    'file_data' => $file_data,
);
// Paquete de protocolo json + retorno de carro
$package = json_encode($package_data)."\n";
// Ejecutar la carga
fwrite($client, $package);
// Imprimir el resultado
echo fread($client, 8192),"\n";

Ejemplo de Uso del Cliente

Ejecutar en la línea de comandos php textclient.php <ruta del archivo>

Por ejemplo php textclient.php abc.jpg