我正在编写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
?
答案 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个选项都可以为您提供所需的行为。