Perl 简明教程

Perl - Socket Programming

What is a Socket?

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

Socket is a Berkeley UNIX mechanism of creating a virtual duplex connection between different processes. This was later ported on to every known OS enabling communication between systems across geographical location running on different OS software. If not for the socket, most of the network communication between systems would never ever have happened.

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

Taking a closer look; a typical computer system on a network receives and sends information as desired by the various applications running on it. This information is routed to the system, since a unique IP address is designated to it. On the system, this information is given to the relevant applications, which listen on different ports. For example an internet browser listens on port 80 for information received from the web server. Also we can write our custom applications which may listen and send/receive information on a specific port number.

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

For now, let’s sum up that a socket is an IP address and a port, enabling connection to send and recieve data over a network.

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

To explain above mentioned socket concept we will take an example of Client - Server Programming using Perl. To complete a client server architecture we would have to go through the following steps −

To Create a Server

  1. Create a socket using socket call.

  2. Bind the socket to a port address using bind call.

  3. Listen to the socket at the port address using listen call.

  4. Accept client connections using accept call.

To Create a Client

  1. Create a socket with socket call.

  2. Connect (the socket) to the server using connect call.

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

Following diagram shows the complete sequence of the calls used by Client and Server to communicate with each other −

perl socket

Server Side Socket Calls

The socket() call

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

The socket() call is the first call in establishing a network connection is creating a socket. This call has the following syntax −

socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

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

The above call creates a SOCKET and other three arguments are integers which should have the following values for TCP/IP connections.

  1. DOMAIN should be PF_INET. It’s probable 2 on your computer.

  2. TYPE should be SOCK_STREAM for TCP/IP connection.

  3. PROTOCOL should be (getprotobyname('tcp'))[2]. It is the particular protocol such as TCP to be spoken over the socket.

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

So socket function call issued by the server will be something like this −

use Socket     # This defines PF_INET and SOCK_STREAM

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

The bind() call

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

The sockets created by socket() call are useless until they are bound to a hostname and a port number. Server uses the following bind() function to specify the port at which they will be accepting connections from the clients.

bind( SOCKET, ADDRESS );

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

Here SOCKET is the descriptor returned by socket() call and ADDRESS is a socket address ( for TCP/IP ) containing three elements −

  1. The address family (For TCP/IP, that’s AF_INET, probably 2 on your system).

  2. The port number (for example 21).

  3. The internet address of the computer (for example 10.12.12.168).

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

As the bind() is used by a server, which does not need to know its own address so the argument list looks like this −

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 or die clause is very important because if a server dies without outstanding connections, the port won’t be immediately reusable unless you use the option SO_REUSEADDR using setsockopt() function. Here pack_sockaddr_in() function is being used to pack the Port and IP address into binary format.

The listen() call

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

If this is a server program, then it is required to issue a call to listen() on the specified port to listen, i.e., wait for the incoming requests. This call has the following syntax −

listen( SOCKET, QUEUESIZE );

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

The above call uses SOCKET descriptor returned by socket() call and QUEUESIZE is the maximum number of outstanding connection request allowed simultaneously.

The accept() call

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

If this is a server program then it is required to issue a call to the access() function to accept the incoming connections. This call has the following syntax −

accept( NEW_SOCKET, SOCKET );

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

The accept call receive SOCKET descriptor returned by socket() function and upon successful completion, a new socket descriptor NEW_SOCKET is returned for all future communication between the client and the server. If access() call fails, then it returns FLASE which is defined in Socket module which we have used initially.

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

Generally, accept() is used in an infinite loop. As soon as one connection arrives the server either creates a child process to deal with it or serves it himself and then goes back to listen for more connections.

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

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

Now all the calls related to server are over and let us see a call which will be required by the client.

Client Side Socket Calls

The connect() call

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

If you are going to prepare client program, then first you will use socket() call to create a socket and then you would have to use connect() call to connect to the server. You already have seen socket() call syntax and it will remain similar to server socket() call, but here is the syntax for connect() call −

connect( SOCKET, ADDRESS );

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

Here SCOKET is the socket descriptor returned by socket() call issued by the client and ADDRESS is a socket address similar to bind call, except that it contains the IP address of the remote server.

$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 描述符开始向服务器发送您的命令,否则您的客户端会通过给出一个错误消息退出。

If you connect to the server successfully, then you can start sending your commands to the server using SOCKET descriptor, otherwise your client will come out by giving an error message.

Client - Server Example

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

Following is a Perl code to implement a simple client-server program using Perl socket. Here server listens for incoming requests and once connection is established, it simply replies Smile from the server. The client reads that message and print on the screen. Let’s see how it has been done, assuming we have our server and client on the same machine.

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 提示符下发出以下命令:

To run the server in background mode issue the following command on Unix prompt −

$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: $!";

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

Now let’s start our client at the command prompt, which will connect to the server and read message sent by the server and displays the same on the screen as follows −

$perl client.pl
Smile from the server

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

NOTE − If you are giving the actual IP address in dot notation, then it is recommended to provide IP address in the same format in both client as well as server to avoid any confusion.