Perl - 套接字编程


什么是套接字?

Socket 是一种 Berkeley UNIX 机制,用于在不同进程之间创建虚拟双工连接。后来它被移植到每个已知的操作系统上,从而实现运行在不同操作系统软件上的跨地理位置的系统之间的通信。如果没有套接字,系统之间的大多数网络通信将永远不会发生。

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

现在,我们总结一下,套接字是一个 IP 地址和一个端口,使连接能够通过网络发送和接收数据。

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

创建服务器

  • 使用套接字调用创建套接字。

  • 使用绑定调用将套接字绑定到端口地址。

  • 使用listen调用侦听端口地址处的套接字。

  • 使用accept调用接受客户端连接。

创建客户端

  • 使用套接字调用创建套接字。

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

下图显示了客户端和服务器用于相互通信的调用的完整顺序 -

Perl 套接字

服务器端套接字调用

socket() 调用

socket ()调用是建立网络连接的第一个调用,即创建套接字。该调用具有以下语法 -

socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

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

  • DOMAIN应为 PF_INET。您的计算机上可能是 2。

  • 对于 TCP/IP 连接, TYPE应为 SOCK_STREAM。

  • 协议应该是(getprotobyname('tcp'))[2]。它是通过套接字使用的特定协议,例如 TCP。

因此服务器发出的套接字函数调用将是这样的 -

use Socket     # This defines PF_INET and SOCK_STREAM

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

调用bind()

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

bind( SOCKET, ADDRESS );

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

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

  • 端口号(例如 21)。

  • 计算机的互联网地址(例如 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 地址打包为二进制格式。

调用listen()

如果这是一个服务器程序,那么需要在指定端口上调用listen()来监听,即等待传入的请求。该调用具有以下语法 -

listen( SOCKET, QUEUESIZE );

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

调用accept()

如果这是一个服务器程序,则需要调用access()函数来接受传入的连接。该调用具有以下语法 -

accept( NEW_SOCKET, SOCKET );

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

一般来说,accept()是在无限循环中使用的。一旦一个连接到达,服务器要么创建一个子进程来处理它,要么自己提供服务,然后返回监听更多连接。

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

现在所有与服务器相关的调用都结束了,让我们看看客户端需要的调用。

客户端套接字调用

connect() 调用

如果您要准备客户端程序,那么首先您将使用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 描述符向服务器发送命令,否则您的客户端将给出错误消息。

客户端-服务器示例

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

创建服务器的脚本

#!/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&

创建客户端的脚本

!/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

注意- 如果您以点表示法给出实际 IP 地址,则建议在客户端和服务器中提供相同格式的 IP 地址,以避免任何混淆。