프로토콜 사용자 정의 방법
실제로 자신의 프로토콜을 만드는 것은 비교적 간단합니다. 간단한 프로토콜은 일반적으로 두 부분으로 구성됩니다:
- 데이터 경계 구분을 위한 식별자
- 데이터 형식 정의
예제
프로토콜 정의
여기서는 데이터 경계 구분을 위한 식별자가 줄 바꿈 문자 "\n"이라고 가정합니다(요청 데이터 자체 내부에는 줄 바꿈 문자가 포함되지 않아야 합니다). 데이터 형식은 Json이며, 아래는 이 규칙을 준수하는 요청 패킷의 예입니다.
{"type":"message","content":"hello"}
위의 요청 데이터 끝에 줄 바꿈 문자(php에서 큰따옴표 문자열 "\n"로 표시됨)가 있어, 요청의 종료를 나타냅니다.
구현 단계
Workerman에서 위의 프로토콜을 구현하려면, 프로토콜 이름을 JsonNL이라고 가정할 때, 프로젝트가 MyApp이라면 다음 단계를 따라야 합니다.
-
프로토콜 파일을 프로젝트의 Protocols 폴더에 넣습니다. 예: MyApp/Protocols/JsonNL.php
-
JsonNL 클래스를 구현하며,
namespace Protocols;를 네임스페이스로 하고, input, encode, decode라는 세 개의 정적 메서드를 반드시 구현합니다.
주의: workerman은 이 세 개의 정적 메서드를 자동으로 호출하여 분할, 해제 및 포장을 수행합니다. 구체적인 절차는 아래 실행 프로세스 설명을 참조하십시오.
workerman과 프로토콜 클래스의 상호작용 프로세스
- 클라이언트가 데이터 패킷을 서버에 전송한다고 가정할 때, 서버는 데이터(부분 데이터일 수 있음)를 수신한 후 즉시 프로토콜의
input메서드를 호출하여 패킷의 길이를 검사합니다.input메서드는 length 값$length를 workerman 프레임워크에 반환합니다. - workerman 프레임워크는 이
$length값을 얻은 후 현재 데이터 버퍼에 이미$length길이의 데이터가 수신되었는지를 판단하며, 수신되지 않았다면 데이터를 계속 기다립니다. 데이터 길이가$length보다 작지 않을 때까지 대기합니다. - 데이터 버퍼의 길이가 충분해지면 workerman은 버퍼에서
$length길이의 데이터를 잘라냅니다(즉, 분할), 그리고 프로토콜의decode메서드를 호출하여 해제를 수행합니다. 해제된 데이터는$data입니다. - 해제 후 workerman은 데이터를
$data형태의onMessage($connection, $data)콜백으로 비즈니스에 전달합니다. 비즈니스 로직에서는 onMessage에서$data변수를 사용하여 클라이언트가 보낸 전체 데이터 및 해제된 데이터를 사용할 수 있습니다. - 비즈니스가
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 세 개의 정적 메서드만 포함하면 됩니다.