內容

名稱

IO::Socket::IP - 支援 IPv4 和 IPv6 的與通訊協定無關的 IP socket

語法

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 socket,旨在取代 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 物件,其中包含一個根據傳遞的名稱引數建立的新 socket 句柄。識別的引數為

PeerHost => STRING
PeerService => STRING

對等方的主機名稱和服務名稱以 connect() 連線。服務名稱可以數字字串形式提供為埠號。

PeerAddr => STRING
PeerPort => STRING

為了與存取器方法對稱,並與 IO::Socket::INET 相容,這些方法分別被接受為 PeerHostPeerService 的同義詞。

PeerAddrInfo => ARRAY

指定對等方以 connect() 連線的替代形式。這應為由 Socket::getaddrinfo 傳回的陣列形式。

此參數優先於 Peer*FamilyTypeProto 引數。

LocalHost => 字串
LocalService => 字串

要繫結到 bind() 的本機地址的主機名稱和服務名稱。

LocalAddr => 字串
LocalPort => 字串

為了與存取器方法對稱,且與 IO::Socket::INET 相容,這些方法分別被接受為 LocalHostLocalService 的同義詞。

LocalAddrInfo => 陣列

指定要繫結到的本機地址的替代形式。這應該是 Socket::getaddrinfo 傳回的陣列形式。

這個參數優先於 Local*FamilyTypeProto 參數。

Family => 整數

傳遞到 getaddrinfo 的地址系列 (例如 AF_INETAF_INET6)。通常這會保持未定義,而 getaddrinfo 會使用系統支援的任何地址系列進行搜尋。

Type => 整數

傳遞到 getaddrinfo 的 Socket 類型 (例如 SOCK_STREAMSOCK_DGRAM)。通常由呼叫者定義;如果保持未定義,getaddrinfo 會嘗試從服務名稱推斷類型。

Proto => 字串或整數

要使用的 Socket 的 IP 協定 (例如 'tcp'IPPROTO_TCP'udp'IPPROTO_UDP)。通常這會保持未定義,而 getaddrinfo 或核心會選擇適當的值。可以字串名稱或數字形式提供。

GetAddrInfoFlags => 整數

傳遞到 getaddrinfo() 函式的更多旗標。如果未提供,將使用預設值 AI_ADDRCONFIG

如果提供了 Listen 參數,這些旗標會與 AI_PASSIVE 結合。如需更多資訊,請參閱 Socket 模組中關於 getaddrinfo() 的文件。

Listen => INT

如果已定義,將套接字置於監聽模式,可以使用 accept 方法接受新連線。所給的值用作 listen(2)佇列大小。

ReuseAddr => BOOL

如果為 true,設定 SO_REUSEADDR sockopt

ReusePort => BOOL

如果為 true,設定 SO_REUSEPORT sockopt(並非所有作業系統都實作此 sockopt)

Broadcast => BOOL

如果為 true,設定 SO_BROADCAST sockopt

Sockopts => ARRAY

在上述三個選項之後套用的其他套接字選項的選用陣列。該值是一個包含 2 或 3 個元素的 ARRAYref 的 ARRAY。每個內部陣列都與單一選項相關,提供層級和選項名稱,以及一個選用值。如果值元素遺失,它將被賦予平台大小整數 1 常數的值(即適合啟用大多數常見布林選項)。

例如,下面給出的兩個選項都等於設定 ReuseAddr

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

如果已定義,在建立 PF_INET6 套接字時將 IPV6_V6ONLY sockopt 設定為給定值。如果為 true,監聽模式套接字將只會在 AF_INET6 位址上監聽;如果為 false,它也會接受來自 AF_INET 位址的連線。

如果未定義,套接字選項將不會被變更,且會套用作業系統設定的預設值。為了在各平台上具有可重複的行為,建議對於監聽模式套接字始終定義此值。

請注意,並非所有平台都支援停用此選項。有些平台,至少 OpenBSD 和 MirBSD,如果嘗試停用它,將會失敗並顯示 EINVAL。若要判斷是否可以停用,可以使用類別方法

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

如果您的平台不支援停用此選項,但您仍想要監聽 AF_INETAF_INET6 連線,您必須建立兩個監聽套接字,一個繫結到每個通訊協定。

MultiHomed

IO::Socket::INET 風格的引數會被忽略,除非它已定義但為 false。請參閱下列 IO::Socket::INET 不相容性區段。

然而,它啟用的行為始終會由 IO::Socket::IP 執行。

Blocking => BOOL

如果已定義但為 false,則套接字將設定為非封鎖模式。否則,它將預設為封鎖模式。有關更多詳細資訊,請參閱下方的非封鎖區段。

Timeout => NUM

如果已定義,則在封鎖模式下,提供每個 connect() 呼叫封鎖的最大時間(以秒為單位)。如果遺失,則不會套用逾時,除了基礎作業系統提供的逾時。在非封鎖模式下,此參數將被忽略。

請注意,如果主機名稱解析為多個位址候選,則相同的逾時將個別套用於每個連線嘗試,而不是整個作業。進一步注意,逾時不適用於初始主機名稱解析作業(如果透過主機名稱連線)。

此行為是從 IO::Socket::INET 複製而來的靈感;若要更精細地控制連線逾時,請考慮直接執行非封鎖連線。

如果未提供 TypeProto 提示,則分別設定 SOCK_STREAMIPPROTO_TCP 預設值,以維持與 IO::Socket::INET 的相容性。未識別的其他命名參數將被忽略。

如果未傳遞 Family 或任何主機或位址,也未傳遞任何 *AddrInfo,則建構函式沒有資訊來決定要建立哪個套接字系列。在這種情況下,它會執行 getaddinfo 呼叫,其中包含 AI_ADDRCONFIG 旗標、沒有主機名稱,以及服務名稱 "0",並使用第一個傳回結果的系列。

如果建構函式失敗,它會將 $@ 設定為適當的錯誤訊息;這可能是來自 $! 或其他字串;並非每個失敗都一定有相關的 errno 值。

new (one arg)

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

作為特殊情況,如果建構函式傳遞一個單一參數(相對於偶數大小的鍵值對清單),則視為 PeerAddr 參數的值。它會以相同的方式進行剖析,根據下方 PeerHostLocalHost 剖析區段中提供的行為。

方法

除了下列方法外,此類別還會繼承 IO::SocketIO::Handle 中的所有方法。

sockhost_service

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

傳回本機位址的主機名稱和服務名稱(也就是 `sockname` 方法所提供的 socket 位址)。

如果 `$numeric` 為 true,這些值會以數字形式提供,而不是解析成名稱。

以下四個方便的包裝函式可用於取得此處傳回的兩個值之一。如果需要主機和服務名稱,此方法會比以下的包裝函式來得好,因為它只會呼叫 `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` 方法所提供的 socket 位址),類似於 `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`。新的物件會包裝與原始物件相同的底層 socket 檔案控制代碼,因此應注意不要同時繼續使用兩個物件。理想情況下,原始的 `$sock` 應該在呼叫此方法後捨棄。

此方法會檢查 socket 網域是否為 `PF_INET`,如果不是,會擲回例外狀況。

NON-BLOCKING

如果建構函式傳入一個已定義但為 false 的值作為 `Blocking` 參數,則 socket 會進入非封鎖模式。在非封鎖模式下,socket 在建構函式傳回時不會設定,因為底層的 `connect(2)` 系統呼叫函式否則必須封鎖。

非封鎖行為是 `IO::Socket::INET` API 的延伸,這是 `IO::Socket::IP` 獨有的,因為前者不支援多宿非封鎖連線。

在使用非封鎖模式時,呼叫者必須重複檢查檔案處理序的寫入能力(例如使用 selectIO::Poll)。每次檔案處理序準備好寫入時,必須呼叫 connect 方法,且不帶任何引數。請注意,某些作業系統,最著名的是 MSWin32,不會使用寫入準備就緒報告 connect() 失敗;因此,您還必須 select() 以取得例外狀態。

connect 傳回 false 時,$! 的值會指出是否應該再次嘗試(設定為 EINPROGRESS 值,或在 MSWin32 上設定為 EWOULDBLOCK),或是否發生永久錯誤(例如 ECONNREFUSED)。

一旦 socket 已連接到對等端,connect 將傳回 true,且 socket 現在會準備好使用。

請注意,對平台底層 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 在建立新的 socket 檔案處理序時會小心保留實際檔案描述符號碼,因此 pollepoll 等技術應該對其在底層重新配置不同的 socket(可能是為了在 PF_INETPF_INET6 之間切換協定系列)是透明的。

如需使用 IO::PollNet::LibAsyncNS 的另一個範例,請參閱模組發行中的 examples/nonblocking_libasyncns.pl 檔案。

PeerHost AND LocalHost 剖析

為支援 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 )

提供上述解析功能的公用程式方法。傳回一個包含 2 個元素的清單,其中包含拆分的主機名稱和埠號描述(如果可以解析),或給定的地址和 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 <leonerd@leonerd.org.uk>