套接字 也可能指代函数:socket

内容

名称

Socket - 网络常量和支持函数

概要

Socket 是一个低级模块,用于包括 IO::Socket 模块系列在内的各种模块。以下示例演示了一些低级用法,但实际程序可能会使用 IO::Socket 或类似模块提供的更高级别 API。

use Socket qw(PF_INET SOCK_STREAM pack_sockaddr_in inet_aton);

socket(my $socket, PF_INET, SOCK_STREAM, 0)
    or die "socket: $!";

my $port = getservbyname "echo", "tcp";
connect($socket, pack_sockaddr_in($port, inet_aton("localhost")))
    or die "connect: $!";

print $socket "Hello, world!\n";
print <$socket>;

另请参阅 "示例" 部分。

描述

此模块提供各种与基于套接字的网络相关的常量、结构操作符和其他函数。提供的这些值和函数在与 Perl 核心函数(如 socket()、setsockopt() 和 bind())结合使用时非常有用。它还提供了一些其他支持函数,主要用于处理网络地址在人类可读形式和本机二进制形式之间的转换,以及用于主机名解析器操作。

此模块默认导出一些常量和函数;但为了向后兼容性,任何新添加的符号都不会默认导出,必须显式请求。当向 use Socket 行提供导入列表时,不会自动导入默认导出。因此,最佳实践是始终显式列出所有所需的符号。

此外,还提供了一些常见的套接字“换行符”常量:常量 CRLFCRLF,以及 $CR$LF$CRLF,它们分别映射到 \015\012\015\012。如果您不想在程序中使用字面字符,请使用此处提供的常量。它们不会默认导出,但可以单独导入,并使用 :crlf 导出标签

use Socket qw(:DEFAULT :crlf);

$sock->print("GET / HTTP/1.0$CRLF");

整个 getaddrinfo() 子系统可以使用标签 :addrinfo 导出;这将导出 getaddrinfo() 和 getnameinfo() 函数,以及所有 AI_*NI_*NIx_*EAI_* 常量。

常量

在以下每个组中,可能提供的常量比标题中给出的示例多得多。如果标题以 ... 结尾,则表示可能还有更多;提供的确切常量将取决于编译时找到的操作系统和头文件。

PF_INET、PF_INET6、PF_UNIX、...

协议族常量,用作 socket() 的第一个参数或 SO_DOMAINSO_FAMILY 套接字选项的值。

AF_INET、AF_INET6、AF_UNIX、...

套接字地址结构使用的地址族常量,用于传递给诸如 inet_pton() 或 getaddrinfo() 之类的函数,或由诸如 sockaddr_family() 之类的函数返回。

SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、...

套接字类型常量,用作 socket() 的第二个参数,或 SO_TYPE 套接字选项的值。

SOCK_NONBLOCK。SOCK_CLOEXEC

Linux 特定的快捷方式,用于在 socket(2) 调用期间指定 O_NONBLOCKFD_CLOEXEC 标志。

socket( my $sockh, PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, 0 )

SOL_SOCKET

用于 setsockopt() 和 getsockopt() 的套接字选项级别常量。

SO_ACCEPTCONN、SO_BROADCAST、SO_ERROR、...

用于 SOL_SOCKET 级别上的 setsockopt() 和 getsockopt() 的套接字选项名称常量。

IP_OPTIONS、IP_TOS、IP_TTL、...

用于 IPPROTO_IP 级别上的 IPv4 套接字选项的套接字选项名称常量。

IP_PMTUDISC_WANT、IP_PMTUDISC_DONT、...

用于 IP_MTU_DISCOVER 套接字选项的套接字选项值常量。

IPTOS_LOWDELAY、IPTOS_THROUGHPUT、IPTOS_RELIABILITY、...

用于 IP_TOS 套接字选项的套接字选项值常量。

MSG_BCAST、MSG_OOB、MSG_TRUNC、...

用于 send() 和 recv() 的消息标志常量。

SHUT_RD、SHUT_RDWR、SHUT_WR

用于 shutdown() 的方向常量。

INADDR_ANY、INADDR_BROADCAST、INADDR_LOOPBACK、INADDR_NONE

常量,用于表示特殊AF_INET地址,包括通配符、广播、本地回环和无效地址。

通常分别等效于 inet_aton('0.0.0.0')、inet_aton('255.255.255.255')、inet_aton('localhost') 和 inet_aton('255.255.255.255')。

IPPROTO_IP、IPPROTO_IPV6、IPPROTO_TCP、...

IP 协议常量,用作 socket() 的第三个参数,getsockopt() 或 setsockopt() 的 level 参数,或 SO_PROTOCOL 套接字选项的值。

TCP_CORK、TCP_KEEPALIVE、TCP_NODELAY、...

套接字选项名称常量,用于 IPPROTO_TCP 级别上的 TCP 套接字选项。

IN6ADDR_ANY、IN6ADDR_LOOPBACK

常量,用于表示特殊AF_INET6地址,包括通配符和本地回环。

通常分别等效于 inet_pton(AF_INET6, "::") 和 inet_pton(AF_INET6, "::1")。

IPV6_ADD_MEMBERSHIP、IPV6_MTU、IPV6_V6ONLY、...

套接字选项名称常量,用于 IPPROTO_IPV6 级别上的 IPv6 套接字选项。

结构操作符

以下函数在 Perl 值列表和表示结构的打包二进制字符串之间进行转换。

$family = sockaddr_family $sockaddr

接受一个打包的套接字地址(由 pack_sockaddr_in()、pack_sockaddr_un() 或 Perl 内置函数 getsockname() 和 getpeername() 返回)。返回地址族标签。这将是 AF_* 常量之一,例如 AF_INET 用于 sockaddr_in 地址或 AF_UNIX 用于 sockaddr_un。它可以用来确定对未知类型的 sockaddr 使用什么解包。

$sockaddr = pack_sockaddr_in $port, $ip_address

接受两个参数,一个端口号和一个不透明字符串(由 inet_aton() 返回,或一个 v-string)。返回 sockaddr_in 结构,其中包含打包的这些参数,并填充 AF_INET。对于互联网域套接字,此结构通常是 bind()、connect() 和 send() 中参数所需的结构。

未定义的 $port 参数将被视为零;未定义的 $ip_address 将被视为致命错误。

($port, $ip_address) = unpack_sockaddr_in $sockaddr

接受一个 sockaddr_in 结构(由 pack_sockaddr_in()、getpeername() 或 recv() 返回)。返回一个包含两个元素的列表:端口和一个表示 IP 地址的不透明字符串(可以使用 inet_ntoa() 将地址转换为四点式数字格式)。如果结构不代表 AF_INET 地址,则会抛出异常。

在标量上下文中,将只返回 IP 地址。

$sockaddr = sockaddr_in $port, $ip_address

($port, $ip_address) = sockaddr_in $sockaddr

pack_sockaddr_in() 或 unpack_sockaddr_in() 的包装器。在列表上下文中,解包其参数并返回一个包含端口和 IP 地址的列表。在标量上下文中,将端口和 IP 地址参数打包为 sockaddr_in 并返回它。

主要为了向后兼容性提供;最好显式使用 pack_sockaddr_in() 或 unpack_sockaddr_in()。

$sockaddr = pack_sockaddr_in6 $port, $ip6_address, [$scope_id, [$flowinfo]]

接受两个到四个参数:端口号、不透明字符串(由 inet_pton() 返回)、可选的范围 ID 号和可选的流标签号。返回包含这些参数的 sockaddr_in6 结构,并填充 AF_INET6。pack_sockaddr_in() 的 IPv6 等效项。

未定义的 $port 参数将被视为零;未定义的 $ip6_address 将被视为致命错误。

($port, $ip6_address, $scope_id, $flowinfo) = unpack_sockaddr_in6 $sockaddr

接受一个 sockaddr_in6 结构。返回一个包含四个元素的列表:端口号、表示 IPv6 地址的不透明字符串、范围 ID 和流标签。(可以使用 inet_ntop() 将地址转换为通常的字符串格式)。如果结构不代表 AF_INET6 地址,则会抛出异常。

在标量上下文中,将只返回 IP 地址。

$sockaddr = sockaddr_in6 $port, $ip6_address, [$scope_id, [$flowinfo]]

($port, $ip6_address, $scope_id, $flowinfo) = sockaddr_in6 $sockaddr

pack_sockaddr_in6() 或 unpack_sockaddr_in6() 的包装器。在列表上下文中,根据 unpack_sockaddr_in6() 解包其参数。在标量上下文中,根据 pack_sockaddr_in6() 打包其参数。

主要为了向后兼容性提供;最好显式使用 pack_sockaddr_in6() 或 unpack_sockaddr_in6()。

$sockaddr = pack_sockaddr_un $path

接受一个参数,一个路径名。返回包含该路径的 sockaddr_un 结构,其中填充了 AF_UNIX。对于 PF_UNIX 套接字,此结构通常是您在 bind()、connect() 和 send() 中需要的参数。

($path) = unpack_sockaddr_un $sockaddr

接受一个 sockaddr_un 结构(由 pack_sockaddr_un()、getpeername() 或 recv() 返回)。返回一个包含一个元素的列表:路径名。如果结构不代表 AF_UNIX 地址,则会抛出异常。

$sockaddr = sockaddr_un $path

($path) = sockaddr_un $sockaddr

pack_sockaddr_un() 或 unpack_sockaddr_un() 的包装器。在列表上下文中,解包其参数并返回一个包含路径名的列表。在标量上下文中,将路径名打包为 sockaddr_un 并返回它。

主要为了向后兼容性提供;最好显式使用 pack_sockaddr_un() 或 unpack_sockaddr_un()。

这些仅在您的系统具有 <sys/un.h> 时才受支持。

$ip_mreq = pack_ip_mreq $multiaddr, $interface

接受一个 IPv4 多播地址,以及可选的接口地址(或 INADDR_ANY)。返回包含这些参数的 ip_mreq 结构。适合与 IP_ADD_MEMBERSHIPIP_DROP_MEMBERSHIP 套接字选项一起使用。

($multiaddr, $interface) = unpack_ip_mreq $ip_mreq

接受一个 ip_mreq 结构。返回一个包含两个元素的列表;IPv4 多播地址和接口地址。

$ip_mreq_source = pack_ip_mreq_source $multiaddr, $source, $interface

接受一个 IPv4 多播地址、源地址,以及可选的接口地址(或 INADDR_ANY)。返回包含这些参数的 ip_mreq_source 结构。适合与 IP_ADD_SOURCE_MEMBERSHIPIP_DROP_SOURCE_MEMBERSHIP 套接字选项一起使用。

($multiaddr, $source, $interface) = unpack_ip_mreq_source $ip_mreq

接收一个 ip_mreq_source 结构体。返回一个包含三个元素的列表:IPv4 多播地址、源地址和接口地址。

$ipv6_mreq = pack_ipv6_mreq $multiaddr6, $ifindex

接收一个 IPv6 多播地址和一个接口编号。返回一个包含这些参数的 ipv6_mreq 结构体。适用于 IPV6_ADD_MEMBERSHIPIPV6_DROP_MEMBERSHIP 套接字选项。

($multiaddr6, $ifindex) = unpack_ipv6_mreq $ipv6_mreq

接收一个 ipv6_mreq 结构体。返回一个包含两个元素的列表:IPv6 地址和接口编号。

函数

$ip_address = inet_aton $string

接收一个包含主机名或 IP 地址文本表示的字符串,并将其转换为适合传递给 pack_sockaddr_in() 的打包二进制地址结构体。如果传递了一个无法解析的主机名,则返回 undef。对于多宿主主机(具有多个地址的主机),返回找到的第一个地址。

为了可移植性,不要假设 inet_aton() 的结果是 32 位宽,换句话说,它只包含网络顺序的 IPv4 地址。

此仅限 IPv4 的函数主要出于遗留原因提供。新编写的代码应使用 getaddrinfo() 或 inet_pton() 来支持 IPv6。

$string = inet_ntoa $ip_address

接收一个打包的二进制地址结构体,例如由 unpack_sockaddr_in() 返回的结构体(或一个表示网络顺序的 IPv4 地址的四个字节的 v-string),并将其转换为 d.d.d.d 格式的字符串,其中 d 是小于 256 的数字(互联网地址的正常人类可读的四点式数字表示法)。

此仅限 IPv4 的函数主要出于遗留原因提供。新编写的代码应使用 getnameinfo() 或 inet_ntop() 来支持 IPv6。

$address = inet_pton $family, $string

接受一个地址族(例如 AF_INETAF_INET6)和一个包含该族地址文本表示的字符串,并将该字符串转换为打包的二进制地址结构。

有关更强大、更灵活的函数,请参见 getaddrinfo(),该函数可根据主机名或文本地址查找套接字地址。

$string = inet_ntop $family, $address

接受一个地址族和一个打包的二进制地址结构,并将其转换为地址的人类可读文本表示形式;通常以 d.d.d.d 形式表示 AF_INET,或以 hhhh:hhhh::hhhh 形式表示 AF_INET6

有关更强大、更灵活的函数,请参见 getnameinfo(),该函数可将套接字地址转换为人类可读文本表示形式。

($err, @result) = getaddrinfo $host, $service, [$hints]

给定主机名和服务名,此函数尝试将主机名解析为网络地址列表,并将服务名解析为协议和端口号,然后返回一个地址结构列表,这些结构适合连接到它。

仅给定主机名,此函数尝试将其解析为网络地址列表,然后返回一个地址结构列表,这些结构给出这些地址。

仅给定服务名,此函数尝试将其解析为协议和端口号,然后返回一个地址结构列表,这些结构代表它,适合绑定到它。此用法应与 AI_PASSIVE 标志结合使用;见下文。

如果两个名称都没有给出,则会生成错误。

如果存在,$hints 应该是一个对哈希的引用,其中识别以下键

flags => INT

包含 AI_* 常量的位域;见下文。

family => INT

仅限于在此地址族中生成地址

socktype => INT

仅限于生成此套接字类型的地址

protocol => INT

仅限于生成此协议的地址

返回值将是一个列表;第一个值为错误指示,后面是地址结构列表(如果未发生错误)。

错误值将是一个双变量;与EAI_*错误常量可比,或可打印为人类可读的错误消息字符串。如果未发生错误,则数值为零,且为空字符串。

结果列表中的每个值都将是一个哈希引用,包含以下字段

family => INT

地址族(例如AF_INET

socktype => INT

套接字类型(例如SOCK_STREAM

protocol => INT

协议(例如IPPROTO_TCP

addr => STRING

打包字符串中的地址(例如,由pack_sockaddr_in()返回)

canonname => STRING

如果提供了AI_CANONNAME标志,则为主机提供规范名称,否则为undef。此字段仅在第一个返回的地址上存在。

$hints 哈希中识别以下标志常量。其他标志常量可能存在,由操作系统提供。

AI_PASSIVE

指示此解析是针对被动(即监听)套接字的本地bind(),而不是主动(即连接)套接字。

AI_CANONNAME

指示调用者希望填充结果的规范主机名(canonname)字段。

AI_NUMERICHOST

指示调用者将传递数字地址,而不是主机名,并且getaddrinfo()不得对该名称执行解析操作。此标志将阻止可能缓慢的网络查找操作,而是返回错误,如果传递了主机名。

($err, $hostname, $servicename) = getnameinfo $sockaddr, [$flags, [$xflags]]

给定一个打包的套接字地址(例如,来自getsockname()、getpeername(),或由getaddrinfo()在addr字段中返回),返回它表示的主机名和符号服务名称。$flags可以是NI_*常量的位掩码,或者如果未指定,则默认为0。

返回值将是一个列表;第一个值为错误条件,其次为主机名和服务名称。

错误值将是一个双变量;与EAI_*错误常量可比,或可打印为人类可读的错误消息字符串。主机名和服务名称将是纯字符串。

以下标志常量被识别为$flags。其他标志常量可能存在,由操作系统提供。

NI_NUMERICHOST

请求直接返回数字地址的人类可读字符串表示,而不是执行可能将其转换为主机名的名称解析操作。这还将避免潜在的阻塞网络 I/O。

NI_NUMERICSERV

请求直接以数字表示形式返回端口号,而不是执行可能将其转换为服务名称的名称解析操作。

NI_NAMEREQD

如果名称解析操作未能提供名称,则此标志将导致 getnameinfo() 指示错误,而不是将数字表示形式作为人类可读字符串返回。

NI_DGRAM

指示套接字地址与 SOCK_DGRAM 套接字相关联,用于其名称在 TCP 和 UDP 协议之间不同的服务。

以下常量可以作为 $xflags 提供。

NIx_NOHOST

指示调用者对结果的主机名不感兴趣,因此无需将其转换。undef 将作为主机名返回。

NIx_NOSERV

指示调用者对结果的服务名称不感兴趣,因此无需将其转换。undef 将作为服务名称返回。

getaddrinfo() / getnameinfo() 错误常量

以下常量可能由 getaddrinfo() 或 getnameinfo() 返回。其他常量可能由操作系统提供。

EAI_AGAIN

名称解析期间发生临时错误。如果稍后重试,操作可能会成功。

EAI_BADFLAGS

flags 提示到 getaddrinfo() 的值,或 getnameinfo() 的 $flags 参数包含无法识别的标志。

EAI_FAMILY

family 提示到 getaddrinfo(),或传递给 getnameinfo() 的套接字地址的族不受支持。

EAI_NODATA

提供给 getaddrinfo() 的主机名未提供任何可用的地址数据。

EAI_NONAME

提供给 getaddrinfo() 的主机名不存在,或者提供给 getnameinfo() 的地址未与主机名关联,并且提供了 NI_NAMEREQD 标志。

EAI_SERVICE

传递给 getaddrinfo() 的服务名称对于 $hints 中给定的套接字类型不可用。

示例

连接查找

getaddrinfo() 函数将主机名和服务名称转换为结构列表,每个结构包含连接到命名主机上的命名服务的潜在方式。

use IO::Socket;
use Socket qw(SOCK_STREAM getaddrinfo);

my %hints = (socktype => SOCK_STREAM);
my ($err, @res) = getaddrinfo("localhost", "echo", \%hints);
die "Cannot getaddrinfo - $err" if $err;

my $sock;

foreach my $ai (@res) {
    my $candidate = IO::Socket->new();

    $candidate->socket($ai->{family}, $ai->{socktype}, $ai->{protocol})
        or next;

    $candidate->connect($ai->{addr})
        or next;

    $sock = $candidate;
    last;
}

die "Cannot connect to localhost:echo" unless $sock;

$sock->print("Hello, world!\n");
print <$sock>;

由于返回了潜在候选者的列表,因此 while 循环依次尝试每个候选者,直到找到一个同时成功执行 socket() 和 connect() 调用的候选者。

此函数执行传统函数 gethostbyname()、getservbyname()、inet_aton() 和 pack_sockaddr_in() 的工作。

实际上,此逻辑最好由 IO::Socket::IP 执行。

将地址转换为人类可读的字符串

getnameinfo() 函数将套接字地址(例如由 getsockname() 或 getpeername() 返回的地址)转换为一对人类可读的字符串,分别表示地址和服务名称。

use IO::Socket::IP;
use Socket qw(getnameinfo);

my $server = IO::Socket::IP->new(LocalPort => 12345, Listen => 1) or
    die "Cannot listen - $@";

my $socket = $server->accept or die "accept: $!";

my ($err, $hostname, $servicename) = getnameinfo($socket->peername);
die "Cannot getnameinfo - $err" if $err;

print "The peer is connected from $hostname\n";

由于在此示例中仅使用了主机名,因此可以通过传递 NIx_NOSERV 标志来省略端口号转换为服务名称的冗余转换。

use Socket qw(getnameinfo NIx_NOSERV);

my ($err, $hostname) = getnameinfo($socket->peername, 0, NIx_NOSERV);

此函数执行传统函数 unpack_sockaddr_in()、inet_ntoa()、gethostbyaddr() 和 getservbyport() 的工作。

实际上,此逻辑最好由 IO::Socket::IP 执行。

将主机名解析为 IP 地址

要将主机名转换为人类可读的纯 IP 地址,请使用 getaddrinfo() 将主机名转换为套接字结构列表,然后对每个结构使用 getnameinfo() 将其再次转换为可读的 IP 地址。

use Socket qw(:addrinfo SOCK_RAW);

my ($err, @res) = getaddrinfo($hostname, "", {socktype => SOCK_RAW});
die "Cannot getaddrinfo - $err" if $err;

while( my $ai = shift @res ) {
    my ($err, $ipaddr) = getnameinfo($ai->{addr}, NI_NUMERICHOST, NIx_NOSERV);
    die "Cannot getnameinfo - $err" if $err;

    print "$ipaddr\n";
}

传递给 getaddrinfo() 的 socktype 提示会过滤结果,使其仅包含一种套接字类型和协议。如果没有此提示,大多数操作系统会返回三种组合,分别用于 SOCK_STREAMSOCK_DGRAMSOCK_RAW,导致地址的输出重复三次。传递给 getnameinfo() 的 NI_NUMERICHOST 标志会导致它返回字符串格式的纯 IP 地址,而不是将其反向解析回主机名。

此组合执行传统函数 gethostbyname() 和 inet_ntoa() 的工作。

访问套接字选项

许多 SO_* 和其他常量为 getsockopt() 和 setsockopt() 提供套接字选项名称。

use IO::Socket::INET;
use Socket qw(SOL_SOCKET SO_RCVBUF IPPROTO_IP IP_TTL);

my $socket = IO::Socket::INET->new(LocalPort => 0, Proto => 'udp')
    or die "Cannot create socket: $@";

$socket->setsockopt(SOL_SOCKET, SO_RCVBUF, 64*1024) or
    die "setsockopt: $!";

print "Receive buffer is ", $socket->getsockopt(SOL_SOCKET, SO_RCVBUF),
    " bytes\n";

print "IP TTL is ", $socket->getsockopt(IPPROTO_IP, IP_TTL), "\n";

为了方便起见,IO::Socket 的 setsockopt() 方法会将数字转换为打包的字节缓冲区,而 getsockopt() 会将正确大小的字节缓冲区解包回数字。

作者

此模块最初由 Perl 5 维护者在 Perl 核心代码库中维护。

它在 1.95 版本时被 Paul Evans <[email protected]> 提取到 CPAN 上,并采用双重许可。