프로토콜 사용자 정의 방법

실제로 자신의 프로토콜을 만드는 것은 비교적 간단합니다. 간단한 프로토콜은 일반적으로 두 부분으로 구성됩니다:

  • 데이터 경계 구분을 위한 식별자
  • 데이터 형식 정의

예제

프로토콜 정의

여기서는 데이터 경계 구분을 위한 식별자가 줄 바꿈 문자 "\n"이라고 가정합니다(요청 데이터 자체 내부에는 줄 바꿈 문자가 포함되지 않아야 합니다). 데이터 형식은 Json이며, 아래는 이 규칙을 준수하는 요청 패킷의 예입니다.

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

위의 요청 데이터 끝에 줄 바꿈 문자(php에서 큰따옴표 문자열 "\n"로 표시됨)가 있어, 요청의 종료를 나타냅니다.

구현 단계

Workerman에서 위의 프로토콜을 구현하려면, 프로토콜 이름을 JsonNL이라고 가정할 때, 프로젝트가 MyApp이라면 다음 단계를 따라야 합니다.

  1. 프로토콜 파일을 프로젝트의 Protocols 폴더에 넣습니다. 예: MyApp/Protocols/JsonNL.php

  2. JsonNL 클래스를 구현하며, namespace Protocols;를 네임스페이스로 하고, input, encode, decode라는 세 개의 정적 메서드를 반드시 구현합니다.

주의: workerman은 이 세 개의 정적 메서드를 자동으로 호출하여 분할, 해제 및 포장을 수행합니다. 구체적인 절차는 아래 실행 프로세스 설명을 참조하십시오.

workerman과 프로토콜 클래스의 상호작용 프로세스

  1. 클라이언트가 데이터 패킷을 서버에 전송한다고 가정할 때, 서버는 데이터(부분 데이터일 수 있음)를 수신한 후 즉시 프로토콜의 input 메서드를 호출하여 패킷의 길이를 검사합니다. input 메서드는 length 값 $length를 workerman 프레임워크에 반환합니다.
  2. workerman 프레임워크는 이 $length 값을 얻은 후 현재 데이터 버퍼에 이미 $length 길이의 데이터가 수신되었는지를 판단하며, 수신되지 않았다면 데이터를 계속 기다립니다. 데이터 길이가 $length보다 작지 않을 때까지 대기합니다.
  3. 데이터 버퍼의 길이가 충분해지면 workerman은 버퍼에서 $length 길이의 데이터를 잘라냅니다(즉, 분할), 그리고 프로토콜의 decode 메서드를 호출하여 해제를 수행합니다. 해제된 데이터는 $data입니다.
  4. 해제 후 workerman은 데이터를 $data 형태의 onMessage($connection, $data) 콜백으로 비즈니스에 전달합니다. 비즈니스 로직에서는 onMessage에서 $data 변수를 사용하여 클라이언트가 보낸 전체 데이터 및 해제된 데이터를 사용할 수 있습니다.
  5. 비즈니스가 onMessage에서 $connection->send($buffer) 메서드를 호출하여 클라이언트에 데이터를 전송할 필요가 있을 때, workerman은 자동으로 프로토콜의 encode 메서드를 활용하여 $buffer포장한 후 클라이언트에 전송합니다.

구체적인 구현

MyApp/Protocols/JsonNL.php의 구현

namespace Protocols;
class JsonNL
{
    /**
     * 패킷의 완전성을 검사합니다.
     * 패킷 길이를 확인할 수 있는 경우 패킷의 buffer 내 길이를 반환하고, 
     * 그렇지 않으면 0을 반환하여 데이터를 계속 기다립니다.
     * 프로토콜에 문제가 있는 경우 -1을 반환할 수 있으며, 현재 클라이언트 연결이 끊어집니다.
     * @param string $buffer
     * @return int
     */
    public static function input($buffer)
    {
        // 줄 바꿈 문자 "\n" 위치 얻기
        $pos = strpos($buffer, "\n");
        // 줄 바꿈 문자가 없으면 패킷 길이를 알 수 없으므로 0을 반환하고 계속 기다립니다.
        if($pos === false)
        {
            return 0;
        }
        // 줄 바꿈 문자가 있으면 현재 패킷 길이(줄 바꿈 문자를 포함)를 반환합니다.
        return $pos+1;
    }

    /**
     * 포장합니다. 클라이언트에 데이터를 전송할 때 자동으로 호출됩니다.
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // json으로 직렬화하고 줄 바꿈 문자를 요청 종료 마크로 추가합니다.
        return json_encode($buffer)."\n";
    }

    /**
     * 해제합니다. 수신된 데이터 바이트 수가 input이 반환한 값(0보다 큰 값)과 같으면 자동으로 호출됩니다.
     * onMessage 콜백 함수의 $data 인수에 전달됩니다.
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        // 줄 바꿈을 제거하고 배열로 복원합니다.
        return json_decode(trim($buffer), true);
    }
}

이로써 JsonNL 프로토콜의 구현이 완료되었으며, MyApp 프로젝트에서 사용 가능합니다. 사용 방법은 아래와 같습니다.

파일: 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는 클라이언트가 보낸 데이터입니다. 데이터는 이미 JsonNL::decode 처리되었습니다.
    echo $data;

    // $connection->send의 데이터는 자동으로 JsonNL::encode 메서드로 포장되어 클라이언트로 전송됩니다.
    $connection->send(array('code'=>0, 'msg'=>'ok'));

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

提示
workerman은 Protocols 네임스페이스 하의 프로토콜을 자동으로 로드하려고 시도합니다. 예: new Worker('JsonNL://0.0.0.0:1234')Protocols\JsonNL 프로토콜을 로드하려고 합니다.
만약 Class 'Protocols\JsonNL' not found 오류가 발생하면, 자동 로드를 참고하여 자동 로드를 구현하십시오.

프로토콜 인터페이스 설명

Workerman에서 개발된 프로토콜 클래스는 반드시 세 개의 정적 메서드인 input, encode, decode를 구현해야 하며, 프로토콜 인터페이스 설명은 Workerman/Protocols/ProtocolInterface.php에 정의되어 있습니다:

namespace Workerman\Protocols;

use \Workerman\Connection\ConnectionInterface;

/**
 * 프로토콜 인터페이스
* @author walkor <walkor@workerman.net>
 */
interface ProtocolInterface
{
    /**
     * 수신된 recv_buffer에서 패킷을 분할하는 데 사용됩니다.
     *
     * $recv_buffer에서 요청 패킷의 길이를 찾을 수 있는 경우 전체 패킷의 길이를 반환합니다.
     * 그렇지 않으면 0을 반환하여 현재 요청 패킷의 길이를 얻기 위해 더 많은 데이터가 필요하다는 것을 나타냅니다.
     * -1을 반환하면 잘못된 요청을 의미하며, 연결이 끊어집니다.
     *
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     * @return int
     */
    public static function input($recv_buffer, ConnectionInterface $connection);

    /**
     * 요청 해제를 수행합니다.
     *
     * input의 반환 값이 0보다 크고 Workerman이 충분한 데이터를 수신하면 decode가 자동으로 호출됩니다.
     * 그런 다음 onMessage 콜백을 트리거하며, decode로 디코딩된 데이터를 onMessage 콜백의 두 번째 매개변수에 전달합니다.
     * 즉, 클라이언트의 전체 요청을 수신할 때 자동으로 decode가 호출되며, 비즈니스 코드에서 수동으로 호출할 필요가 없습니다.
     * @param ConnectionInterface $connection
     * @param string $recv_buffer
     */
    public static function decode($recv_buffer, ConnectionInterface $connection);

    /**
     * 요청 포장을 수행합니다.
     *
     * 클라이언트에 데이터를 전송할 필요가 있을 때, 즉 $connection->send($data);가 호출될 때
     * 자동으로 $data를 encode로 포장하여 프로토콜 데이터 형식에 맞추고 클라이언트에 전송합니다.
     * 즉, 클라이언트에 전송되는 데이터는 자동으로 encode로 포장되며, 비즈니스 코드에서 수동으로 호출할 필요가 없습니다.
     * @param ConnectionInterface $connection
     * @param mixed $data
     */
    public static function encode($data, ConnectionInterface $connection);
}

주의:

Workerman에서는 프로토콜 클래스가 반드시 ProtocolInterface를 기반으로 구현해야 하는 것은 아닙니다. 실제로 프로토콜 클래스는 input, encode, decode 세 개의 정적 메서드만 포함하면 됩니다.