Perl - Socket 套接字编程

什么是套接字?

Socket 是一种在不同进程之间创建虚拟双工连接的 Berkeley UNIX 机制。 这后来被移植到每个已知的操作系统上,使跨地理位置的系统之间在不同操作系统软件上运行的通信成为可能。 如果没有套接字,系统之间的大部分网络通信将永远不会发生。

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

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

为了解释上面提到的套接字概念,我们将举一个使用 Perl 进行客户端-服务器编程的例子。 要完成客户端服务器架构,我们必须完成以下步骤 −


创建服务器

  • 使用 socket 调用创建一个套接字。

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

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

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


创建客户端

  • 使用 socket 调用创建一个套接字。

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

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

Perl Socket

服务器端套接字调用

socket() 调用

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

socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

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

  • DOMAIN 应该是 PF_INET。 您的计算机上可能有 2 个。

  • TYPE 应该是 TCP/IP 连接的 SOCK_STREAM。

  • PROTOCOL 应该是 (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)。

  • 计算机的 Internet 地址(例如 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 子句非常重要,因为如果服务器在没有未完成连接的情况下死机,则端口不会立即可重用,除非您使用选项 SO_REUSEADDR 使用 setsockopt() 函数。 这里使用 pack_sockaddr_in() 函数将端口和 IP 地址打包成二进制格式。

listen() 调用

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

listen( SOCKET, QUEUESIZE );

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

accept() 调用

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

accept( NEW_SOCKET, SOCKET );

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

一般情况下,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 from the 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

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