Perl 简明教程

Perl - Socket Programming

What is a Socket?

套接字是伯克利 UNIX 用于在不同进程之间创建虚拟双工连接的机制。此功能稍后移植到所有已知的操作系统,从而支持不同操作系统软件在在不同地理位置上的系统之间进行通信。如果没有套接字,绝大多数系统之间的网络通信都永远无法实现。

仔细观察可以看到:在网络上的典型计算机系统根据其上运行的各种应用程序的需求接收和发送信息。此信息被路由到该系统,因为它有一个指定的唯一 IP 地址。在系统中,此信息被提供给监听不同端口的相关应用程序。例如,某个互联网浏览器监听端口 80 以接收从网络服务器接收的信息。我们还可以编写自己的自定义应用程序,这些应用程序可以监听特定端口号并发送/接收信息。

目前,我们总结一下,套接字是一个 IP 地址和一个端口,用于在网络中启用连接以发送和接收数据。

为了解释上述套接字概念,我们将使用 Perl 展示一个客户端 - 服务器编程示例。要完成一个客户端 - 服务器架构,我们必须执行以下步骤:

To Create a Server

  1. 使用 socket 的调用创建套接字。

  2. 使用 bind 的调用将套接字绑定到端口地址。

  3. 使用 listen 的调用监听端口地址处的套接字。

  4. 使用 accept 的调用接受客户端连接。

To Create a Client

  1. 使用 socket 的调用创建套接字。

  2. 使用 connect 调用将 (套接字) 连接到服务器。

下图显示了客户端和服务器用于彼此通信的调用的完整序列−

perl socket

Server Side Socket Calls

The socket() call

socket() 调用是建立网络连接的第一个调用,即创建套接字。此调用的语法如下:

socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

上述调用创建一个 SOCKET,其他三个参数是整数,对于 TCP/IP 连接应具有以下值。

  1. DOMAIN 应为 PF_INET。在您的计算机上可能是 2。

  2. TYPE 对于 TCP/IP 连接应为 SOCK_STREAM。

  3. PROTOCOL 应为 (getprotobyname('tcp'))[2] 。它是通过套接字传输的特定的协议,例如 TCP。

因此,服务器发出的 socket 函数调用将类似于以下内容:

use Socket     # This defines PF_INET and SOCK_STREAM

socket(SOCKET,PF_INET,SOCK_STREAM,(getprotobyname('tcp'))[2]);

The bind() call

由 socket() 调用创建的套接字在绑定到主机名和端口号之前是无用的。服务器使用以下 bind() 函数指定它将接受来自客户端的连接的端口。

bind( SOCKET, ADDRESS );

此处,SOCKET 是由 socket() 调用返回的描述符,ADDRESS 是包含三个元素的套接字地址 (对于 TCP/IP):

  1. 地址族(对于 TCP/IP,即 AF_INET,在您的系统上可能是 2)。

  2. 端口号(例如 21)。

  3. 计算机的互联网地址(例如 10.12.12.168)。

由于 bind() 由服务器使用,服务器不需要知道其自己的地址,因此参数列表如下:

use Socket        # This defines PF_INET and SOCK_STREAM

$port = 12345;    # The unique port used by the sever to listen requests
$server_ip_address = "10.12.12.168";
bind( SOCKET, pack_sockaddr_in($port, inet_aton($server_ip_address)))
   or die "Can't bind to port $port! \n";

or die 子句非常重要,因为如果服务器在没有未完成连接的情况下死去,则除非使用 setsockopt() 函数中的 SO_REUSEADDR 选项,否则端口将无法立即重复使用。此处 pack_sockaddr_in() 函数用于将端口和 IP 地址打包为二进制格式。

The listen() call

如果这是服务器程序,则需要在指定端口上对 listen() 发出调用进行侦听,即等待传入请求。此调用的语法如下:

listen( SOCKET, QUEUESIZE );

上述调用使用由 socket() 调用返回的 SOCKET 描述符,QUEUESIZE 是同时允许的最大未完成连接请求数。

The accept() call

如果这是服务器程序,则需要对 access() 函数发出调用以接受传入连接。此调用的语法如下:

accept( NEW_SOCKET, SOCKET );

accept 调用接收由 socket() 函数返回的套接字描述符,并在成功完成时,为客户端和服务器之间的所有未来通信返回一个用于后续通讯的新套接字描述符 NEW_SOCKET。如果 access() 调用失败,它将返回在最初使用的 Socket 模块中定义的 False。

通常,accept() 用于一个无限循环。一旦一个连接到达,服务器会创建一个子进程来处理它,或者自己处理它,然后返回监听更多的连接。

while(1) {
   accept( NEW_SOCKET, SOCKT );
   .......
}

现在所有与服务器相关的呼叫都已完成,让我们看看客户端需要的呼叫。

Client Side Socket Calls

The connect() call

如果您要准备客户端程序,那么首先您将使用 socket() 调用来创建一个套接字,然后您必须使用 connect() 调用来连接到服务器。您已经看到了 socket() 调用语法,它将与服务器 socket() 调用类似,但以下是 connect() 调用的语法−

connect( SOCKET, ADDRESS );

这里,SCOKET 是客户端发起的 socket() 调用返回的套接字描述符,ADDRESS 是一个套接字地址,类似于 bind 调用,不同之处在于它包含远程服务器的 IP 地址。

$port = 21;    # For example, the ftp port
$server_ip_address = "10.12.12.168";
connect( SOCKET, pack_sockaddr_in($port, inet_aton($server_ip_address)))
   or die "Can't connect to port $port! \n";

如果您成功连接到服务器,那么您可以使用 SOCKET 描述符开始向服务器发送您的命令,否则您的客户端会通过给出一个错误消息退出。

Client - Server Example

以下是使用 Perl 套接字实现一个简单的客户端-服务器程序的 Perl 代码。这里服务器监听传入的请求,一旦连接建立,它仅仅从服务器回复微笑。客户端读取消息并在屏幕上打印出来。我们假设我们的服务器和客户端在同一台机器上,让我们看看它是如何完成的。

Script to Create a Server

#!/usr/bin/perl -w
# Filename : server.pl

use strict;
use Socket;

# use port 7890 as default
my $port = shift || 7890;
my $proto = getprotobyname('tcp');
my $server = "localhost";  # Host IP running the server

# create a socket, make it reusable
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
   or die "Can't open socket $!\n";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, 1)
   or die "Can't set socket option to SO_REUSEADDR $!\n";

# bind to a port, then listen
bind( SOCKET, pack_sockaddr_in($port, inet_aton($server)))
   or die "Can't bind to port $port! \n";

listen(SOCKET, 5) or die "listen: $!";
print "SERVER started on port $port\n";

# accepting a connection
my $client_addr;
while ($client_addr = accept(NEW_SOCKET, SOCKET)) {
   # send them a message, close connection
   my $name = gethostbyaddr($client_addr, AF_INET );
   print NEW_SOCKET "Smile from the server";
   print "Connection recieved from $name\n";
   close NEW_SOCKET;
}

要在后台模式下运行服务器,在 Unix 提示符下发出以下命令:

$perl sever.pl&

Script to Create a Client

!/usr/bin/perl -w
# Filename : client.pl

use strict;
use Socket;

# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 7890;
my $server = "localhost";  # Host IP running the server

# create the socket, connect to the port
socket(SOCKET,PF_INET,SOCK_STREAM,(getprotobyname('tcp'))[2])
   or die "Can't create a socket $!\n";
connect( SOCKET, pack_sockaddr_in($port, inet_aton($server)))
   or die "Can't connect to port $port! \n";

my $line;
while ($line = <SOCKET>) {
   print "$line\n";
}
close SOCKET or die "close: $!";

现在让我们在命令提示符下启动我们的客户端,它会连接到服务器并读出服务器发送的消息,并在屏幕上显示,如下所示:

$perl client.pl
Smile from the server

NOTE − 如果您提供的是点分符号表示的实际 IP 地址,那么建议在客户端和服务器中以相同的格式提供 IP 地址,以免产生混淆。