ZetCode

Perl 套接字

最后修改于 2023 年 8 月 24 日

Perl 套接字教程展示了如何在 Perl 中使用套接字。套接字编程是低级别的。本教程的目的是介绍网络编程,包括这些低级细节。还有一些更高级的 API,在实际场景中可能更实用。

注意: 在网络中,套接字一词有不同的含义。它用于 IP 地址和端口号的组合。

网络协议

TCP/IP 是一套协议,用于设备在 Internet 和大多数本地网络上进行通信。TCP 更可靠,具有广泛的错误检查,并且需要更多资源。HTTP、SMTP 或 FTP 等服务使用 TCP。UDP 可靠性差得多,错误检查有限,并且需要更少的资源。VoIP 等服务使用 UDP。

Perl 套接字模块

Socket 模块提供了网络常量和支持函数。

IO::Socket::INET 提供了创建和使用 AF_INET 域中套接字的面向对象接口;它与 IPv4 地址一起使用。IO::Socket::IP 是一个用于处理 IPv4 和 IPv6 地址的模块。

Perl UDP 套接字示例

UDP 是一种通信协议,通过网络传输独立的数据包,不保证到达,也不保证传递顺序。回显是一项使用 UDP 的服务。

回显协议是 RFC 862 中定义的 Internet 协议套件中的一项服务。回显协议可以使用 TCP 或 UDP,端口号为 7。服务器会发送回它接收到的数据的相同副本。

我们在本地 Debian 系统上设置了一个回显服务。

$ cat /etc/services | grep echo | head -4
echo            7/tcp
echo            7/udp
echo            4/ddp                   # AppleTalk Echo Protocol

端口 7 保留给回显服务。

由于安全原因,在公开可用的计算机上,回显服务在大多数情况下是禁用的。因此,我们在本地网络中创建自己的服务。

我们在本地网络中的另一台计算机上启动回显服务。

# apt install xinetd

我们安装了 xinetd 包。该包包含 xinetd 守护进程,它是一个 TCP 包装的超级服务,用于访问回显、FTP、IMAP 和 telnet 等部分常用网络服务。

...
# This is the udp version.
service echo
{
        disable         = no
        type            = INTERNAL
        id              = echo-dgram
        socket_type     = dgram
        protocol        = udp
        user            = root
        wait            = yes
}

/etc/xinetd.d/echo 文件中,我们将 disable 选项设置为 no。

# systemctl start xinetd

我们启动了该服务。

echo_client.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $addr = 'core9';
my $port = 7;

my $msg = shift || "hello";

my $sock = IO::Socket::INET->new(
    Domain => AF_INET,
    PeerAddr => $addr,
    PeerPort => $port,
    Proto => 'udp',
) or die "failed to create socket: $!\n";

$sock->send("$msg\n", 0);

my $data = <$sock>;
say "$data";

$sock->close();

该示例将消息发送到本地网络计算机上的回显服务。该计算机将消息回显回来。

my $addr = 'core9';
my $port = 7;

我们定义了网络地址和端口。

my $sock = IO::Socket::INET->new(
    Domain => AF_INET,
    PeerAddr => $addr,
    PeerPort => $port,
    Proto => 'udp',
) or die "failed to create socket: $!\n";

我们使用 IO::Socket::INET 创建了一个新的套接字。我们指定了域、地址、端口和协议。

$sock->send("$msg\n", 0);

我们在套接字上发送了一条消息。

my $data = <$sock>;
say "$data";

我们读取并打印了响应。

$sock->close();

我们使用 close 关闭了套接字。

$ ./echo_client.pl cau
cau

Perl 套接字 QOTD

每日名言服务是一个调试和测量工具。每日名言服务只是发送一条简短的消息,而不考虑输入。

$ cat /etc/services | grep qotd
qotd            17/tcp          quote

端口 17 保留给每日名言服务。

qotd.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $sock = IO::Socket::INET->new("djxmmx.net:17")
    or die "failed to create socket: $!\n";

$sock->send('', 0);

while (<$sock>) {
    print $_;
}

print "\n";

$sock->close();

该示例创建了一个连接到 QOTD 服务的客户端程序。

my $sock = IO::Socket::INET->new("djxmmx.net:17")
    or die "failed to create socket: $!\n";

会创建一个 TCP 套接字连接到指定的地址和端口。如果未指定协议,则假定为 TCP。请注意,此类服务是短暂的;它们可能随时被移除。

$sock->send('', 0);

我们向套接字发送了一条空消息。

$ ./qotd.pl 
"Here's the rule for bargains: "Do other men, for they would do you."
    That's the true business precept." Charles Dickens (1812-70)

Perl WHOIS 套接字示例

WHOIS 服务允许我们查找有关域名注册的信息,例如注册日期和域名年龄,或域名所有者联系方式。

在现代 Internet 中,WHOIS 服务通常使用传输控制协议 (TCP) 进行通信。服务器在知名端口号 43 上监听请求。

请注意,whoise 服务通常仅限于一小组域名。

whs.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $domainName = shift || "example.me";
my $host = "whois.nic.me";
my $port = 43;

my $sock = IO::Socket::INET->new(
    PeerHost => $host,
    PeerPort => $port,
    Proto => 'tcp',
) or die "failed to create socket: $!\n";

say 'socket created';

my $size = $sock->send("$domainName\n", 0);
say "Sent data of length: $size";

say '-------------------------';
 
while (<$sock>) {
    print $_;
}

close $sock;

在示例中,我们查询了给定域名的 WHOIS 服务。

$ ./whs.pl tada.me
socket created
Sent data of length: 8
-------------------------
Domain Name: TADA.ME
Registry Domain ID: D425500000006230617-AGRS
Registrar WHOIS Server: whois.godaddy.com
Registrar URL: http://www.godaddy.com
Updated Date: 2021-01-11T19:57:02Z
Creation Date: 2017-09-09T18:21:42Z
Registry Expiry Date: 2021-09-09T18:21:42Z
...

Perl 套接字 HEAD 请求

HEAD 请求是一种 HTTP GET 请求,但不包含消息正文。请求/响应的头部包含元数据,例如 HTTP 协议版本或内容类型。

head_req.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $addr = 'webcode.me:80';
my $req = "HEAD / HTTP/1.0\r\n\r\n";

my $sock = IO::Socket::INET->new(
    Domain => AF_INET,
    PeerAddr => $addr,
) or die "failed to create socket: $!\n";

$sock->send($req, 0);

while (<$sock>) {
    print $_;
}

$sock->close();

在示例中,我们向 webcode.me 发送了一个 HEAD 请求。

$sock->send("HEAD / HTTP/1.0\r\n\r\n", 0);

HEAD 请求使用 HEAD 命令后跟资源 URL 和 HTTP 协议版本发出。请注意,\r\n 字符是通信过程中强制性的部分。详细信息在 RFC 7231 文档中进行了描述。

$ ./head_req.pl 
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Wed, 30 Jun 2021 12:57:05 GMT
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT
Connection: close
ETag: "5d32ffc5-15c"
Accept-Ranges: bytes

Perl 套接字 GET 请求

HTTP GET 方法请求指定资源的表示。使用 GET 的请求应该只检索数据。

get_req.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $addr = 'webcode.me:80';

my $req = "GET / HTTP/1.0\r\n" .
    "Host: webcode.me\r\n" .
    "User-Agent: Perl client\r\n\r\n";

my $sock = IO::Socket::INET->new(
    PeerAddr => $addr,
) or die "failed to create socket: $!\n";

$sock->send($req, 0);

while (<$sock>) {
    print $_;
}

$sock->close();

该示例使用 GET 请求读取 webcode.me 的主页。

my $req = "GET / HTTP/1.0\r\n" .
    "Host: webcode.me\r\n" .
    "User-Agent: Perl client\r\n\r\n";

我们编写了一个简单的 GET 请求。

Perl 套接字发送邮件

要通过套接字发送电子邮件,我们使用 SMTP 命令,例如 HELO、MAIL FROM 和 DATA。

send_mail.pl
#!/usr/bin/perl

use 5.30.0;
use warnings;
use IO::Socket::INET;

my $from = 'john.doe@example.com';
my $to = 'root@core9';
my $name = 'John Doe';
my $subject = 'Hello';
my $body = 'Hello there';
my $addr = 'core9:25';

my $req = "HELO core9\r\n" .
    "MAIL FROM: $from\r\n" .
    "RCPT TO: $to\r\n" .
    "DATA\r\n" .
    "From: $name\r\n" .
    "Subject: $subject \r\n" .
    "$body\r\n.\r\n" . "QUIT\r\n";

my $sock = IO::Socket::INET->new(
    PeerAddr => $addr,
) or die "failed to create socket: $!\n";

$sock->send($req, 0);

while (<$sock>) {
    print $_;
}

$sock->close();

该示例将电子邮件发送到本地网络上托管邮件服务器的计算机。

$ ./send_mail.pl 
220 core9 ESMTP Sendmail 8.15.2/8.15.2; Thu, 1 Jul 2021 14:27:19 +0200 (CEST)
250 core9 Hello spartan.local [192.168.0.20], pleased to meet you
250 2.1.0 john.doe@example.com... Sender ok
250 2.1.5 root@core9... Recipient ok
354 Enter mail, end with "." on a line by itself
250 2.0.0 161CRJJv001451 Message accepted for delivery
221 2.0.0 core9 closing connection

我们发送了电子邮件。

From john.doe@example.com Thu Jul  1 14:27:19 2021
Return-Path: <john.doe@example.com>
Received: from core9 (spartan.local [192.168.0.20])
	by core9 (8.15.2/8.15.2) with SMTP id 161CRJJv001451
	for root@core9; Thu, 1 Jul 2021 14:27:19 +0200 (CEST)
	(envelope-from john.doe@example.com)
Date: Thu, 1 Jul 2021 14:27:19 +0200 (CEST)
Message-Id: <202107011227.161CRJJv001451@core9>
From: John.Doe
Subject: Hello 
To: undisclosed-recipients:;
Status: RO

Hello there

我们在接收端检查了电子邮件。

在本文中,我们学习了如何在 Perl 中使用套接字。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出 所有 Perl 教程