内容

名称

IO::Socket::IP - 支持 IPv4 和 IPv6 的与族无关的 IP 套接字

概要

use IO::Socket::IP;

my $sock = IO::Socket::IP->new(
   PeerHost => "www.google.com",
   PeerPort => "http",
   Type     => SOCK_STREAM,
) or die "Cannot construct socket - $@";

my $familyname = ( $sock->sockdomain == PF_INET6 ) ? "IPv6" :
                 ( $sock->sockdomain == PF_INET  ) ? "IPv4" :
                                                     "unknown";

printf "Connected to google via %s\n", $familyname;

描述

此模块提供了一种与协议无关的方式来使用 IPv4 和 IPv6 套接字,旨在替代 IO::Socket::INET。大多数构造函数参数和方法都以向后兼容的方式提供。有关已知差异的列表,请参见下面的 IO::Socket::INET 不兼容性部分。

它使用 getaddrinfo(3) 函数将主机名和服务名或端口号转换为要连接或监听的一组可能的地址。这使它能够在系统支持的情况下用于 IPv6,同时在不支持的系统上回退到仅 IPv4。

替换 IO::Socket 默认行为

通过将 -register 放入 IO::Socket::IP 的导入列表中,它将向 IO::Socket 注册自身,作为处理 PF_INET 的类。它还会要求处理 PF_INET6,前提是该常量可用。

更改 IO::Socket 的默认行为意味着,使用 PF_INETPF_INET6 作为 Domain 参数调用 IO::Socket 构造函数将生成一个 IO::Socket::IP 对象。

use IO::Socket::IP -register;

my $sock = IO::Socket->new(
   Domain    => PF_INET6,
   LocalHost => "::1",
   Listen    => 1,
) or die "Cannot create socket - $@\n";

print "Created a socket of type " . ref($sock) . "\n";

请注意,-register 是一个全局设置,适用于整个程序;它不能仅对某些调用者应用,也不能删除或通过词法范围限制。

构造函数

new

$sock = IO::Socket::IP->new( %args )

创建一个新的 IO::Socket::IP 对象,其中包含根据传递的命名参数创建的新套接字句柄。识别出的参数是

PeerHost => STRING
PeerService => STRING

要连接到的对等方的主机名和服务名称。服务名称可以作为端口号给出,也可以作为十进制字符串给出。

PeerAddr => STRING
PeerPort => STRING

为了与访问器方法对称并与 IO::Socket::INET 兼容,这些被接受为 PeerHostPeerService 的同义词。

PeerAddrInfo => ARRAY

指定要连接到的对等方的另一种形式。这应该是一个由 Socket::getaddrinfo 返回的数组。

此参数优先于 Peer*FamilyTypeProto 参数。

LocalHost => STRING
LocalService => STRING

要绑定到的本地地址的主机名和服务名称。

LocalAddr => STRING
LocalPort => STRING

为了与访问器方法对称并与 IO::Socket::INET 兼容,这些被接受为 LocalHostLocalService 的同义词。

LocalAddrInfo => ARRAY

指定绑定到 bind() 的本地地址的备用形式。这应该是一个由 Socket::getaddrinfo 返回的数组形式。

此参数优先于 Local*FamilyTypeProto 参数。

Family => INT

要传递给 getaddrinfo 的地址族(例如 AF_INETAF_INET6)。通常,这将保持未定义,getaddrinfo 将使用系统支持的任何地址族进行搜索。

Type => INT

要传递给 getaddrinfo 的套接字类型(例如 SOCK_STREAMSOCK_DGRAM)。通常由调用者定义;如果保持未定义,getaddrinfo 可能会尝试从服务名称推断类型。

Proto => STRING 或 INT

套接字要使用的 IP 协议(例如 'tcp'IPPROTO_TCP'udp'IPPROTO_UDP)。通常,这将保持未定义,getaddrinfo 或内核将选择适当的值。可以以字符串名称或数字形式给出。

GetAddrInfoFlags => INT

要传递给 getaddrinfo() 函数的更多标志。如果未提供,将使用默认值 AI_ADDRCONFIG

如果给出 Listen 参数,这些标志将与 AI_PASSIVE 结合。有关更多信息,请参阅 Socket 模块中关于 getaddrinfo() 的文档。

Listen => INT

如果定义,则将套接字置于监听模式,其中可以使用 accept 方法接受新连接。给出的值用作 listen(2) 队列大小。

ReuseAddr => BOOL

如果为真,则设置 SO_REUSEADDR sockopt

ReusePort => BOOL

如果为真,则设置 SO_REUSEPORT sockopt(并非所有操作系统都实现此 sockopt)

Broadcast => BOOL

如果为真,则设置 SO_BROADCAST sockopt

Sockopts => ARRAY

一个可选的数组,用于在上面列出的三个选项之后应用其他套接字选项。该值是一个包含 2 或 3 个元素的 ARRAYrefs 的 ARRAY。每个内部数组都与单个选项相关联,给出级别和选项名称,以及一个可选值。如果缺少值元素,它将被赋予一个平台大小的整数 1 常量值(即适合启用大多数常见的布尔选项)。

例如,下面给出的两个选项都等效于设置 ReuseAddr

Sockopts => [
   [ SOL_SOCKET, SO_REUSEADDR ],
   [ SOL_SOCKET, SO_REUSEADDR, pack( "i", 1 ) ],
]
V6Only => BOOL

如果定义,则在创建 PF_INET6 套接字时将 IPV6_V6ONLY sockopt 设置为给定值。如果为真,则监听模式套接字将只监听 AF_INET6 地址;如果为假,它还将接受来自 AF_INET 地址的连接。

如果未定义,套接字选项将不会更改,并将应用操作系统设置的默认值。为了跨平台的可重复行为,建议始终为监听模式套接字定义此值。

请注意,并非所有平台都支持禁用此选项。至少 OpenBSD 和 MirBSD 会在您尝试禁用它时失败并出现 EINVAL。要确定是否可以禁用,您可以使用类方法

if( IO::Socket::IP->CAN_DISABLE_V6ONLY ) {
   ...
}
else {
   ...
}

如果您的平台不支持禁用此选项,但您仍然希望监听 AF_INETAF_INET6 连接,则必须创建两个监听套接字,一个绑定到每个协议。

MultiHomed

IO::Socket::INET 风格的参数将被忽略,除非它被定义但为假。请参阅下面的 IO::Socket::INET 不兼容性部分。

但是,它启用的行为始终由 IO::Socket::IP 执行。

Blocking => BOOL

如果定义但为假,套接字将设置为非阻塞模式。否则,它将默认为阻塞模式。有关更多详细信息,请参阅下面的非阻塞部分。

Timeout => NUM

如果定义,则在阻塞模式下,每次 connect() 调用阻塞的最大时间(以秒为单位)。如果缺失,则除了底层操作系统提供的超时之外,不会应用任何超时。在非阻塞模式下,此参数将被忽略。

请注意,如果主机名解析为多个地址候选,则相同的超时将分别应用于每次连接尝试,而不是整个操作。进一步注意,超时不适用于初始主机名解析操作(如果通过主机名连接)。

此行为是受 IO::Socket::INET 的启发而复制的;为了更细粒度地控制连接超时,请考虑直接执行非阻塞连接。

如果既没有提供 Type 也没有提供 Proto 提示,则将分别设置 SOCK_STREAMIPPROTO_TCP 的默认值,以保持与 IO::Socket::INET 的兼容性。其他未识别的命名参数将被忽略。

如果既没有传递 Family 也没有传递任何主机或地址,也没有传递任何 *AddrInfo,则构造函数没有关于要创建哪个套接字族的信息。在这种情况下,它使用 AI_ADDRCONFIG 标志、没有主机名和 "0" 的服务名执行 getaddinfo 调用,并使用第一个返回结果的族。

如果构造函数失败,它将设置 $@ 为适当的错误消息;这可能是来自 $! 或其他一些字符串;并非所有失败都一定与 errno 值相关联。

new (一个参数)

$sock = IO::Socket::IP->new( $peeraddr )

作为特殊情况,如果构造函数传递一个参数(而不是偶数个键值对列表),则该参数被视为 PeerAddr 参数的值。它以相同的方式解析,根据下面 PeerHostLocalHost 解析部分中给出的行为。

方法

除了以下方法外,此类还继承了 IO::SocketIO::Handle 中的所有方法。

sockhost_service

( $host, $service ) = $sock->sockhost_service( $numeric )

返回本地地址的主机名和服务名(即由 sockname 方法给出的套接字地址)。

如果 $numeric 为真,则这些将以数字形式给出,而不是解析为名称。

以下四个方便的包装器可用于获取此处返回的两个值之一。如果需要主机名和服务名,则此方法优于以下包装器,因为它只会调用 getnameinfo(3) 一次。

sockhost

$addr = $sock->sockhost

以文本形式返回本地地址的数字形式

sockport

$port = $sock->sockport

返回本地端口号的数字形式

sockhostname

$host = $sock->sockhostname

返回本地地址的解析名称

sockservice

$service = $sock->sockservice

返回本地端口号的解析名称

sockaddr

$addr = $sock->sockaddr

以二进制八位字节字符串形式返回本地地址

peerhost_service

( $host, $service ) = $sock->peerhost_service( $numeric )

返回对端地址的主机名和服务名(即由 peername 方法给出的套接字地址),类似于 sockhost_service 方法。

以下四个方便的包装器可用于获取此处返回的两个值之一。如果需要主机名和服务名,则此方法优于以下包装器,因为它只会调用 getnameinfo(3) 一次。

peerhost

$addr = $sock->peerhost

以文本形式返回对端地址的数字形式

peerport

$port = $sock->peerport

返回对端端口号的数字形式

peerhostname

$host = $sock->peerhostname

返回对端地址的解析名称

peerservice

$service = $sock->peerservice

返回对端端口号的解析名称

peeraddr

$addr = $peer->peeraddr

以二进制八位字节字符串形式返回对端地址

as_inet

$inet = $sock->as_inet

返回一个新的 IO::Socket::INET 实例,它包装了相同的句柄。这在需要(为了向后兼容性)使用 IO::Socket::INET 类型的真实对象而不是 IO::Socket::IP 的情况下可能很有用。新对象将包装与原始对象相同的底层套接字句柄,因此应注意不要同时使用这两个对象。理想情况下,在调用此方法后应丢弃原始 $sock

此方法检查套接字域是否为 PF_INET,如果不是,则会抛出异常。

非阻塞

如果构造函数为Blocking参数传递了已定义但为假的 value,则套接字将进入非阻塞模式。在非阻塞模式下,套接字在构造函数返回时不会被设置,因为底层的connect(2)系统调用否则将必须阻塞。

非阻塞行为是IO::Socket::INET API 的扩展,是IO::Socket::IP 独有的,因为前者不支持多宿主非阻塞连接。

在使用非阻塞模式时,调用者必须反复检查文件句柄的可写性(例如使用selectIO::Poll)。每次文件句柄准备写入时,必须调用connect方法,不带任何参数。请注意,某些操作系统,最显着的是MSWin32,不会使用写就绪报告connect()失败;因此,您还必须select()异常状态。

虽然connect返回 false,但$!的值指示是否应该再次尝试(通过将其设置为EINPROGRESS的值,或在 MSWin32 上设置为EWOULDBLOCK),或者是否发生了永久错误(例如ECONNREFUSED)。

一旦套接字已连接到对等方,connect将返回 true,并且套接字现在将准备使用。

请注意,对平台底层getaddrinfo(3)函数的调用可能会阻塞。如果IO::Socket::IP必须执行此查找,即使在非阻塞模式下,构造函数也会阻塞。

为了避免这种阻塞行为,调用者应该使用PeerAddrInfoLocalAddrInfo参数传递此类查找的结果。这可以通过使用Net::LibAsyncNS来实现,或者可以在子进程中调用getaddrinfo(3)函数。

use IO::Socket::IP;
use Errno qw( EINPROGRESS EWOULDBLOCK );

my @peeraddrinfo = ... # Caller must obtain the getaddinfo result here

my $socket = IO::Socket::IP->new(
   PeerAddrInfo => \@peeraddrinfo,
   Blocking     => 0,
) or die "Cannot construct socket - $@";

while( !$socket->connect and ( $! == EINPROGRESS || $! == EWOULDBLOCK ) ) {
   my $wvec = '';
   vec( $wvec, fileno $socket, 1 ) = 1;
   my $evec = '';
   vec( $evec, fileno $socket, 1 ) = 1;

   select( undef, $wvec, $evec, undef ) or die "Cannot select - $!";
}

die "Cannot connect - $!" if $!;

...

上面的示例使用select(),但任何类似的机制都应该类似地工作。IO::Socket::IP在创建新的套接字文件句柄时会小心地保留实际的文件描述符编号,因此诸如pollepoll之类的技术应该对其底层不同套接字的重新分配透明,也许是为了在PF_INETPF_INET6之间切换协议族。

有关使用IO::PollNet::LibAsyncNS的另一个示例,请参阅模块分发中的examples/nonblocking_libasyncns.pl文件。

PeerHostLocalHost 解析

为了支持IO::Socket::INET API,主机和端口信息可以传递在一个字符串中,而不是作为两个单独的参数。

如果LocalHostPeerHost(或它们的...Addr同义词)具有以下任何特殊形式,则将应用特殊解析。

...Host 参数的值将被分割,以提供主机名和端口(或服务名称)。

hostname.example.org:http    # Host name
192.0.2.1:80                 # IPv4 address
[2001:db8::1]:80             # IPv6 address

在每种情况下,端口或服务名称(例如 80)将作为 LocalServicePeerService 参数传递。

LocalServicePeerService(或它们的 ...Port 同义词)可以是服务名称、十进制数字或包含服务名称和数字的字符串,形式如下:

http(80)

在这种情况下,将首先尝试名称(http),但如果解析器不理解它,则将使用端口号(80)。

如果 ...Host 参数采用这种特殊形式,并且相应的 ...Service...Port 参数也被定义,则从 ...Host 参数解析的优先级更高,另一个将被忽略。

split_addr

( $host, $port ) = IO::Socket::IP->split_addr( $addr )

提供上述解析功能的实用方法。返回一个包含两个元素的列表,如果可以解析,则包含分割的主机名和端口描述,或者如果未识别,则包含给定的地址和 undef

IO::Socket::IP->split_addr( "hostname:http" )
                             # ( "hostname",  "http" )

IO::Socket::IP->split_addr( "192.0.2.1:80" )
                             # ( "192.0.2.1", "80"   )

IO::Socket::IP->split_addr( "[2001:db8::1]:80" )
                             # ( "2001:db8::1", "80" )

IO::Socket::IP->split_addr( "something.else" )
                             # ( "something.else", undef )

join_addr

$addr = IO::Socket::IP->join_addr( $host, $port )

执行 split_addr 反向操作的实用方法,返回通过连接指定的主机地址和端口号形成的字符串。如果需要(因为它是原始 IPv6 数字地址),主机地址将用 [] 括号括起来。

这在与 sockhost_servicepeerhost_service 方法结合使用时尤其有用。

say "Connected to ", IO::Socket::IP->join_addr( $sock->peerhost_service );

IO::Socket::INET 不兼容性

待办事项

作者

Paul Evans <[email protected]>