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_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
物件,其中包含一個根據傳遞的名稱引數建立的新 socket 句柄。識別的引數為
對等方的主機名稱和服務名稱以 connect()
連線。服務名稱可以數字字串形式提供為埠號。
為了與存取器方法對稱,並與 IO::Socket::INET
相容,這些方法分別被接受為 PeerHost
和 PeerService
的同義詞。
指定對等方以 connect()
連線的替代形式。這應為由 Socket::getaddrinfo
傳回的陣列形式。
此參數優先於 Peer*
、Family
、Type
和 Proto
引數。
要繫結到 bind()
的本機地址的主機名稱和服務名稱。
為了與存取器方法對稱,且與 IO::Socket::INET
相容,這些方法分別被接受為 LocalHost
和 LocalService
的同義詞。
指定要繫結到的本機地址的替代形式。這應該是 Socket::getaddrinfo
傳回的陣列形式。
這個參數優先於 Local*
、Family
、Type
和 Proto
參數。
傳遞到 getaddrinfo
的地址系列 (例如 AF_INET
、AF_INET6
)。通常這會保持未定義,而 getaddrinfo
會使用系統支援的任何地址系列進行搜尋。
傳遞到 getaddrinfo
的 Socket 類型 (例如 SOCK_STREAM
、SOCK_DGRAM
)。通常由呼叫者定義;如果保持未定義,getaddrinfo
會嘗試從服務名稱推斷類型。
要使用的 Socket 的 IP 協定 (例如 'tcp'
、IPPROTO_TCP
、'udp'
、IPPROTO_UDP
)。通常這會保持未定義,而 getaddrinfo
或核心會選擇適當的值。可以字串名稱或數字形式提供。
傳遞到 getaddrinfo()
函式的更多旗標。如果未提供,將使用預設值 AI_ADDRCONFIG
。
如果提供了 Listen
參數,這些旗標會與 AI_PASSIVE
結合。如需更多資訊,請參閱 Socket 模組中關於 getaddrinfo()
的文件。
如果已定義,將套接字置於監聽模式,可以使用 accept
方法接受新連線。所給的值用作 listen(2)
佇列大小。
如果為 true,設定 SO_REUSEADDR
sockopt
如果為 true,設定 SO_REUSEPORT
sockopt(並非所有作業系統都實作此 sockopt)
如果為 true,設定 SO_BROADCAST
sockopt
在上述三個選項之後套用的其他套接字選項的選用陣列。該值是一個包含 2 或 3 個元素的 ARRAYref 的 ARRAY。每個內部陣列都與單一選項相關,提供層級和選項名稱,以及一個選用值。如果值元素遺失,它將被賦予平台大小整數 1 常數的值(即適合啟用大多數常見布林選項)。
例如,下面給出的兩個選項都等於設定 ReuseAddr
。
Sockopts => [
[ SOL_SOCKET, SO_REUSEADDR ],
[ SOL_SOCKET, SO_REUSEADDR, pack( "i", 1 ) ],
]
如果已定義,在建立 PF_INET6
套接字時將 IPV6_V6ONLY
sockopt 設定為給定值。如果為 true,監聽模式套接字將只會在 AF_INET6
位址上監聽;如果為 false,它也會接受來自 AF_INET
位址的連線。
如果未定義,套接字選項將不會被變更,且會套用作業系統設定的預設值。為了在各平台上具有可重複的行為,建議對於監聽模式套接字始終定義此值。
請注意,並非所有平台都支援停用此選項。有些平台,至少 OpenBSD 和 MirBSD,如果嘗試停用它,將會失敗並顯示 EINVAL
。若要判斷是否可以停用,可以使用類別方法
if( IO::Socket::IP->CAN_DISABLE_V6ONLY ) {
...
}
else {
...
}
如果您的平台不支援停用此選項,但您仍想要監聽 AF_INET
和 AF_INET6
連線,您必須建立兩個監聽套接字,一個繫結到每個通訊協定。
此 IO::Socket::INET
風格的引數會被忽略,除非它已定義但為 false。請參閱下列 IO::Socket::INET
不相容性區段。
然而,它啟用的行為始終會由 IO::Socket::IP
執行。
如果已定義但為 false,則套接字將設定為非封鎖模式。否則,它將預設為封鎖模式。有關更多詳細資訊,請參閱下方的非封鎖區段。
如果已定義,則在封鎖模式下,提供每個 connect()
呼叫封鎖的最大時間(以秒為單位)。如果遺失,則不會套用逾時,除了基礎作業系統提供的逾時。在非封鎖模式下,此參數將被忽略。
請注意,如果主機名稱解析為多個位址候選,則相同的逾時將個別套用於每個連線嘗試,而不是整個作業。進一步注意,逾時不適用於初始主機名稱解析作業(如果透過主機名稱連線)。
此行為是從 IO::Socket::INET
複製而來的靈感;若要更精細地控制連線逾時,請考慮直接執行非封鎖連線。
如果未提供 Type
或 Proto
提示,則分別設定 SOCK_STREAM
和 IPPROTO_TCP
預設值,以維持與 IO::Socket::INET
的相容性。未識別的其他命名參數將被忽略。
如果未傳遞 Family
或任何主機或位址,也未傳遞任何 *AddrInfo
,則建構函式沒有資訊來決定要建立哪個套接字系列。在這種情況下,它會執行 getaddinfo
呼叫,其中包含 AI_ADDRCONFIG
旗標、沒有主機名稱,以及服務名稱 "0"
,並使用第一個傳回結果的系列。
如果建構函式失敗,它會將 $@
設定為適當的錯誤訊息;這可能是來自 $!
或其他字串;並非每個失敗都一定有相關的 errno
值。
$sock = IO::Socket::IP->new( $peeraddr )
作為特殊情況,如果建構函式傳遞一個單一參數(相對於偶數大小的鍵值對清單),則視為 PeerAddr
參數的值。它會以相同的方式進行剖析,根據下方 PeerHost
和 LocalHost
剖析區段中提供的行為。
除了下列方法外,此類別還會繼承 IO::Socket 和 IO::Handle 中的所有方法。
( $host, $service ) = $sock->sockhost_service( $numeric )
傳回本機位址的主機名稱和服務名稱(也就是 `sockname` 方法所提供的 socket 位址)。
如果 `$numeric` 為 true,這些值會以數字形式提供,而不是解析成名稱。
以下四個方便的包裝函式可用於取得此處傳回的兩個值之一。如果需要主機和服務名稱,此方法會比以下的包裝函式來得好,因為它只會呼叫 `getnameinfo(3)` 一次。
$addr = $sock->sockhost
傳回本機位址的數字形式,以文字表示
$port = $sock->sockport
傳回本機埠號的數字形式
$host = $sock->sockhostname
傳回本機位址的解析名稱
$service = $sock->sockservice
傳回本機埠號的解析名稱
$addr = $sock->sockaddr
傳回本機位址,以二進位八進位字串表示
( $host, $service ) = $sock->peerhost_service( $numeric )
傳回對端位址的主機名稱和服務名稱(也就是 `peername` 方法所提供的 socket 位址),類似於 `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`。新的物件會包裝與原始物件相同的底層 socket 檔案控制代碼,因此應注意不要同時繼續使用兩個物件。理想情況下,原始的 `$sock` 應該在呼叫此方法後捨棄。
此方法會檢查 socket 網域是否為 `PF_INET`,如果不是,會擲回例外狀況。
如果建構函式傳入一個已定義但為 false 的值作為 `Blocking` 參數,則 socket 會進入非封鎖模式。在非封鎖模式下,socket 在建構函式傳回時不會設定,因為底層的 `connect(2)` 系統呼叫函式否則必須封鎖。
非封鎖行為是 `IO::Socket::INET` API 的延伸,這是 `IO::Socket::IP` 獨有的,因為前者不支援多宿非封鎖連線。
在使用非封鎖模式時,呼叫者必須重複檢查檔案處理序的寫入能力(例如使用 select
或 IO::Poll
)。每次檔案處理序準備好寫入時,必須呼叫 connect
方法,且不帶任何引數。請注意,某些作業系統,最著名的是 MSWin32
,不會使用寫入準備就緒報告 connect()
失敗;因此,您還必須 select()
以取得例外狀態。
當 connect
傳回 false 時,$!
的值會指出是否應該再次嘗試(設定為 EINPROGRESS
值,或在 MSWin32 上設定為 EWOULDBLOCK
),或是否發生永久錯誤(例如 ECONNREFUSED
)。
一旦 socket 已連接到對等端,connect
將傳回 true,且 socket 現在會準備好使用。
請注意,對平台底層 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
在建立新的 socket 檔案處理序時會小心保留實際檔案描述符號碼,因此 poll
或 epoll
等技術應該對其在底層重新配置不同的 socket(可能是為了在 PF_INET
和 PF_INET6
之間切換協定系列)是透明的。
如需使用 IO::Poll
和 Net::LibAsyncNS
的另一個範例,請參閱模組發行中的 examples/nonblocking_libasyncns.pl 檔案。
PeerHost
AND 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 )
提供上述解析功能的公用程式方法。傳回一個包含 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 )
$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)
呼叫結果中搜尋可用的地址。建構函式將會忽略此參數的值,除非它有定義但為 false。在此情況下會擲回例外,因為這會要求它首先停用 getaddrinfo(3)
搜尋行為。
IO::Socket::IP
實作 Blocking
和 Timeout
參數,但它以不同的方式實作兩者的互動。
在 ::INET
中,提供逾時會覆寫非封鎖行為,這表示 connect()
作業仍然會封鎖,儘管呼叫者要求非封鎖 socket。這在其文件中有明確說明,而且作者也不認為這是一個有用的行為 - 這似乎來自實作的怪癖。
在 ::IP
中,因此 Blocking
參數優先 - 如果請求非封鎖 socket,則不會封鎖任何操作。此處的 Timeout
參數僅定義封鎖 connect()
呼叫將等待的最長時間(如果它封鎖的話)。
為了明確取得使用 ::IP
時,指定此選項組合至 ::INET
的「封鎖連線然後非封鎖傳送和接收」行為,請先執行封鎖連線,然後再將 socket 轉換為非封鎖模式。
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 <leonerd@leonerd.org.uk>