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_INET
或 PF_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
是一个全局设置,适用于整个程序;它不能仅对某些调用者应用,也不能删除或通过词法范围限制。
$sock = IO::Socket::IP->new( %args )
创建一个新的 IO::Socket::IP
对象,其中包含根据传递的命名参数创建的新套接字句柄。识别出的参数是
要连接到的对等方的主机名和服务名称。服务名称可以作为端口号给出,也可以作为十进制字符串给出。
为了与访问器方法对称并与 IO::Socket::INET
兼容,这些被接受为 PeerHost
和 PeerService
的同义词。
指定要连接到的对等方的另一种形式。这应该是一个由 Socket::getaddrinfo
返回的数组。
此参数优先于 Peer*
、Family
、Type
和 Proto
参数。
要绑定到的本地地址的主机名和服务名称。
为了与访问器方法对称并与 IO::Socket::INET
兼容,这些被接受为 LocalHost
和 LocalService
的同义词。
指定绑定到 bind()
的本地地址的备用形式。这应该是一个由 Socket::getaddrinfo
返回的数组形式。
此参数优先于 Local*
、Family
、Type
和 Proto
参数。
要传递给 getaddrinfo
的地址族(例如 AF_INET
、AF_INET6
)。通常,这将保持未定义,getaddrinfo
将使用系统支持的任何地址族进行搜索。
要传递给 getaddrinfo
的套接字类型(例如 SOCK_STREAM
、SOCK_DGRAM
)。通常由调用者定义;如果保持未定义,getaddrinfo
可能会尝试从服务名称推断类型。
套接字要使用的 IP 协议(例如 'tcp'
、IPPROTO_TCP
、'udp'
、IPPROTO_UDP
)。通常,这将保持未定义,getaddrinfo
或内核将选择适当的值。可以以字符串名称或数字形式给出。
要传递给 getaddrinfo()
函数的更多标志。如果未提供,将使用默认值 AI_ADDRCONFIG
。
如果给出 Listen
参数,这些标志将与 AI_PASSIVE
结合。有关更多信息,请参阅 Socket 模块中关于 getaddrinfo()
的文档。
如果定义,则将套接字置于监听模式,其中可以使用 accept
方法接受新连接。给出的值用作 listen(2)
队列大小。
如果为真,则设置 SO_REUSEADDR
sockopt
如果为真,则设置 SO_REUSEPORT
sockopt(并非所有操作系统都实现此 sockopt)
如果为真,则设置 SO_BROADCAST
sockopt
一个可选的数组,用于在上面列出的三个选项之后应用其他套接字选项。该值是一个包含 2 或 3 个元素的 ARRAYrefs 的 ARRAY。每个内部数组都与单个选项相关联,给出级别和选项名称,以及一个可选值。如果缺少值元素,它将被赋予一个平台大小的整数 1 常量值(即适合启用大多数常见的布尔选项)。
例如,下面给出的两个选项都等效于设置 ReuseAddr
。
Sockopts => [
[ SOL_SOCKET, SO_REUSEADDR ],
[ SOL_SOCKET, SO_REUSEADDR, pack( "i", 1 ) ],
]
如果定义,则在创建 PF_INET6
套接字时将 IPV6_V6ONLY
sockopt 设置为给定值。如果为真,则监听模式套接字将只监听 AF_INET6
地址;如果为假,它还将接受来自 AF_INET
地址的连接。
如果未定义,套接字选项将不会更改,并将应用操作系统设置的默认值。为了跨平台的可重复行为,建议始终为监听模式套接字定义此值。
请注意,并非所有平台都支持禁用此选项。至少 OpenBSD 和 MirBSD 会在您尝试禁用它时失败并出现 EINVAL
。要确定是否可以禁用,您可以使用类方法
if( IO::Socket::IP->CAN_DISABLE_V6ONLY ) {
...
}
else {
...
}
如果您的平台不支持禁用此选项,但您仍然希望监听 AF_INET
和 AF_INET6
连接,则必须创建两个监听套接字,一个绑定到每个协议。
此 IO::Socket::INET
风格的参数将被忽略,除非它被定义但为假。请参阅下面的 IO::Socket::INET
不兼容性部分。
但是,它启用的行为始终由 IO::Socket::IP
执行。
如果定义但为假,套接字将设置为非阻塞模式。否则,它将默认为阻塞模式。有关更多详细信息,请参阅下面的非阻塞部分。
如果定义,则在阻塞模式下,每次 connect()
调用阻塞的最大时间(以秒为单位)。如果缺失,则除了底层操作系统提供的超时之外,不会应用任何超时。在非阻塞模式下,此参数将被忽略。
请注意,如果主机名解析为多个地址候选,则相同的超时将分别应用于每次连接尝试,而不是整个操作。进一步注意,超时不适用于初始主机名解析操作(如果通过主机名连接)。
此行为是受 IO::Socket::INET
的启发而复制的;为了更细粒度地控制连接超时,请考虑直接执行非阻塞连接。
如果既没有提供 Type
也没有提供 Proto
提示,则将分别设置 SOCK_STREAM
和 IPPROTO_TCP
的默认值,以保持与 IO::Socket::INET
的兼容性。其他未识别的命名参数将被忽略。
如果既没有传递 Family
也没有传递任何主机或地址,也没有传递任何 *AddrInfo
,则构造函数没有关于要创建哪个套接字族的信息。在这种情况下,它使用 AI_ADDRCONFIG
标志、没有主机名和 "0"
的服务名执行 getaddinfo
调用,并使用第一个返回结果的族。
如果构造函数失败,它将设置 $@
为适当的错误消息;这可能是来自 $!
或其他一些字符串;并非所有失败都一定与 errno
值相关联。
$sock = IO::Socket::IP->new( $peeraddr )
作为特殊情况,如果构造函数传递一个参数(而不是偶数个键值对列表),则该参数被视为 PeerAddr
参数的值。它以相同的方式解析,根据下面 PeerHost
和 LocalHost
解析部分中给出的行为。
除了以下方法外,此类还继承了 IO::Socket 和 IO::Handle 中的所有方法。
( $host, $service ) = $sock->sockhost_service( $numeric )
返回本地地址的主机名和服务名(即由 sockname
方法给出的套接字地址)。
如果 $numeric
为真,则这些将以数字形式给出,而不是解析为名称。
以下四个方便的包装器可用于获取此处返回的两个值之一。如果需要主机名和服务名,则此方法优于以下包装器,因为它只会调用 getnameinfo(3)
一次。
$addr = $sock->sockhost
以文本形式返回本地地址的数字形式
$port = $sock->sockport
返回本地端口号的数字形式
$host = $sock->sockhostname
返回本地地址的解析名称
$service = $sock->sockservice
返回本地端口号的解析名称
$addr = $sock->sockaddr
以二进制八位字节字符串形式返回本地地址
( $host, $service ) = $sock->peerhost_service( $numeric )
返回对端地址的主机名和服务名(即由 peername
方法给出的套接字地址),类似于 sockhost_service
方法。
以下四个方便的包装器可用于获取此处返回的两个值之一。如果需要主机名和服务名,则此方法优于以下包装器,因为它只会调用 getnameinfo(3)
一次。
$addr = $sock->peerhost
以文本形式返回对端地址的数字形式
$port = $sock->peerport
返回对端端口号的数字形式
$host = $sock->peerhostname
返回对端地址的解析名称
$service = $sock->peerservice
返回对端端口号的解析名称
$addr = $peer->peeraddr
以二进制八位字节字符串形式返回对端地址
$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
独有的,因为前者不支持多宿主非阻塞连接。
在使用非阻塞模式时,调用者必须反复检查文件句柄的可写性(例如使用select
或IO::Poll
)。每次文件句柄准备写入时,必须调用connect
方法,不带任何参数。请注意,某些操作系统,最显着的是MSWin32
,不会使用写就绪报告connect()
失败;因此,您还必须select()
异常状态。
虽然connect
返回 false,但$!
的值指示是否应该再次尝试(通过将其设置为EINPROGRESS
的值,或在 MSWin32 上设置为EWOULDBLOCK
),或者是否发生了永久错误(例如ECONNREFUSED
)。
一旦套接字已连接到对等方,connect
将返回 true,并且套接字现在将准备使用。
请注意,对平台底层getaddrinfo(3)
函数的调用可能会阻塞。如果IO::Socket::IP
必须执行此查找,即使在非阻塞模式下,构造函数也会阻塞。
为了避免这种阻塞行为,调用者应该使用PeerAddrInfo
或LocalAddrInfo
参数传递此类查找的结果。这可以通过使用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
在创建新的套接字文件句柄时会小心地保留实际的文件描述符编号,因此诸如poll
或epoll
之类的技术应该对其底层不同套接字的重新分配透明,也许是为了在PF_INET
和PF_INET6
之间切换协议族。
有关使用IO::Poll
和Net::LibAsyncNS
的另一个示例,请参阅模块分发中的examples/nonblocking_libasyncns.pl文件。
PeerHost
和 LocalHost
解析为了支持IO::Socket::INET
API,主机和端口信息可以传递在一个字符串中,而不是作为两个单独的参数。
如果LocalHost
或PeerHost
(或它们的...Addr
同义词)具有以下任何特殊形式,则将应用特殊解析。
...Host
参数的值将被分割,以提供主机名和端口(或服务名称)。
hostname.example.org:http # Host name
192.0.2.1:80 # IPv4 address
[2001:db8::1]:80 # IPv6 address
在每种情况下,端口或服务名称(例如 80
)将作为 LocalService
或 PeerService
参数传递。
LocalService
或 PeerService
(或它们的 ...Port
同义词)可以是服务名称、十进制数字或包含服务名称和数字的字符串,形式如下:
http(80)
在这种情况下,将首先尝试名称(http
),但如果解析器不理解它,则将使用端口号(80
)。
如果 ...Host
参数采用这种特殊形式,并且相应的 ...Service
或 ...Port
参数也被定义,则从 ...Host
参数解析的优先级更高,另一个将被忽略。
( $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 )
$addr = IO::Socket::IP->join_addr( $host, $port )
执行 split_addr
反向操作的实用方法,返回通过连接指定的主机地址和端口号形成的字符串。如果需要(因为它是原始 IPv6 数字地址),主机地址将用 []
括号括起来。
这在与 sockhost_service
或 peerhost_service
方法结合使用时尤其有用。
say "Connected to ", IO::Socket::IP->join_addr( $sock->peerhost_service );
IO::Socket::INET
不兼容性MultiHomed
启用的行为实际上是由 IO::Socket::IP
实现的,因为它需要正确支持从 getaddrinfo(3)
调用的结果中搜索可用的地址。构造函数将忽略此参数的值,除非它被定义但为假。在这种情况下会抛出异常,因为这将请求它首先禁用 getaddrinfo(3)
搜索行为。
IO::Socket::IP
实现 Blocking
和 Timeout
参数,但它以不同的方式实现这两个参数的交互。
在 ::INET
中,提供超时会覆盖非阻塞行为,这意味着即使调用者请求非阻塞套接字,connect()
操作仍然会阻塞。这在其文档中没有明确说明,该作者也不认为这是一个有用的行为 - 它似乎来自实现中的一个怪癖。
因此,在 ::IP
中,Blocking
参数优先 - 如果请求非阻塞套接字,则任何操作都不会阻塞。这里的 Timeout
参数只是定义阻塞 connect()
调用将等待的最大时间(如果它确实阻塞了)。
为了专门获得将此组合选项指定给 ::INET
时使用 ::IP
的“阻塞连接然后非阻塞发送和接收”行为,请先执行阻塞连接,然后将套接字转换为非阻塞模式。
my $sock = IO::Socket::IP->new(
PeerHost => $peer,
Timeout => 20,
) or die "Cannot connect - $@";
$sock->blocking( 0 );
这段代码在 IO::Socket::INET
和 IO::Socket::IP
下的行为完全相同。
调查 POSIX::dup2
是否会影响 BSD 的 kqueue
观察器,如果是,考虑可以应用哪些可能的解决方法。
Paul Evans <[email protected]>