内容

名称

IO::Socket - 套接字通信的对象接口

概要

use strict;
use warnings;

use IO::Socket qw(AF_INET AF_UNIX);

# create a new AF_INET socket
my $sock = IO::Socket->new(Domain => AF_INET);
# which is the same as
$sock = IO::Socket::INET->new();

# create a new AF_UNIX socket
$sock = IO::Socket->new(Domain => AF_UNIX);
# which is the same as
$sock = IO::Socket::UNIX->new();

说明

IO::Socket 提供了一个面向对象的、基于 IO::Handle 的接口,用于通过 Socket 创建和使用套接字,后者提供了一个与 C 套接字库几乎一一对应的接口。

IO::Socket 是一个基类,实际上只定义了那些对所有类型的套接字都通用的操作的方法。特定于特定套接字域的操作在 IO::Socket 的子类中定义了方法。有关此类子类的示例,请参见 IO::Socket::INETIO::Socket::UNIXIO::Socket::IP

IO::Socket 将导出 Socket 定义的所有函数(和常量)。

构造函数参数

鉴于 IO::Socket 在传统意义上没有属性,因此可以将以下参数(而不是属性)传递给构造函数。

构造函数参数应以 Key => 'Value' 对的形式传递。

唯一必需的参数是 "Domain" in IO::Socket

阻塞

my $sock = IO::Socket->new(..., Blocking => 1);
$sock = IO::Socket->new(..., Blocking => 0);

如果定义但为 false,则套接字将设置为非阻塞模式。如果未指定,则默认为 1(阻塞模式)。

my $sock = IO::Socket->new(Domain => IO::Socket::AF_INET);
$sock = IO::Socket->new(Domain => IO::Socket::AF_UNIX);

套接字域将定义使用 IO::Socket 的哪个子类。此发行版中提供的两个选项是 AF_INETAF_UNIX

AF_INET 用于 Internet 地址族套接字,并通过 IO::Socket::INET 处理。AF_INET 套接字绑定到 Internet 地址和端口。

AF_UNIX 用于 Unix 域套接字,并通过 IO::Socket::UNIX 处理。AF_UNIX 套接字绑定到文件系统作为其地址名称空间。

此参数是必需的。所有其他参数都是可选的。

侦听

my $sock = IO::Socket->new(..., Listen => 5);

侦听应为整数或留空。

如果提供,此参数将使套接字进入侦听模式。然后可以使用 "accept" in IO::Socket 方法接受新连接。给定的值用作 listen(2) 队列大小。

如果给出了 Listen 参数,但为 false,则队列大小将设置为 5。

超时

my $sock = IO::Socket->new(..., Timeout => 5);

此套接字连接的超时值(以秒为单位)。如何利用此值在利用该值的套接字域子类中定义。

类型

my $sock = IO::Socket->new(..., Type => IO::Socket::SOCK_STREAM);

将使用的套接字类型。这些通常是 SOCK_STREAMSOCK_DGRAMSOCK_RAW。如果此参数未定义,则将尝试从服务名称推断类型。

例如,通常会将 SOCK_STREAMtcp 连接一起使用,将 SOCK_DGRAMudp 连接一起使用。

构造函数

IO::Socket 扩展了 IO::Handle 构造函数。

new

my $sock = IO::Socket->new();

# get a new IO::Socket::INET instance
$sock = IO::Socket->new(Domain => IO::Socket::AF_INET);
# get a new IO::Socket::UNIX instance
$sock = IO::Socket->new(Domain => IO::Socket::AF_UNIX);

# Domain is the only required argument
$sock = IO::Socket->new(
    Domain => IO::Socket::AF_INET, # AF_INET, AF_UNIX
    Type => IO::Socket::SOCK_STREAM, # SOCK_STREAM, SOCK_DGRAM, ...
    Proto => 'tcp', # 'tcp', 'udp', IPPROTO_TCP, IPPROTO_UDP
    # and so on...
);

创建一个 IO::Socket,它是一个新创建符号的引用(参见 Symbol 包)。new 可选地获取参数,这些参数在 "CONSTRUCTOR ARGUMENTS" in IO::Socket 中定义。

任何 "CONSTRUCTOR ARGUMENTS" in IO::Socket 都可以传递给构造函数,但如果提供了任何参数,则其中一个必须是 "Domain" in IO::Socket 参数。默认情况下,"Domain" in IO::Socket 参数可以是 AF_INETAF_UNIX。如果注册了域系列的适当子类,则可以使用其他域。所有其他参数将传递给该域包的 configuration 方法。

如果构造函数失败,它将返回 undef 并设置 $errstr 包变量以包含错误消息。

$sock = IO::Socket->new(...)
    or die "Cannot create socket - $IO::Socket::errstr\n";

出于传统原因,错误消息也设置到全局 $@ 变量中,你可能仍然会发现旧代码在这里查找。

$sock = IO::Socket->new(...)
    or die "Cannot create socket - $@\n";

方法

IO::Socket 继承 IO::Handle 中的所有方法并实现以下新方法。

accept

my $client_sock = $sock->accept();
my $inet_sock = $sock->accept('IO::Socket::INET');

accept 方法将在套接字上执行系统调用 accept 并返回一个新对象。新对象将在与侦听套接字相同的类中创建,除非指定了特定的包名称。此对象可用于与尝试连接的客户端通信。

这与 perlfunc 中的 accept 函数略有不同。

在标量上下文中,将返回新套接字,或者在失败时返回 undef。在列表上下文中,将返回一个包含新套接字和对等地址的二元数组;失败时列表将为空。

atmark

my $integer = $sock->atmark();
# read in some data on a given socket
my $data;
$sock->read($data, 1024) until $sock->atmark;

# or, export the function to use:
use IO::Socket 'sockatmark';
$sock->read($data, 1024) until sockatmark($sock);

如果套接字当前位于紧急数据标记处,则为真,否则为假。如果你的系统尚未实现 sockatmark,这将引发异常。

如果你的系统不支持 sockatmark,则 use 声明将在编译时失败。

autoflush

# by default, autoflush will be turned on when referenced
$sock->autoflush(); # turns on autoflush
# turn off autoflush
$sock->autoflush(0);
# turn on autoflush
$sock->autoflush(1);

此属性不会覆盖 IO::Handle 的实现。但是,由于我们默认启用它,因此值得在此处提及。

bind

use Socket qw(pack_sockaddr_in);
my $port = 3000;
my $ip_address = '0.0.0.0';
my $packed_addr = pack_sockaddr_in($port, $ip_address);
$sock->bind($packed_addr);

将网络地址绑定到套接字,就像 bind(2) 所做的那样。如果成功,则返回 true,否则返回 false。您应该为套接字提供适当类型的打包地址。

connected

my $peer_addr = $sock->connected();
if ($peer_addr) {
    say "We're connected to $peer_addr";
}

如果套接字处于连接状态,则返回对等地址。如果套接字未处于连接状态,则返回 undef

请注意,此方法认为半开 TCP 套接字“处于连接状态”。具体来说,它不区分 ESTABLISHEDCLOSE-WAIT TCP 状态;它在任何情况下都返回对等地址,而不是 undef。因此,通常情况下,它不能可靠地判断对等方是否已启动正常关闭,因为在大多数情况下(见下文),本地 TCP 状态机在本地应用程序调用 IO::Socket 中的“shutdown”close 之前仍处于 CLOSE-WAIT 状态。仅在这一点上,此函数才返回 undef

之所以说“在大多数情况下”,是因为本地 TCP 状态机行为可能取决于对等方的套接字选项。特别是,如果对等方套接字启用了 SO_LINGER 且超时为零,则对等方的 close 将生成 RST 段。在收到该段后,本地 TCP 立即转换为 CLOSED,在此状态下,此方法返回 undef

getsockopt

my $value = $sock->getsockopt(SOL_SOCKET, SO_REUSEADDR);
my $buf = $socket->getsockopt(SOL_SOCKET, SO_RCVBUF);
say "Receive buffer is $buf bytes";

获取与套接字关联的选项。此处可以指定除 SOL_SOCKET 之外的级别。为了方便起见,此方法会将正确大小的字节缓冲区解包回数字。

listen

$sock->listen(5);

执行与 listen(2) 系统调用相同的操作。如果成功,则返回 true,否则返回 false。使用给定的队列大小侦听套接字。

peername

my $sockaddr_in = $sock->peername();

返回套接字连接另一端的打包 sockaddr 地址。它调用 getpeername

protocol

my $proto = $sock->protocol();

如果已知,则返回套接字上使用的协议的编号。如果协议未知,如 AF_UNIX 套接字,则返回零。

recv

my $buffer = "";
my $length = 1024;
my $flags = 0; # default. optional
$sock->recv($buffer, $length);
$sock->recv($buffer, $length, $flags);

功能类似于 perlfunc 中的“recv”

在套接字上接收消息。尝试从指定的套接字接收 $length 个字符的数据到 $buffer 中。$buffer 将增长或缩小到实际读取的长度。采用与同名系统调用相同的标志。如果套接字的协议支持,则返回发件人的地址;否则返回空字符串。如果出现错误,则返回 undef。此调用实际上是根据 recvfrom(2) 系统调用实现的。

标志是 ORed 在一起的值,例如 MSG_BCASTMSG_OOBMSG_TRUNC。标志的默认值为 0

recv 的结果会更新 IO::Socketpeername 的缓存值。

注意:在 Perl v5.30 及更新版本中,如果套接字已标记为 :utf8recv 将抛出异常。:encoding(...) 层隐式引入了 :utf8 层。请参见 perlfunc 中的 binmode

注意:在低于 v5.30 的 Perl 版本中,根据套接字的状态,将接收(8 位)字节或字符。默认情况下,所有套接字都对字节进行操作,但例如,如果已使用 perlfunc 中的 binmode 更改套接字以使用 :encoding(UTF-8) I/O 层(请参见 perlfunc 中的 open 实用程序),则 I/O 将对 UTF8 编码的 Unicode 字符进行操作,而不是字节。:encoding 层类似:在这种情况下,几乎可以读取任何字符。

send

my $message = "Hello, world!";
my $flags = 0; # defaults to zero
my $to = '0.0.0.0'; # optional destination
my $sent = $sock->send($message);
$sent = $sock->send($message, $flags);
$sent = $sock->send($message, $flags, $to);

功能类似于 perlfunc 中的 send

在套接字上发送消息。尝试将标量消息发送到套接字。采用与同名系统调用的相同标志。对于未连接的套接字,您必须指定要发送到的目标,在这种情况下,它会执行 sendto(2) 系统调用。返回发送的字符数,或在出错时返回 undef。目前尚未实现 sendmsg(2) 系统调用。

flags 选项是可选的,默认为 0

在使用 $to 成功发送后,在未连接的套接字上对 send 的进一步调用在没有 $to 的情况下将发送到同一地址,并且 $to 将用作 IO::Socketpeername 的结果。

注意:在 Perl v5.30 及更新版本中,如果套接字已标记为 :utf8send 将抛出异常。:encoding(...) 层隐式引入了 :utf8 层。请参见 perlfunc 中的 binmode

注意:在低于 v5.30 的 Perl 版本中,根据套接字的状态,将发送(8 位)字节或字符。默认情况下,所有套接字都对字节进行操作,但例如,如果已使用 perlfunc 中的 binmode 更改套接字以使用 :encoding(UTF-8) I/O 层(请参见 perlfunc 中的 open 实用程序),则 I/O 将对 UTF8 编码的 Unicode 字符进行操作,而不是字节。:encoding 层类似:在这种情况下,几乎可以发送任何字符。

setsockopt

$sock->setsockopt(SOL_SOCKET, SO_REUSEADDR, 1);
$sock->setsockopt(SOL_SOCKET, SO_RCVBUF, 64*1024);

设置与套接字关联的选项。此处可以指定除 SOL_SOCKET 以外的级别。为了方便,此方法会将数字转换为打包的字节缓冲区。

shutdown

$sock->shutdown(SHUT_RD); # we stopped reading data
$sock->shutdown(SHUT_WR); # we stopped writing data
$sock->shutdown(SHUT_RDWR); # we stopped using this socket

按照传入的值指示的方式关闭套接字连接,该值与同名系统调用的解释相同。

当您想告诉另一方您已完成写入但未完成读取,或反之亦然时,此方法对于套接字很有用。它也是 close 的一种更坚决的形式,因为它还会禁用其他进程中任何已 fork 副本中的文件描述符。

成功时返回 1;如果套接字不是有效的文件句柄,则在出错时返回 undef,或返回 0 并为任何其他故障设置 $!

sockdomain

my $domain = $sock->sockdomain();

返回套接字域类型的数字。例如,对于 AF_INET 套接字,将返回 &AF_INET 的值。

socket

my $sock = IO::Socket->new(); # no values given
# now let's actually get a socket with the socket method
# domain, type, and protocol are required
$sock = $sock->socket(AF_INET, SOCK_STREAM, 'tcp');

打开指定类型的套接字并返回它。域、类型和协议的指定方式与同名系统调用的相同。

socketpair

my ($r, $w) = $sock->socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC);
($r, $w) = IO::Socket::UNIX
    ->socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC);

将返回一个列表,其中包含两个已创建的套接字(读取和写入),或在失败时返回一个空列表。

perlfunc 中的 socketpair 略有不同,因为参数列表更简单一些。

sockname

my $packed_addr = $sock->sockname();

返回此连接端点的打包 sockaddr 地址。它与 getsockname(2) 相同。

sockopt

my $value = $sock->sockopt(SO_REUSEADDR);
$sock->sockopt(SO_REUSEADDR, 1);

统一的方法,用于同时在 SOL_SOCKET 级别设置和获取选项。如果使用一个参数调用,则会调用 IO::Socket 中的“getsockopt”,否则会调用 IO::Socket 中的“setsockopt”

socktype

my $type = $sock->socktype();

返回套接字类型的数字。例如,对于 SOCK_STREAM 套接字,将返回 &SOCK_STREAM 的值。

timeout

my $seconds = $sock->timeout();
my $old_val = $sock->timeout(5); # set new and return old value

设置或获取与此套接字关联的超时值(以秒为单位)。如果未提供任何参数进行调用,则返回当前设置。如果使用参数进行调用,则会更改当前设置并返回前一个值。

此方法可用于所有 IO::Socket 实现,但各个域子类可能使用或不使用此方法。

示例

让我们在 localhost:3333 上创建一个 TCP 服务器。

use strict;
use warnings;
use feature 'say';

use IO::Socket qw(AF_INET AF_UNIX SOCK_STREAM SHUT_WR);

my $server = IO::Socket->new(
    Domain => AF_INET,
    Type => SOCK_STREAM,
    Proto => 'tcp',
    LocalHost => '0.0.0.0',
    LocalPort => 3333,
    ReusePort => 1,
    Listen => 5,
) || die "Can't open socket: $IO::Socket::errstr";
say "Waiting on 3333";

while (1) {
    # waiting for a new client connection
    my $client = $server->accept();

    # get information about a newly connected client
    my $client_address = $client->peerhost();
    my $client_port = $client->peerport();
    say "Connection from $client_address:$client_port";

    # read up to 1024 characters from the connected client
    my $data = "";
    $client->recv($data, 1024);
    say "received data: $data";

    # write response data to the connected client
    $data = "ok";
    $client->send($data);

    # notify client that response has been sent
    $client->shutdown(SHUT_WR);
}

$server->close();

这样的服务器的客户端可以是

use strict;
use warnings;
use feature 'say';

use IO::Socket qw(AF_INET AF_UNIX SOCK_STREAM SHUT_WR);

my $client = IO::Socket->new(
    Domain => AF_INET,
    Type => SOCK_STREAM,
    proto => 'tcp',
    PeerPort => 3333,
    PeerHost => '0.0.0.0',
) || die "Can't open socket: $IO::Socket::errstr";

say "Sending Hello World!";
my $size = $client->send("Hello World!");
say "Sent data of length: $size";

$client->shutdown(SHUT_WR);

my $buffer;
$client->recv($buffer, 1024);
say "Got back $buffer";

$client->close();

限制

在某些系统上,对于使用 new_from_fd 创建的 IO::Socket 对象,或者从该对象中使用 "accept" in IO::Socket 创建的 IO::Socket 对象,"protocol" in IO::Socket"sockdomain" in IO::Socket"socktype" in IO::Socket 方法可能会返回 undef

另请参阅

SocketIO::HandleIO::Socket::INETIO::Socket::UNIXIO::Socket::IP

作者

Graham Barr。atmark() 由 Lincoln Stein 编写。目前由 Perl 5 Porters 维护。请将所有错误报告到 https://github.com/Perl/perl5/issues

版权

版权所有 (c) 1997-8 Graham Barr <[email protected]>。保留所有权利。本程序是免费软件;您可以在与 Perl 自身相同的条款下重新分发和/或修改它。

atmark() 实现:版权所有 2001,Lincoln Stein <[email protected]>。本模块在与 Perl 自身相同的条款下分发。只要您保留正确的归属,请随时使用、修改和重新分发它。