Perl 和套接字

作者:Mike Mull

Perl 非常适合编写原型或成熟的应用程序,因为它非常完整。该语言几乎不需要扩展即可完成您期望在 Linux 系统上使用 C 或 C++ 完成的各种任务。一个值得注意的例子是 Berkeley 套接字函数,Perl 甚至在互联网还只是一项很酷的技术,而不是全球文化现象的时候就包含了这些函数。

套接字是一种通用的进程间通信 (IPC) 机制。当进程在不同的机器上运行时,它们可以利用套接字使用互联网协议进行通信。这是大多数互联网客户端和服务器的基础。许多互联网协议都基于交换简单的文本;许多有趣的内容也是如此。由于 Perl 擅长处理文本,因此它非常适合编写诸如 Web 服务器或任何类型的客户端等应用程序,这些应用程序解析或搜索文本。在本文中,我们将开发一个非常简单的客户端,它在指定的网站上搜索正则表达式——你可以说它是一个不太智能的代理。

我假设读者事先不了解套接字,但是如果您使用过 C 语言中的套接字函数,那么它们在 Perl 中看起来会非常熟悉。基本函数包括 socket、connect、bind、listenaccept。Perl 也有诸如 gethostbynamegetprotobyname 之类的函数版本,这使得套接字通信更加容易。当然,这些 Perl 函数最终会调用 C 版本,因此参数列表非常相似。唯一的区别在于 Perl 文件句柄与 C 文件描述符(只是整数)不同,并且 Perl 版本不需要用于字符串或结构的额外冗长参数。

稍后我们将讨论互联网客户端所需的套接字函数的详细信息,但让我们首先简要了解一下互联网通信的正常操作顺序。服务器首先使用 socket 函数建立一个套接字,该函数返回一个类似于文件描述符的套接字描述符。服务器接下来使用 bind 将地址分配给套接字,然后告诉系统它愿意接受来自 listen 函数的连接。accept 函数可以阻塞,直到客户端连接到服务器。客户端程序也调用 socket 并获取一个套接字描述符。客户端使用 connect 函数连接到服务器的 bind 调用指定的地址。如果一切顺利,客户端可以像处理文件描述符一样读取和写入套接字描述符。请参阅清单 2,了解如何在典型程序中使用 socketconnect 函数。

如上所述,客户端程序必须首先调用 socket 以获取套接字描述符,或者在 Perl 的情况下,获取文件句柄。此函数指定要使用的特定通信协议,并为通信设置一个端点——即,一个插入连接的地方——一个“套接字”,暂且这么称呼。此函数的语法为

socket SOCKET, DOMAIN, TYPE, PROTOCOL

SOCKET 是文件句柄。DOMAINTYPE 是指定地址域(或族)和套接字类型的整数。在 Perl 4 中,您必须显式设置这些数字,但 Perl 5 在 Socket 模块中定义了它们。要访问 Socket 模块,请将以下行添加到程序的顶部

use Socket;
对于互联网应用程序,将 DOMAIN 设置为 AF_INET(通常为 2),将 TYPE 设置为 SOCK_STREAM(通常为 1)。这基本上意味着服务器的地址将具有熟悉的互联网形式(例如,192.42.55.55),并且您将像任何 I/O 流一样从套接字读取和写入。对于大多数应用程序,您可以将 PROTOCOL 参数设置为 0,但使用 getprotobyname 函数很容易获得正确的值。

接下来,您需要使用 connect 函数连接到服务器。如果您没有最新版本的 Socket 模块,这在 Perl 中可能会变得有点棘手,主要是因为很难指定服务器的地址。connect 函数的语法为

connect SOCKET, NAME

SOCKET 是由 socket 函数创建的文件句柄,这很容易。但是,NAME 参数被描述为“套接字适当类型的打包网络地址”,如果您还不熟悉套接字,这可能会让您摸不着头脑。对于互联网应用程序,C 版本的 connect 函数的正确网络地址类型由清单 1(来自 <netinet/in.h><linux/in.h>)中的结构体之类的结构体给出。

清单 1

稍加仔细观察,您可以看到您需要将三条信息打包成一个 16 字节长的二进制结构。首先您需要地址族,它是 AF_INET,与 socketDOMAIN 参数相同。第二部分是服务器套接字的端口号。大多数常见服务器都有所谓的“知名”端口号(对于 HTTP 服务器,这是 80),但应用程序应该有一种指示备用端口号的方法。最后,您需要知道服务器的互联网地址。从上面的结构体中,您可以看出这是一个 32 位的值。幸运的是,如果您知道服务器的互联网名称(例如,www.linux.com),您可以使用 gethostbyname 函数获取地址。一旦您组装好这些信息,您可以使用 Perl pack 函数创建 NAME 参数。代码可能如下所示

$sockaddr_in = 'S n a4 x8';
$in_addr = (gethostbyname("www.linux.com"))[4];
$server_addr = pack( $sockaddr_in, AF_INET, 80, $in_addr );

最新版本的 Perl(5.002 及更高版本)通过 Socket 模块中的 sockaddr_in 函数大大简化了整个过程。此函数接受端口号和服务器的互联网地址,并返回适当的打包结构。我在清单 2 的迷你客户端中使用了此技术。如果您需要可移植性,或者只是想要可读性,我强烈建议使用 Perl 5.002 或更高版本。

清单 2

所以我们终于设置好了套接字并建立了与服务器的连接。现在事情变得相当容易了,因为我们可以像处理任何其他文件句柄一样处理套接字。唯一的麻烦是我们想确保写入套接字的任何内容都不是缓冲的,因为它需要在我们读取服务器的响应之前到达服务器。为此,我们使用 Perl select 函数,该函数设置用于标准输出的文件句柄。请注意,在清单 2 中,选择了套接字文件句柄;然后将特殊变量 $| 设置为 1,以强制在每次写入后刷新缓冲区;然后重新选择 STDOUT

现在我们的客户端可以向服务器发送请求。此应用程序只向 HTTP 服务器发送一个 GET 命令,以便它返回 URL 指定的页面。一旦命令发送,我们就会逐行读取到达套接字的任何内容,并查找我们指定的模式。您可以使用从服务器返回的 HTML 执行任何您想做的事情,甚至解析它或查找要关注的其他超文本链接。

毫无疑问,我们还没有涵盖套接字的许多方面。特别是,我没有讨论编写服务器(主要是为了使本文保持在可管理的长度内)。如果您想了解更多关于在 Perl 中编写互联网服务器的信息,我建议阅读 Wall、Christiansen 和 Schwarz 合著的 Programming Perl(通常称为“骆驼书”)。Perl 还包含几个我没有提及的套接字函数,包括 sendrecv,它们可以像 write 和 read 调用一样使用,以及 sendtorecvfrom,它们用于所谓的“无连接”通信。同样,有关这些函数的详细信息以及一般的网络通信,请参阅骆驼书,我推荐 W. Richard Stevens 的 Unix Network Programming。另外,不要忘记许多 Perl 互联网应用程序已经存在于互联网上,因此请参考这些应用程序的示例。我特别推荐 tinyhttpd,一个非常紧凑的 HTTP 服务器,作为学习如何构建服务器的一个好方法(参见 http://www.inka.de/~bigred/sw/tinyhttpd.html)。

Mike Mull 编写软件来模拟亚微观物体。更奇怪的是,人们付钱让他做这件事。Mike 认为 Linux 很棒。他最喜欢的编程项目是他 2 岁的儿子 Nathan。可以通过 mwm@cts.com 联系到 Mike。

加载 Disqus 评论