2016-10-06 13 views
3

私はPerlスクリプトを書いてTCPを介してクライアントとサーバーの間に文字列を送ります。接続は行われますが、クライアントからのデータ送信は受信されません。クライアント用サーバーシャットダウンする前に送信されたすべてのソケットデータが受信されるわけではありません。

#!/usr/bin/perl -w 
use strict; 
use Socket; 

my $port = 8021; 
my $proto = getprotobyname ('tcp'); 
my $server = "localhost"; 

socket (SOCKET, PF_INET, SOCK_STREAM, $proto) 
    or die "Can't open socket: $!"; 

bind (SOCKET, pack_sockaddr_in ($port, inet_aton ($server))) 
    or die "Can't bind $server:$port: $!"; 

listen (SOCKET, 5) 
    or die "Can't listen on $server:$port: $!"; 

my $client_addr; 

while ($client_addr = accept (NEW_SOCKET, SOCKET)) 
{ 
    my $name = gethostbyaddr ($client_addr, AF_INET); 

    while (<NEW_SOCKET>) 
    { 
     print "Recieved from client: $_<<ENDL\n"; 
    } 

    print NEW_SOCKET "Smile from the server."; 

    print "Connection recieved from $name\n"; 

    close NEW_SOCKET or warn "Couldn't close socket: $!"; 
} 

全コードの

完全なコード。コメントSEE HEREを探します。

#!/usr/bin/perl -w 
use strict; 
use Socket; 

my $port = 8021; 
my $proto = getprotobyname ('tcp'); 
my $server = "localhost"; 

$server = $ARGV[0] if (1 == @ARGV); 

socket (SOCKET, PF_INET, SOCK_STREAM, $proto) 
    or die "Can't create a socket: $!\n"; 

connect (SOCKET, pack_sockaddr_in ($port, inet_aton ($server))) 
    or die "Can't connect to $server:$port: $!\n"; 

# SEE HERE 
# my $s = select (SOCKET); 
# $| = 1; # make it hot 
# select ($s); 
print SOCKET "Test message from client to $server:$port."; 

shutdown (SOCKET, 1) or warn "Couldn't shutdown socket to $server:$port."; 

while (<SOCKET>) 
{ 
    print "Server $server:$port responds with: $_<<ENDL\n"; 
} 

close SOCKET or die "Can't close socket to $server:$port: $!"; 

現状ではもののTest messageは、サーバー出力

Connection recieved from ANantes-256-1-226-50.w2-0.abo.wanadoo.fr 

とクライアントの出力

Server localhost:8021 responds with: Smile from the server.<<ENDL 

は明らかに、クライアントのshutdownコマンドが正しく、終了し、サーバーのwhile(<NEW_SOCKET>)ループを引き起こしクライアントの印刷物はそのループで読み取られません。

しかし、SEE HERE以下の行のコメントを外すと、サーバーは応答を送信する前にテストメッセージを出力します。

確かにshutdownの場合、保留中のデータはすべてフラッシュされるはずです。

なぜ$| = 1行が必要ですか?

また、trust$| = 1の行は可能ですか?メッセージが切り捨てられないようにshutdown(またはおそらく)closeを信頼できない場合、どうすれば$|=1を信頼できますか?

+0

サーバの 'while'には' SERVER'とは何ですか? '警告を使用してください'。 – zdim

+0

それはタイプミスでしたが、私はそれを修正しました、ありがとう、しかし同じ問題がまだあります。 – spraff

+0

さて、サーバーでwhile() 'に変更し、両方のプログラムがハングするようになりました。 – spraff

答えて

3

これは、典型的なケースです:Suffering from Buffering?です。詳細に興味がある場合は、記事を読むことをお勧めします。

シャットダウン時に、保留中のすべてのデータがフラッシュされるはずです。

そうではありません。あなたがshutdownのperldocをチェックすると、フラッシュについてのヒントはありません。また、shutdown(3)のマニュアルページには、この動作はまったく言及されていません。

... 

socket (SOCKET, PF_INET, SOCK_STREAM, $proto) 
    or die "Can't create a socket: $!\n"; 

connect (SOCKET, pack_sockaddr_in ($port, inet_aton ($server))) 
    or die "Can't connect to $server:$port: $!\n"; 

# Here is the problem 
print SOCKET "Test message from client to $server:$port."; 

shutdown (SOCKET, 1) or warn "Couldn't shutdown socket to $server:$port."; 

... 

は、ファイルハンドルSOCKETが完全にバッファされ、瞬時にネットワークに印刷されていない、あなたのデータを意味しますが中に蓄積されます:

実際の問題は、クライアントコード内の次の行(コードのコメントを参照してください)最初に内部バッファ。内部バッファがいっぱいになるとすぐに、そのデータが送信されます。 printで送信しようとするメッセージは、わずか数バイトで、内部バッファーを最大サイズまで充填していません。

あなたは既に特別なPerl変数$|を使用して、デフォルトのバッファリング動作を変更する方法を理解しました。この変数のデフォルト値を変更することによって、現在アクティブなファイルハンドルをとすることができます。。つまり、内部バッファはすべてprintの後でファイルハンドルにフラッシュされます。

さて、あなたは次のようなメッセージを送信したい場合は、上記の問題のある行を交換する必要がどのようなさまざまなオプションを見てみましょう:

my $msg = "Test message from client to $server:$port."; 

オプション#1:

use FileHandle; 
SOCKET->autoflush(1); 
print SOCKET $msg; 

することができますautoflush()FileHandleから使用して、SOCKETファイルハンドルのバッファリングを無効にしてください。

オプション#2:

use IO::Handle; # or: use FileHandle 
print SOCKET $msg; 
SOCKET->flush(); 

またprint後に手動でflushメソッドflush()に使用することができます。

オプション#3:

{ 
    my $oldfh = select SOCKET; 
    local $| = 1; # make SOCKET hot 
    print SOCKET $msg; 
    select $oldfh; 
} 

これはローカルスコープに1$|を設定することによって、それホット作るためにファイルハンドルSOCKETを選択します。その後、$msgというメッセージがprintによって送信され、古いファイルハンドルが再選択されて復元されます。グローバル変数$|の古い値は、{ }ブロック内のローカルにのみに変更されたため、影響を受けません。

オプション#4:

syswrite SOCKET, $msg; 

さらに、あなたは、単に完全に内部バッファリングをバイパスするsyswrite()を使用することができます。

オプション#5:

use IO::Socket::INET; 
my $sock = IO::Socket::INET->new(
    PeerAddr => $server, 
    PeerPort => $port, 
    Proto => "tcp" 
); 
$sock->send($msg); 

これはシンプルで使うのが簡単であり、あなたのコードのほとんどすべてを置き換えます。 IO::Socketのドキュメントによると:

VERSION 1.18のように、すべてのIO ::ソケットオブジェクトが自動フラッシュはデフォルトでオンになっています。これは以前のリリースでは当てはまりませんでした。

これらの5つのオプションはすべて、あなたが探している動作を提供します。

関連する問題