번역입니다.
번역: amoc
원문: http://www.perl.com/pub/a/2004/07/22/poe.html
예제 코드는 BSD License를 따릅니다.
1. POE 어플리케이션 디자인
2. POE 어플리케이션 개발하기
Building Applications with POE
POE 어플리케이션 개발하기
by Matt Cashner
July 23, 2004
Earlier, we talked about the fundamental principles of application design with POE. Now it's time to put my money where my mouth is and build some actual working code.
조금 전에, 우리는 POE를 이용한 어플리케이션 디자인의 기본적인 원칙들에 대해 이야기 했습니다. 이제는 배운 것을 실천하여 실제로 작동하는 코드를 작성합시다.
To make life a bit easier, let's lay out a very simple problem. Let's say we would like accept and parse data that resembles CGI query strings. This data will be key value pairs in which the key and value are separated by ='s and the pairs themselves are delimited by &. An example string we'll use throughout this article is as follows:
골치 아픈 것은 버리도록 하고, 아주 간단한 문제로 시작합시다. 우리가 CGI의 쿼리 문자열과 닮은 데이터를 수용하고 파싱하고자 합니다. 이 데이타는 키와 값이 =으로 나뉜 쌍이고 각 쌍들은 &으로 구분됩니다. 우리가 이 문서 전체에서 사용할 예제 문자열은 다음과 같습니다:
By the time we're done, we will have a working Filter and Component to handle this incoming data.
우리가 작업을 끝냈을 때에는, 작동하는 들어오는 데이터를 처리할 Filter와 Component을 가질 것입니다.
Step 1: A Filter - 필터
The first step is building a simple filter to parse this incoming data. As we discussed earlier, filters are much easier to deal with because they are unaware of their environment and the POE context in which they are run. Our filter is made even easier since we are just parsing incoming data and not generating an outgoing datastream.
첫번째 단계에서는 이 들어오는 데이터를 파싱할 간단한 필터를 만드는 것입니다. 전문에 우리가 논한 것처럼 필터는 다루기 아주 쉽습니다. 이것은 그들이 놓인 환경을 모르며, 어느 POE 문맥에서 돌아가고 있는지 알지 못하기 때문입니다. 우리는 그저 들어오는 데이터를 파싱할 뿐 나가는 데이터흐름(datastream)을 생성하지는 않을 것이므로 필터가 더 쉽게 만들어질 것입니다.
First off, we need the basics of any good module.
먼저 우리는 어떤 좋은 모듈이든지 필요한 기본 뼈대가 필요합니다.
Next we need a constructor.
다음으로 생성자가 필요합니다.
This is about the simplest constructor possible. This very simple filter requires no parameters to operate. It is perfectly reasonable, however, to demand parameters of filter users. For instance, if the filter could rot13 the incoming data before parsing, and a parameter could turn that feature on.
이것은 가능한한 가장 간단하게 작성된 생성자입니다. 우리가 만들 필터는 매우 간단하므로 어떠한 인자도 필요하지 않습니다. 물론 필터 사용자에게 인자를 요구하는 것은 명백히 합리적입니다. 예를 들어 필터가 들어오는 데이터를 파싱하기 전에 rot13을 할 수 있다면, 그 기능을 켤 수 있는 인자가 있어야 합니다.
get()
Now we need the ability to parse data. We will be using the newer and much simpler get/put version of the Filter API. This version of the standard POE Filter API requires get() and put() methods with the ability to transform multiple record sets per invocation.
이제 우리는 데이터를 파싱할 능력이 필요합니다. 우리는 더 간단한 최근 방식인 필터 API의 get/put 버전을 사용할 것입니다. POE 필터 API 표준에서 이 버전은 get() 과 put() 메쏘드를 요구하며, 이로 한번 호출될(invocation) 때 마다 여러개의 레코드셋(record set)을 변형 할 수 있습니다.
get()'s job is transform raw data into cooked record sets. The example string above (foo=bar&baz=1&bat=2) will become a hash:
get()이 하는 일은 가공하지 않은(raw) 데이터를 맛있게 요리된 레코드셋으로 변환하는 일입니다. 위의 예제 문자열 (foo=bar&baz1=&bat=2)는 해쉬로 변환될 것입니다:
A POE Filter is just a normal Perl object with a defined interface.
POE 필터는 그저 정의된 인터페이스를 가진 일반 Perl 객체입니다.
The buffer can and probably will contain multiple records. The size of the buffer is determined by the POE Driver being used and the operating system in question. $buffer will always be an array reference. While it is generally sensible to test $buffer to make sure it conforms to the standard interface, for the purpose of this exercise, we will just trust POE.
버퍼(buffer)는 하나 또는 여러개의 레코드를 가질 수 있습니다. 버퍼의 크기는 사용되고 있는 POE 드라이버(POE Driver)와 해당 운영체제에 따라 결정됩니다. $buffer는 언제나 배열 레퍼런스일 것입니다.
In our super-easy format, an individual record is terminated by a \n. Key value pairs are delimited by & and key and value themselves are separated by an =. Note that we aren't dealing with issues like character escaping or data taint. Production quality code will need to deal with these issues.
우리의 완전 간단한 포맷에서 각 레코드는 하나의 \n 으로 끝납니다. 키와 값 쌍들은 & 으로 구분되며 그 안에 키와 값 각각은 하나의 = 으로 나눠집니다. 여기서 우리는 데이터
Each parsed line makes up a chunk of data. We want to represent each record as a distinct entity to the user.
각 파싱된 라인은 여러개의 데이터로 구성됩니다. 우리는 유저에게 각 레코드를 구분하여 표현하고 싶습니다.
So what happens if there is more than one instance of a given key in a record? Simple. We make an array reference. The user will need to inspect the value of each key to determine if they have more than one value:
그러면 레코드의 해당 키에 하나 이상의 instance가 존재할 경우에는 어떻게 될까요? 간단합니다. 우리는 배열 레퍼런스를 만듭니다. 사용자는 하나 이상의 값이 존재하는지 결정하기 위해 각 키의 값을 살펴봐야 합니다.
put()
We now have a simple query-string-like data parser. This is fine for read-only servers but it makes sense to allow our users to send data back and forth in the same format. To allow for that, we need a put() method. put()'s job is take the cooked form of our records and translate it to the raw form. In this case we will be taking a hash reference that looks like:
우리는 이제 간단한 쿼리-문자열-같은 데이터 파서가 있습니다. 읽기전용 서버에서는 이것만 있어도 괜찮겠지만, 유저가 같은 포맷으로 데이터를 주고 받을 수 있도록 하는 것이 합리적입니다. 이를 위해 우리는 put() 메쏘드가 필요합니다. puts() 이 하는 일은 우리가 요리해 놓은 레코드를 받아서 raw 형태로 변환하는 것입니다. 이 때에는 다음과 같은 해쉬 레퍼런스 받게 됩니다.
And transforming it into:
그리고 이것을 다음과 같이 변환합니다:
So that whatever Wheel our user has chosen can put the data onto the wire. Like get(), put() is a normal method call on a normal perl object.
즉 사용자가 선택한 어떠한 어떠한 Wheel이든 데이터를 전선으로 흘려보낼 수 있습니다. get()과 마찬가지로 put()은 일반 perl 객체에서의 일반 메쏘드입니다.
Like get(), the data to act on is passed in as a parameter to the method call. It is always an array reference of records to translate.
get()과 마찬가지로, 사용할 데이터는 메쏘드 호출시 인자로 전달됩니다. 전달받는 인자는 언제나 변환할 레코드들의 배열 레퍼런스입니다.
Basically, our put() method performs an exact reversal of the get() algorithm. Take each key/value pair and concatenate them with an =. Each pair is then joined with a &.
근본적으로 우리의 put() 메쏘드는 get() 알고리즘의 완전히 반전한 형태로 작동할 것입니다. 각 키/값 쌍을 받아서 =으로 이어 붙이고, 다시 각 쌍을 &로 연결합니다.
The only real error condition we are checking for is the presence of non-array data. Since we haven't defined behavior for this condition, we warn the user about the data and skip it:
단 한가지 우리가 확인해야할 진짜 오류는 배열이 아닌 데이터가 들어오는 경우입니다. 우리는 이 경우에 대한 행동을 정의하지 않았기 때문에, 사용자에게 이를 경고하고 데이터는 무시합니다.
The raw data is returned to the caller as an array reference of data chunks. The caller has the responsibility of putting the data on the wire in the appropriate fashion.
raw 데이터는 여러 묶음이 데이터를 보관한 배열 레퍼런스 형태로 호출한 곳에 리턴합니다. 메쏘드를 호출한 부분은 적당한 형태로 네트워크 밖으로 보낼 책임이 있습니다.
Step 2: A Wheel - 휠(바퀴)
Chances are that our users want to send data in one of the standard UNIX methods -- sockets, pipes, and so on. Lucky for us, POE already has Wheels to deal with just about any methodology of data transfer you can imagine. Let's work with a method that should work just about anywhere, TCP sockets. POE::Wheel::SocketFactory provides the functionality we need. First, we need a session to plug the wheel into. (Remember that wheels mutate sessions to provide new functionality.)
아마 우리의 사용자는 소켓이나 파이프 등의 표준 UNIX 방법들 중 하나를 사용하여 데이터를 전송할 것입니다. 다행히도 POE는 우리가 상상할 수 있는 거의 어떠한 방법론과도 다룰수 있는 Wheel들을 제공합니다. 거의 어디서나 동작할 수 있는 TCP 소켓을 제공하도록 합시다. POE::Wheel::SocketFactory가 이 기능을 제공합니다. 먼저 wheel을 붙일 수 있는 세션이 필요합니다. (wheel은 세션을 변화시켜 새로운 함수를 제공한다는 것을 기억하세요)
This session will be our controller for the wheels we need to perform socket operations. Each wheel-based event provides a unique identifier so it is possible to handle more than one client per session.
이 세션은 소켓 작업을 할 wheel의 컨트롤러(controller)가 될 것입니다. 각 wheel 중심의(wheel-based) 이벤트는 유일한 식별자를 가지므로 세션마다 하나 이상의 클라이언트를 다루는(handle) 것이 가능합니다.
When the session starts up, we spin up the SocketFactory wheel. With the Reuse flag on, SocketFactory will continuously listen on the specified port and address, handing us events for each client. The unique id passed to the SuccessEvent identifies each client.
세션이 시작되면, SocketFactory wheel(바퀴)를 가동합니다. Reuse 플래그를 키면 SocketFactory는 끊임없이 각 클라이언트마다 이벤트를 처리하기 위해 특정 포트와 주소를 listen할 것입니다.
When a client makes a connection, the SocketFactory lets us know. It is our job to figure out what to do with the filehandle SocketFactory built for us. In this case, we want read/write functionality using the filter we built above. POE::Wheel::ReadWrite provides this functionality, including the ability to plug in our filter.
SocketFactory는 클라이언트가 연결되면 우리에게 이를 알립니다. SocketFactory가 생성하여 우리에게 전달한 파일핸들을 어떻게 처리할 지 알아내는 것은 우리의 몫입니다. 이 경우에서는 위에서 생성한 필터를 사용하여 읽기/쓰기 기능을 제공하고 싶습니다. POE::Wheel::ReadWrite가 우리의 필터를 포함하는 기능과 함께 이를 제공합니다.
Now the data path is set up. We have the ability for programs to connect to a port and provide data in our simple format. What to do with the data though? Let's simply print it out and echo it back to the client.
이제 데이터 경로가 설정되었습니다. 우리의 프로그램은 포트에 연결하고 간단한 포맷을 사용하여 데이터를 제공할 능력이 생겼습니다. 그런데 데이터는 어떻게 해야 할까요? 간단히 이를 출력하고 클라이언트에 그대로 돌려주도록(echo) 해 봅시다.
Data::Dumper handles printing out the structure for us. The put() call puts the structure back out onto the wire. If our algorithms are correct, we should get the same data back that we put in.
Data::Dumper는 데이터 구조를 출력해 줍니다. put()은 데이터 구조를 다시 돌려보냅니다. 우리의 알고리즘이 올바르다면, 우리가 보낸 데이터와 동일한 것을 받아야 할 것입니다.
The server prints out:
서버는 다음과 같이 출력합니다:
And then echoes back to us:
그리고 클라리언트에게 그대로 돌려줍니다(echo):
We're in business.
야호.
Step 3: A Component - 컴포넌트
Man, that was a lot of code to get a simple TCP server up and running. Surely this can be simplified. Again, POE itself comes to the rescue. POE ships with a component specifically designed to simplify TCP server creation. We can replace all that code above with a simple call to the component's constructor.
간단한 TCP 서버를 만들고 구동하는데에는 많은 코드를 사용했습니다. 물론 더 간단하게 할 수 있습니다. 다시말하지만, POE는 자체적으로 회복할 수 있습니다. POE는 TCP 서버 생성을 간단하게 디자인한 컴포넌트를 가지고 있습니다. 우리는 위의 모든 코드를 컴포넌트 생성자 호출 하나로 대체할 수 있습니다.
And we're done. The downside is that Server::TCP doesn't allow for argument passing to the filter's constructor and we lose the flexibility of doing things by hand. For a lot of problems, however, this component does the trick quite nicely.
이렇게 하면 끝입니다. 내부의 Server::TCP는 필터 생성자에 인자를 전달하는 것을 허용하지 않으며, 우리는 직접 다룰 수 있는 유연성을 잃습니다. 하지만 많은 문제 해결에서 이 컴포넌트가 멋지게 작동할 수 있습니다.
We can make this even easier for our users by making our own component. For the purpose of this example, we're going to wrap the smaller code above instead of the larger wheel based example. There is no reason why you couldn't use the wheel-based code in your component, however.
우리는 직접 컴포넌트를 만듬으로써 사용자에게 더 쉽게 만들 수 있습니다. 예를 위해, 우리는 위의 큰 wheel 기반의 예제를 보다 작은 코드로 감쌀(wrap) 것입니다.
Now our users can just load up the component like so:
이제 사용자는 간단히 컴포넌트를 다음처럼 불러들일 수 있습니다:
Conclusion
We have seen how to build POE filters and components and combine them with wheels and custom code to create flexible and maintainable programs. The code examples provided above may be downloaded under the BSD License.
POE 필터와 컴포넌트를 만들고 wheel과 합치거나, 유연하고 유지보수성이 좋은 프로그램을 만들기 위한 추가적인 코드를 작성하는 방법을 살펴보았다. 위에서 제공된 예제 코드들은 BSD 라이센스를 따라 다운받을 수 있다.
번역: amoc
원문: http://www.perl.com/pub/a/2004/07/22/poe.html
예제 코드는 BSD License를 따릅니다.
1. POE 어플리케이션 디자인
2. POE 어플리케이션 개발하기
Building Applications with POE
POE 어플리케이션 개발하기
by Matt Cashner
July 23, 2004
Earlier, we talked about the fundamental principles of application design with POE. Now it's time to put my money where my mouth is and build some actual working code.
조금 전에, 우리는 POE를 이용한 어플리케이션 디자인의 기본적인 원칙들에 대해 이야기 했습니다. 이제는 배운 것을 실천하여 실제로 작동하는 코드를 작성합시다.
To make life a bit easier, let's lay out a very simple problem. Let's say we would like accept and parse data that resembles CGI query strings. This data will be key value pairs in which the key and value are separated by ='s and the pairs themselves are delimited by &. An example string we'll use throughout this article is as follows:
골치 아픈 것은 버리도록 하고, 아주 간단한 문제로 시작합시다. 우리가 CGI의 쿼리 문자열과 닮은 데이터를 수용하고 파싱하고자 합니다. 이 데이타는 키와 값이 =으로 나뉜 쌍이고 각 쌍들은 &으로 구분됩니다. 우리가 이 문서 전체에서 사용할 예제 문자열은 다음과 같습니다:
foo=bar&baz=1&bat=2
By the time we're done, we will have a working Filter and Component to handle this incoming data.
우리가 작업을 끝냈을 때에는, 작동하는 들어오는 데이터를 처리할 Filter와 Component을 가질 것입니다.
Step 1: A Filter - 필터
The first step is building a simple filter to parse this incoming data. As we discussed earlier, filters are much easier to deal with because they are unaware of their environment and the POE context in which they are run. Our filter is made even easier since we are just parsing incoming data and not generating an outgoing datastream.
첫번째 단계에서는 이 들어오는 데이터를 파싱할 간단한 필터를 만드는 것입니다. 전문에 우리가 논한 것처럼 필터는 다루기 아주 쉽습니다. 이것은 그들이 놓인 환경을 모르며, 어느 POE 문맥에서 돌아가고 있는지 알지 못하기 때문입니다. 우리는 그저 들어오는 데이터를 파싱할 뿐 나가는 데이터흐름(datastream)을 생성하지는 않을 것이므로 필터가 더 쉽게 만들어질 것입니다.
First off, we need the basics of any good module.
먼저 우리는 어떤 좋은 모듈이든지 필요한 기본 뼈대가 필요합니다.
package POE::Filter::SimpleQueryString;
use warnings;
use strict;
use Carp qw(carp croak);
use warnings;
use strict;
use Carp qw(carp croak);
Next we need a constructor.
다음으로 생성자가 필요합니다.
sub new {
my $class = shift;
my $self = bless {}, $class;
return $self;
}
my $class = shift;
my $self = bless {}, $class;
return $self;
}
This is about the simplest constructor possible. This very simple filter requires no parameters to operate. It is perfectly reasonable, however, to demand parameters of filter users. For instance, if the filter could rot13 the incoming data before parsing, and a parameter could turn that feature on.
이것은 가능한한 가장 간단하게 작성된 생성자입니다. 우리가 만들 필터는 매우 간단하므로 어떠한 인자도 필요하지 않습니다. 물론 필터 사용자에게 인자를 요구하는 것은 명백히 합리적입니다. 예를 들어 필터가 들어오는 데이터를 파싱하기 전에 rot13을 할 수 있다면, 그 기능을 켤 수 있는 인자가 있어야 합니다.
get()
Now we need the ability to parse data. We will be using the newer and much simpler get/put version of the Filter API. This version of the standard POE Filter API requires get() and put() methods with the ability to transform multiple record sets per invocation.
이제 우리는 데이터를 파싱할 능력이 필요합니다. 우리는 더 간단한 최근 방식인 필터 API의 get/put 버전을 사용할 것입니다. POE 필터 API 표준에서 이 버전은 get() 과 put() 메쏘드를 요구하며, 이로 한번 호출될(invocation) 때 마다 여러개의 레코드셋(record set)을 변형 할 수 있습니다.
get()'s job is transform raw data into cooked record sets. The example string above (foo=bar&baz=1&bat=2) will become a hash:
get()이 하는 일은 가공하지 않은(raw) 데이터를 맛있게 요리된 레코드셋으로 변환하는 일입니다. 위의 예제 문자열 (foo=bar&baz1=&bat=2)는 해쉬로 변환될 것입니다:
$VAR1 = {
'foo' => 'bar',
'baz => '1',
'bat => '2',
};
'foo' => 'bar',
'baz => '1',
'bat => '2',
};
A POE Filter is just a normal Perl object with a defined interface.
POE 필터는 그저 정의된 인터페이스를 가진 일반 Perl 객체입니다.
sub get {
my $self = shift;
my $buffer = shift;
my $self = shift;
my $buffer = shift;
The buffer can and probably will contain multiple records. The size of the buffer is determined by the POE Driver being used and the operating system in question. $buffer will always be an array reference. While it is generally sensible to test $buffer to make sure it conforms to the standard interface, for the purpose of this exercise, we will just trust POE.
버퍼(buffer)는 하나 또는 여러개의 레코드를 가질 수 있습니다. 버퍼의 크기는 사용되고 있는 POE 드라이버(POE Driver)와 해당 운영체제에 따라 결정됩니다. $buffer는 언제나 배열 레퍼런스일 것입니다.
In our super-easy format, an individual record is terminated by a \n. Key value pairs are delimited by & and key and value themselves are separated by an =. Note that we aren't dealing with issues like character escaping or data taint. Production quality code will need to deal with these issues.
우리의 완전 간단한 포맷에서 각 레코드는 하나의 \n 으로 끝납니다. 키와 값 쌍들은 & 으로 구분되며 그 안에 키와 값 각각은 하나의 = 으로 나눠집니다. 여기서 우리는 데이터
my @chunks;
Each parsed line makes up a chunk of data. We want to represent each record as a distinct entity to the user.
각 파싱된 라인은 여러개의 데이터로 구성됩니다. 우리는 유저에게 각 레코드를 구분하여 표현하고 싶습니다.
foreach my $record (@$buffer) {
$record =~ s/\x0d\x0a$//;
my @pairs = split(/&/, $record);
my %chunk;
foreach my $pair (@pairs) {
my ($key, $value) = split(/=/, $pair, 2);
$record =~ s/\x0d\x0a$//;
my @pairs = split(/&/, $record);
my %chunk;
foreach my $pair (@pairs) {
my ($key, $value) = split(/=/, $pair, 2);
So what happens if there is more than one instance of a given key in a record? Simple. We make an array reference. The user will need to inspect the value of each key to determine if they have more than one value:
그러면 레코드의 해당 키에 하나 이상의 instance가 존재할 경우에는 어떻게 될까요? 간단합니다. 우리는 배열 레퍼런스를 만듭니다. 사용자는 하나 이상의 값이 존재하는지 결정하기 위해 각 키의 값을 살펴봐야 합니다.
if(defined $chunk{$key}) {
if(ref $chunk{$key} eq 'ARRAY') {
push @{ $chunk{$key} }, $value;
} else {
$chunk{$key} = [ $chunk{$key}, $value ],
}
} else {
$chunk{$key} = $value;
}
}
push @chunks, \%chunk;
}
return \@chunks;
}
if(ref $chunk{$key} eq 'ARRAY') {
push @{ $chunk{$key} }, $value;
} else {
$chunk{$key} = [ $chunk{$key}, $value ],
}
} else {
$chunk{$key} = $value;
}
}
push @chunks, \%chunk;
}
return \@chunks;
}
put()
We now have a simple query-string-like data parser. This is fine for read-only servers but it makes sense to allow our users to send data back and forth in the same format. To allow for that, we need a put() method. put()'s job is take the cooked form of our records and translate it to the raw form. In this case we will be taking a hash reference that looks like:
우리는 이제 간단한 쿼리-문자열-같은 데이터 파서가 있습니다. 읽기전용 서버에서는 이것만 있어도 괜찮겠지만, 유저가 같은 포맷으로 데이터를 주고 받을 수 있도록 하는 것이 합리적입니다. 이를 위해 우리는 put() 메쏘드가 필요합니다. puts() 이 하는 일은 우리가 요리해 놓은 레코드를 받아서 raw 형태로 변환하는 것입니다. 이 때에는 다음과 같은 해쉬 레퍼런스 받게 됩니다.
$VAR1 = {
'foo' => 'bar',
'baz' => '1',
'bat' => '2',
};
'foo' => 'bar',
'baz' => '1',
'bat' => '2',
};
And transforming it into:
그리고 이것을 다음과 같이 변환합니다:
foo=bar&baz=1&bat=2
So that whatever Wheel our user has chosen can put the data onto the wire. Like get(), put() is a normal method call on a normal perl object.
즉 사용자가 선택한 어떠한 어떠한 Wheel이든 데이터를 전선으로 흘려보낼 수 있습니다. get()과 마찬가지로 put()은 일반 perl 객체에서의 일반 메쏘드입니다.
sub put {
my $self = shift;
my $records = shift;
my $self = shift;
my $records = shift;
Like get(), the data to act on is passed in as a parameter to the method call. It is always an array reference of records to translate.
get()과 마찬가지로, 사용할 데이터는 메쏘드 호출시 인자로 전달됩니다. 전달받는 인자는 언제나 변환할 레코드들의 배열 레퍼런스입니다.
Basically, our put() method performs an exact reversal of the get() algorithm. Take each key/value pair and concatenate them with an =. Each pair is then joined with a &.
근본적으로 우리의 put() 메쏘드는 get() 알고리즘의 완전히 반전한 형태로 작동할 것입니다. 각 키/값 쌍을 받아서 =으로 이어 붙이고, 다시 각 쌍을 &로 연결합니다.
The only real error condition we are checking for is the presence of non-array data. Since we haven't defined behavior for this condition, we warn the user about the data and skip it:
단 한가지 우리가 확인해야할 진짜 오류는 배열이 아닌 데이터가 들어오는 경우입니다. 우리는 이 경우에 대한 행동을 정의하지 않았기 때문에, 사용자에게 이를 경고하고 데이터는 무시합니다.
my @raw;
foreach my $record (@$records) {
my @chunks;
foreach my $key (sort keys %$record) {
if(ref $record->{$key}) {
if(ref $record->{$key} eq 'ARRAY') {
foreach my $value ( @{ $record->{$key} } ) {
push @chunks, $key."=".$value;
}
} else {
carp __PACKAGE__." cannot handle data of type
".ref $record->{$key};
}
} else {
push @chunks, $key."=".$record->{$key};
}
}
push @raw, join('&',@chunks)."\x0d\x0a";
}
return \@raw;
}
foreach my $record (@$records) {
my @chunks;
foreach my $key (sort keys %$record) {
if(ref $record->{$key}) {
if(ref $record->{$key} eq 'ARRAY') {
foreach my $value ( @{ $record->{$key} } ) {
push @chunks, $key."=".$value;
}
} else {
carp __PACKAGE__." cannot handle data of type
".ref $record->{$key};
}
} else {
push @chunks, $key."=".$record->{$key};
}
}
push @raw, join('&',@chunks)."\x0d\x0a";
}
return \@raw;
}
The raw data is returned to the caller as an array reference of data chunks. The caller has the responsibility of putting the data on the wire in the appropriate fashion.
raw 데이터는 여러 묶음이 데이터를 보관한 배열 레퍼런스 형태로 호출한 곳에 리턴합니다. 메쏘드를 호출한 부분은 적당한 형태로 네트워크 밖으로 보낼 책임이 있습니다.
Step 2: A Wheel - 휠(바퀴)
Chances are that our users want to send data in one of the standard UNIX methods -- sockets, pipes, and so on. Lucky for us, POE already has Wheels to deal with just about any methodology of data transfer you can imagine. Let's work with a method that should work just about anywhere, TCP sockets. POE::Wheel::SocketFactory provides the functionality we need. First, we need a session to plug the wheel into. (Remember that wheels mutate sessions to provide new functionality.)
아마 우리의 사용자는 소켓이나 파이프 등의 표준 UNIX 방법들 중 하나를 사용하여 데이터를 전송할 것입니다. 다행히도 POE는 우리가 상상할 수 있는 거의 어떠한 방법론과도 다룰수 있는 Wheel들을 제공합니다. 거의 어디서나 동작할 수 있는 TCP 소켓을 제공하도록 합시다. POE::Wheel::SocketFactory가 이 기능을 제공합니다. 먼저 wheel을 붙일 수 있는 세션이 필요합니다. (wheel은 세션을 변화시켜 새로운 함수를 제공한다는 것을 기억하세요)
POE::Session->create(
inline_states => {
_start => \&start,
factory_success => \&factory_success,
client_input => \&client_input,
client_error => \&client_error,
fatal_error => sub { die "A fatal error occurred" },
_stop => sub {},
},
);
POE::Kernel->run();
inline_states => {
_start => \&start,
factory_success => \&factory_success,
client_input => \&client_input,
client_error => \&client_error,
fatal_error => sub { die "A fatal error occurred" },
_stop => sub {},
},
);
POE::Kernel->run();
This session will be our controller for the wheels we need to perform socket operations. Each wheel-based event provides a unique identifier so it is possible to handle more than one client per session.
이 세션은 소켓 작업을 할 wheel의 컨트롤러(controller)가 될 것입니다. 각 wheel 중심의(wheel-based) 이벤트는 유일한 식별자를 가지므로 세션마다 하나 이상의 클라이언트를 다루는(handle) 것이 가능합니다.
When the session starts up, we spin up the SocketFactory wheel. With the Reuse flag on, SocketFactory will continuously listen on the specified port and address, handing us events for each client. The unique id passed to the SuccessEvent identifies each client.
세션이 시작되면, SocketFactory wheel(바퀴)를 가동합니다. Reuse 플래그를 키면 SocketFactory는 끊임없이 각 클라이언트마다 이벤트를 처리하기 위해 특정 포트와 주소를 listen할 것입니다.
sub start {
$_[HEAP]->{factory} = POE::Wheel::SocketFactory->new(
BindAddress => '127.0.0.1',
BindPort => '31337',
SuccessEvent => 'factory_success',
FailureEvent => 'fatal_error',
SocketProtocol => 'tcp',
Reuse => 'on',
);
}
$_[HEAP]->{factory} = POE::Wheel::SocketFactory->new(
BindAddress => '127.0.0.1',
BindPort => '31337',
SuccessEvent => 'factory_success',
FailureEvent => 'fatal_error',
SocketProtocol => 'tcp',
Reuse => 'on',
);
}
When a client makes a connection, the SocketFactory lets us know. It is our job to figure out what to do with the filehandle SocketFactory built for us. In this case, we want read/write functionality using the filter we built above. POE::Wheel::ReadWrite provides this functionality, including the ability to plug in our filter.
SocketFactory는 클라이언트가 연결되면 우리에게 이를 알립니다. SocketFactory가 생성하여 우리에게 전달한 파일핸들을 어떻게 처리할 지 알아내는 것은 우리의 몫입니다. 이 경우에서는 위에서 생성한 필터를 사용하여 읽기/쓰기 기능을 제공하고 싶습니다. POE::Wheel::ReadWrite가 우리의 필터를 포함하는 기능과 함께 이를 제공합니다.
sub factory_success {
my( $handle, $wheel_id ) = @_[ARG0, ARG1];
$_[HEAP]->{clients}->{ $wheel_id } =
POE::Wheel::ReadWrite->new(
Handle => $handle,
Driver => POE::Driver::SysRW->new(),
Filter => POE::Filter::SimpleQueryString->new(),
InputEvent => 'client_input',
);
}
my( $handle, $wheel_id ) = @_[ARG0, ARG1];
$_[HEAP]->{clients}->{ $wheel_id } =
POE::Wheel::ReadWrite->new(
Handle => $handle,
Driver => POE::Driver::SysRW->new(),
Filter => POE::Filter::SimpleQueryString->new(),
InputEvent => 'client_input',
);
}
Now the data path is set up. We have the ability for programs to connect to a port and provide data in our simple format. What to do with the data though? Let's simply print it out and echo it back to the client.
이제 데이터 경로가 설정되었습니다. 우리의 프로그램은 포트에 연결하고 간단한 포맷을 사용하여 데이터를 제공할 능력이 생겼습니다. 그런데 데이터는 어떻게 해야 할까요? 간단히 이를 출력하고 클라이언트에 그대로 돌려주도록(echo) 해 봅시다.
sub client_input {
my ($input, $wheel_id) = @_[ARG0, ARG1];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{clients}->{ $wheel_id }->put( $input );
}
my ($input, $wheel_id) = @_[ARG0, ARG1];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{clients}->{ $wheel_id }->put( $input );
}
Data::Dumper handles printing out the structure for us. The put() call puts the structure back out onto the wire. If our algorithms are correct, we should get the same data back that we put in.
Data::Dumper는 데이터 구조를 출력해 줍니다. put()은 데이터 구조를 다시 돌려보냅니다. 우리의 알고리즘이 올바르다면, 우리가 보낸 데이터와 동일한 것을 받아야 할 것입니다.
sungo@cthulu% telnet localhost 31337
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
foo=bar
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
foo=bar
The server prints out:
서버는 다음과 같이 출력합니다:
sungo@cthulu% perl -Ilib examples/server.pl
$VAR1 = {
'foo' => 'bar'
};
$VAR1 = {
'foo' => 'bar'
};
And then echoes back to us:
그리고 클라리언트에게 그대로 돌려줍니다(echo):
foo=bar
We're in business.
야호.
Step 3: A Component - 컴포넌트
Man, that was a lot of code to get a simple TCP server up and running. Surely this can be simplified. Again, POE itself comes to the rescue. POE ships with a component specifically designed to simplify TCP server creation. We can replace all that code above with a simple call to the component's constructor.
간단한 TCP 서버를 만들고 구동하는데에는 많은 코드를 사용했습니다. 물론 더 간단하게 할 수 있습니다. 다시말하지만, POE는 자체적으로 회복할 수 있습니다. POE는 TCP 서버 생성을 간단하게 디자인한 컴포넌트를 가지고 있습니다. 우리는 위의 모든 코드를 컴포넌트 생성자 호출 하나로 대체할 수 있습니다.
POE::Component::Server::TCP->new(
Address => '127.0.0.1',
Port => '31337',
ClientFilter => "POE::Filter::SimpleQueryString",
ClientInput => sub {
my $input = $_[ARG0];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{client}->put($input);
}
Address => '127.0.0.1',
Port => '31337',
ClientFilter => "POE::Filter::SimpleQueryString",
ClientInput => sub {
my $input = $_[ARG0];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{client}->put($input);
}
And we're done. The downside is that Server::TCP doesn't allow for argument passing to the filter's constructor and we lose the flexibility of doing things by hand. For a lot of problems, however, this component does the trick quite nicely.
이렇게 하면 끝입니다. 내부의 Server::TCP는 필터 생성자에 인자를 전달하는 것을 허용하지 않으며, 우리는 직접 다룰 수 있는 유연성을 잃습니다. 하지만 많은 문제 해결에서 이 컴포넌트가 멋지게 작동할 수 있습니다.
We can make this even easier for our users by making our own component. For the purpose of this example, we're going to wrap the smaller code above instead of the larger wheel based example. There is no reason why you couldn't use the wheel-based code in your component, however.
우리는 직접 컴포넌트를 만듬으로써 사용자에게 더 쉽게 만들 수 있습니다. 예를 위해, 우리는 위의 큰 wheel 기반의 예제를 보다 작은 코드로 감쌀(wrap) 것입니다.
package POE::Component::SimpleQueryString;
use warnings;
use strict;
use POE;
use POE::Component::Server::TCP;
use POE::Filter::SimpleQueryString;
use Carp qw(croak carp);
sub new {
my $class = shift;
my %args = @_;
my $addr = delete $args{ListenAddr} || croak "ListenAddr required";
my $port = delete $args{ListenPort} || croak "ListenPort required";
my $input_event = delete $args{InputEvent} ||
croak "InputEvent required";
my $server = POE::Component::Server::TCP->new(
Address => $addr,
Port => $port,
ClientInput => $input_event,
ClientFilter => "POE::Filter::SimpleQueryString",
);
return $server;
}
1;
use warnings;
use strict;
use POE;
use POE::Component::Server::TCP;
use POE::Filter::SimpleQueryString;
use Carp qw(croak carp);
sub new {
my $class = shift;
my %args = @_;
my $addr = delete $args{ListenAddr} || croak "ListenAddr required";
my $port = delete $args{ListenPort} || croak "ListenPort required";
my $input_event = delete $args{InputEvent} ||
croak "InputEvent required";
my $server = POE::Component::Server::TCP->new(
Address => $addr,
Port => $port,
ClientInput => $input_event,
ClientFilter => "POE::Filter::SimpleQueryString",
);
return $server;
}
1;
Now our users can just load up the component like so:
이제 사용자는 간단히 컴포넌트를 다음처럼 불러들일 수 있습니다:
POE::Component::SimpleQueryString->new(
ListenAddr => '127.0.0.1',
ListenPort => '31337',
InputEvent => sub {
my $input = $_[ARG0];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{client}->put($input);
},
);
ListenAddr => '127.0.0.1',
ListenPort => '31337',
InputEvent => sub {
my $input = $_[ARG0];
use Data::Dumper;
print Dumper $input;
$_[HEAP]->{client}->put($input);
},
);
Conclusion
We have seen how to build POE filters and components and combine them with wheels and custom code to create flexible and maintainable programs. The code examples provided above may be downloaded under the BSD License.
POE 필터와 컴포넌트를 만들고 wheel과 합치거나, 유연하고 유지보수성이 좋은 프로그램을 만들기 위한 추가적인 코드를 작성하는 방법을 살펴보았다. 위에서 제공된 예제 코드들은 BSD 라이센스를 따라 다운받을 수 있다.




댓글을 달아 주세요
한빈이 2010/01/14 09:47 댓글주소 수정/삭제 댓글쓰기
롸져~ ㅋ...
형 블로그 발견 !