Perl로 windows에서 Client/Server 작성시 fork/thread 사용 질문드려요.

전민수의 이미지

안녕하세요.

현재 camelbox(http://code.google.com/p/camelbox/)를 Windows XP에 설치하였고요,
IO::Socket과 socket 두 버전으로 코딩을 했는데요.
둘 다 fork를 하기 전에는 cmd.exe 창을 통해서 잘 실행이 되는데
fork를 적용한 경우 cmd.exe 창에서는 데이터가 전송되지 않고 멈춘것처럼 보입니다.
Cygwin에서 같은 코드를 실행시킬 경우에는 잘 됩니다....

처음에는 flushing 문제라 생각하고 처리해 주었는데요, (http://coding.derkeiler.com/Archive/Perl/comp.lang.perl.misc/2005-05/msg01045.html)
fork 만 사용하면 데이터 전송이 멈춘것처럼 동작해서요...
Cygwin에서 동작하는 걸로 봐서 Windows에서만 발생하는 문제인것 같은데,

(http://win32.perl.org/wiki/index.php?title=Compatibility_List_of_Perl_Modules#IO::Socket)
위 사이트에 보면 IO::Socket 의 경우 버그가 있다고는 하는데요.
그렇다면 listen,bind,accept,socket등의 함수를 사용한 버전은 동작해야 한다고 생각했는데 여전히 같은 현상이 발생하네요...

Windows에서 socket과 fork를 동시에 사용한 프로그래밍이 불가능한건가요?
조언 부탁드립니다.

아래는 테스트한 Server/Client 프로그램 코드입니다.

#!/bin/perl -w
require 5.002;
use strict;
use Socket;
use POSIX qw(:sys_wait_h);
use Carp;
use Sys::Hostname;
 
sub REAPER {
    1 until (-1 == waitpid(-1, WNOHANG));
    $SIG{CHLD} = \&REAPER;
}
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }
 
my $port  = shift || 2345;
my $proto = getprotobyname('tcp');
my $kid;
 
socket(Server, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die "setsockopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) or die "bind: $!";
listen(Server, SOMAXCONN)                    or die "listen: $!";
 
logmsg "server started on port $port";
 
my $paddr;
$SIG{CHLD} = \&REAPER;
$| = 1;
 
while ( $paddr = accept(Client, Server)) {
	my ($port, $iaddr) = sockaddr_in($paddr);
	my $name = gethostbyaddr($iaddr, AF_INET);
	logmsg "connection from $name [", inet_ntoa($iaddr), "] at port $port";
 
	my $old_fh = select(Client);
	$| = 1;
	select($old_fh);
 
	# execute a fork, if this is the parent,
	# its work is done, go straight to continue
	next if $kid = fork;
	die "fork: $!" unless defined $kid;
 
	# child now...
	# close the server - not needed
	close Server;
 
	print Client "Hello there, $name, it\'s now ", scalar localtime, "\n";
 
	my $buf;
	while (defined($buf = <Client>)) {	
		foreach ($buf) {
			if (/^HELLO$/) {
				print Client "Hi\n";
				last;
			}
			elsif (/^NAME$/) {
				print Client hostname(), "\n";				
				last;
			}
			elsif (/^DATE$/) {
				print Client scalar(localtime), "\n";
				last;
			}
			# default case:
			print Client "DEFAULT\n";
		}
	}
	close Client;	
}

#!/bin/perl -w
require 5.002;
use strict;
use Socket;
 
my ($remote, $port, $iaddr, $paddr, $proto, $line);
my ($in, $buf, $kid);
 
$remote = shift || 'localhost';
$port   = shift || 2345;		# random port
 
if ($port =~ /\D/) { $port = getservbyname($port, 'tcp') }
die "No port" unless $port;
 
$iaddr = inet_aton($remote) or die "no host; $remote";
$paddr = sockaddr_in($port, $iaddr);
$proto = getprotobyname('tcp');
 
socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
connect(SOCK, $paddr)                      or die "connect: $!";
 
my $old_fh = select(SOCK);
$| = 1;
select($old_fh);
 
die "fork fail: $!" unless defined($kid = fork);
 
if ($kid) {
    # parent reads from STDIN, prints to socket
    while (defined($in = <STDIN>)) {
		print SOCK $in;
    }
    # kill the child process
    kill(TERM => $kid);
} else {
    # child reads from socket, prints to STDOUT
    while (defined($buf = <SOCK>)) {
		print $buf;
    }
    close SOCK;
}
 
exit;
aero의 이미지

일단 윈도우용 Perl의 대세는 딸기펄 ( http://strawberryperl.com/ ) 입니다.

시스템 콜관련 부분은 UNIX(linux), Win32 간에 포팅이 되었다고 해도 미묘하게 동작이 다른 부분이 있어
저수준(?)으로 구현하면 여러가지로 예외상황이나 신경써야 될 부분이 많을 수 있습니다.
이때는 그러한 층을 Wrapping해서 좀 더 고수준에서 편하게 쓸 수 있게 해주는 모듈들을 사용해 보세요.

POE - http://poe.perl.org/
AnyEvent - http://search.cpan.org/dist/AnyEvent/
요즘 뜨는
Coro - http://search.cpan.org/dist/Coro/

기타 참고할만한 글들
http://d.hatena.ne.jp/tokuhirom/20090630/1246324092
http://j2k.naver.com/j2k_frame.php/korean/d.hatena.ne.jp/tokuhirom/20090629/1246239835

전민수의 이미지

말씀해주신 자료 참고해서 한번 해보겠습니다. :-)