并非收到关机前发送的所有套接字数据

时间:2016-10-06 16:50:50

标签: perl sockets

我正在编写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: $!";

目前,服务器输出

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>)循环退出,尽管客户端打印的Test message未被读取在那个循环中。

但是,如果我取消注释SEE HERE以下的行,服务器会在发送响应之前打印测试消息。

肯定当你shutdown时,所有待处理的数据都应该被刷新,对吗?

为什么我需要$| = 1行?

另外,我可以信任 $| = 1行吗?如果我不相信shutdown(或者,可能)close不截断邮件,我怎么能相信$|=1

1 个答案:

答案 0 :(得分: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变量$|修改默认缓冲行为。通过更改此变量的默认值,您可以使当前活动的文件句柄 hot 。这意味着内部缓冲区将在每个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();

您还可以使用方法flush()print之后手动刷新。

选项#3:

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

这会通过在本地范围内将SOCKET设置为$|来选择文件句柄1以使其。之后,您的邮件$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);

这很简单易用,几乎替换了所有代码。根据{{​​3}}的文件:

  

从VERSION 1.18开始,默认情况下,所有IO :: Socket对象都 autoflush打开。早期版本的情况并非如此。

所有这5个选项都可以为您提供所需的行为。