Skip to content

Instantly share code, notes, and snippets.

@keiya
Created May 24, 2012 16:01
Show Gist options
  • Save keiya/2782414 to your computer and use it in GitHub Desktop.
Save keiya/2782414 to your computer and use it in GitHub Desktop.
Perl Multithread & Multiprocess Socket Program
サーバ:マルチプロセスモデルのEchoサーバ、1プロセス1クライアント。
クライアント:マルチスレッド(threads)のクライアント
(標準入力に1スレッド,受信(と標準出力)に1スレッド,送信に1スレッドで、情報はキューで受け渡し)
クライアントの方はなぜかシグナルハンドラがゴミでシグナルが無視される。
いちばん手間かかったのがtty周りという。stty rawとかしてる。cursesとかつかえば簡単なんだろうけど。
でもこれでssh/telnetもどきができる土俵は整ったかな。
# raw stdin echo client
# keiya_21@yahoo.co.jp
# thanks to
# main socket program http://d.hatena.ne.jp/perlcodesample/20100512/1276960096
# thread program http://d.hatena.ne.jp/ashitano244/20090129/1233208853
use strict;
use warnings;
use Socket;
use threads;
use Thread::Queue;
$| = 1;
our $sock;
&connect($sock);
$SIG{'INT'} = 'disconnect';
$SIG{'TERM'} = 'disconnect';
$SIG{'QUIT'} = 'disconnect';
my $queue = new Thread::Queue;
my $write_thread = threads->new(\&tx);
my $read_thread = threads->new(\&rx);
my $stdio_thread = threads->new(\&stdio);
$write_thread->join;
$read_thread->join;
$stdio_thread->join;
sub connect {
# 1. ソケットの作成
socket( $main::sock, PF_INET, SOCK_STREAM, getprotobyname('tcp' ) )
or die "Cannot create socket: $!";
# 2. ソケット情報の作成
# 接続先のホスト名
my $remote_host = 'localhost';
my $packed_remote_host = inet_aton( $remote_host )
or die "Cannot pack $remote_host: $!";
# 接続先のポート番号
my $remote_port = 9000;
print("${remote_host}:${remote_port}...");
# ホスト名とポート番号をパック
my $sock_addr = sockaddr_in( $remote_port, $packed_remote_host )
or die "Cannot pack $remote_host:$remote_port: $!";
# 3. ソケットを使って接続
connect( $main::sock, $sock_addr )
or die "Cannot connect $remote_host:$remote_port: $!";
print("connected\n");
}
sub stdio {
my $i=0;
system("stty raw -echo");
{
my $char = '';
while ($char eq '') {
$char = getc();
threads->yield();
}
if ($char =~ /[]/) {
print "\n";
&disconnect();
threads->exit();
}
else {
$queue->enqueue($char);
#$queue->enqueue(undef);
redo;
}
}
}
sub tx {
# 4. データの書き込み
my $old_handle = select $main::sock;
$| = 1;
select $old_handle;
while (my $in = $queue->dequeue()) {
#print $main::sock "$in\n";
send($main::sock,$in,0);
threads->yield();
}
}
sub rx {
# 5. データの読み込み
while (my $receive_buf = getc($main::sock)) {
#print $sock_client $receive_buf;
print $receive_buf;
threads->yield();
}
}
sub disconnect {
shutdown $main::sock, 1; # 書き込みを終了する。
# 6. ソケットを閉じる
close $main::sock;
print "Disconnected.\n";
system("stty sane");
return;
}
__END__
# echo server
# keiya_21@yahoo.co.jp
# thanks to
# main socket program http://d.hatena.ne.jp/perlcodesample/20100512/1276960096
# fork program http://x68000.q-e-d.net/~68user/net/
use strict;
use warnings;
use Socket;
$| = 1;
# 1. 受付用ソケットの作成
my $sock_receive;
socket( $sock_receive, PF_INET, SOCK_STREAM, getprotobyname( 'tcp' ) )
or die "Cannot create socket: $!";
# 2. 受付用ソケット情報の作成
my $local_port = 9000;
my $pack_addr = sockaddr_in( $local_port, INADDR_ANY );
# 3. 受付用ソケットと受付用ソケット情報を結びつける
bind( $sock_receive, $pack_addr )
or die "Cannot bind: $!";
# 4. 接続を受け付ける準備をする。
listen( $sock_receive, SOMAXCONN )
or die "Cannot listen: $!";
print '['.$$.']Started parent process on port '.$local_port."\n";
my $sock_client;
# 5. 接続を受け付けて応答する。
while(1) {
my $paddr = accept($sock_client, $sock_receive);
# ホスト名、IPアドレス、クライアントのポート番号を取得
my ($client_port, $client_iaddr) = unpack_sockaddr_in($paddr);
my $client_hostname = gethostbyaddr($client_iaddr, AF_INET);
my $client_ip = inet_ntoa($client_iaddr);
print '['.$$.']Connection from: '.$client_ip.':'.$client_port."\n";
# forkで子プロセスを生成
if (my $pid = fork()){
# こちらは親プロセス
# 親プロセスはソケットをクローズ
close($sock_client);
next;
} else {
# こっちは子プロセス
print '['.$$."]Started child process\n";
# クライアントに対してバッファリングしない
my $old_handle = select $sock_client;
$| = 1;
select $old_handle;
print "\n";
while (my $receive_buf = getc($sock_client)) {
print($receive_buf);
# クライアントにメッセージを返す
send($sock_client,$receive_buf,0);
}
print "\n";
close($sock_client);
print '['.$$."]: Disconnect, process exit.\n";
# ポートの監視は親プロセスが行っているので、
# クライアントとのやりとりが終了すれば exit
exit;
}
}
__END__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment