Perl Socket 編程

Socket又稱"套接字",應(yīng)用程序通常通過(guò)"套接字"向網(wǎng)絡(luò)發(fā)出請(qǐng)求或者應(yīng)答網(wǎng)絡(luò)請(qǐng)求,使主機(jī)間或者一臺(tái)計(jì)算機(jī)上的進(jìn)程間可以通訊。

本章節(jié)我們?yōu)榇蠹医邮?Perl 語(yǔ)言中如何使用 Socket 服務(wù)。


創(chuàng)建服務(wù)端

  • 使用 socket 函數(shù)來(lái)創(chuàng)建 socket服務(wù)。

  • 使用 bind 函數(shù)綁定端口。

  • 使用 listen 函數(shù)監(jiān)聽(tīng)端口。

  • 使用 accept 函數(shù)接收客戶端請(qǐng)求。

創(chuàng)建客戶端

  • 使用 socket 函數(shù)來(lái)創(chuàng)建 socket 服務(wù)。

  • 使用 connect 函數(shù)連接到 socket 服務(wù)端。

以下圖表演示了客戶端與服務(wù)端之間的通信流程:


服務(wù)端 socket 函數(shù)

socket 函數(shù)

Perl 中,我們用 socket()函數(shù)來(lái)創(chuàng)建套接字,語(yǔ)法格式如下:

socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

參數(shù)解析:

  • DOMAIN 創(chuàng)建的套接字指定協(xié)議集。 例如:

    • AF_INET 表示IPv4網(wǎng)絡(luò)協(xié)議
    • AF_INET6 表示IPv6
    • AF_UNIX 表示本地套接字(使用一個(gè)文件)

  • TYPE 套接字類型可以根據(jù)是面向連接的還是非連接分為SOCK_STREAM或SOCK_DGRAM

  • PROTOCOL 應(yīng)該是 (getprotobyname('tcp'))[2]。指定實(shí)際使用的傳輸協(xié)議。

所以 socket 函數(shù)調(diào)用方式如下:

use Socket     # 定義了 PF_INET 和 SOCK_STREAM

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

bind() 函數(shù)

使用 bind() 為套接字分配一個(gè)地址:

bind( SOCKET, ADDRESS );

SOCKET 一個(gè)socket的描述符。 ADDRESS 是 socket 地址 ( TCP/IP ) 包含了三個(gè)元素:

  • 地址簇 (TCP/IP, 是 AF_INET, 在你系統(tǒng)上可能是 2)

  • 端口號(hào) (例如 21)

  • 網(wǎng)絡(luò)地址 (例如 10.12.12.168)

使用socket()創(chuàng)建套接字后,只賦予其所使用的協(xié)議,并未分配地址。在接受其它主機(jī)的連接前,必須先調(diào)用bind()為套接字分配一個(gè)地址。

簡(jiǎn)單實(shí)例如下:

use Socket        # 定義了 PF_INET 和 SOCK_STREAM

$port = 12345;    # 監(jiān)聽(tīng)的端口
$server_ip_address = "10.12.12.168";
bind( SOCKET, pack_sockaddr_in($port, inet_aton($server_ip_address)))
   or die "無(wú)法綁定端口! \n";

or die 在綁定地址失敗后執(zhí)行。

通過(guò)設(shè)置 setsockopt() 可選項(xiàng) SO_REUSEADDR 設(shè)置端口可立即重復(fù)使用。

pack_sockaddr_in() 函數(shù)將地址轉(zhuǎn)換為二進(jìn)制格式。

listen() 函數(shù)

當(dāng)socket和一個(gè)地址綁定之后,listen()函數(shù)會(huì)開(kāi)始監(jiān)聽(tīng)可能的連接請(qǐng)求。然而,這只能在有可靠數(shù)據(jù)流保證的時(shí)候使用:

listen( SOCKET, QUEUESIZE );

SOCKET : 一個(gè)socket的描述符。

QUEUESIZE : 是 一個(gè)決定監(jiān)聽(tīng)隊(duì)列大小的整數(shù),當(dāng)有一個(gè)連接請(qǐng)求到來(lái),就會(huì)進(jìn)入此監(jiān)聽(tīng)隊(duì)列;當(dāng)一個(gè)連接請(qǐng)求被accept()接受,則從監(jiān)聽(tīng)隊(duì)列中移出;當(dāng)隊(duì)列滿后,新的連接請(qǐng)求會(huì)返回錯(cuò)誤。

一旦連接被接受,返回0表示成功,錯(cuò)誤返回-1。

accept() 函數(shù)

accept() 函數(shù)接受請(qǐng)求的socket連接。如果成功則返回壓縮形式的網(wǎng)絡(luò)地址,否則返回FALSE:

accept( NEW_SOCKET, SOCKET );

SOCKET : 一個(gè)socket的描述符。

ADDRESS : ADDRESS 是 socket 地址 ( TCP/IP ) 包含了三個(gè)元素:

  • 地址簇 (TCP/IP, 是 AF_INET, 在你系統(tǒng)上可能是 2)

  • 端口號(hào) (例如 21)

  • 網(wǎng)絡(luò)地址 (例如 10.12.12.168)

accept() 通常應(yīng)用在無(wú)限循環(huán)當(dāng)中:

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

以上實(shí)例可以實(shí)時(shí)監(jiān)聽(tīng)客戶端的請(qǐng)求。


客戶端函數(shù)

connect() 函數(shù)

connect()系統(tǒng)調(diào)用為一個(gè)套接字設(shè)置連接,參數(shù)有文件描述符和主機(jī)地址。

connect( SOCKET, ADDRESS );

以下創(chuàng)建一個(gè)連接到服務(wù)端 socket 的實(shí)例:

$port = 21;    #  ftp 端口
$server_ip_address = "10.12.12.168";
connect( SOCKET, pack_sockaddr_in($port, inet_aton($server_ip_address)))
    or die "無(wú)法綁定端口! \n";

完整實(shí)例

接下來(lái)我們通過(guò)一個(gè)完整實(shí)例來(lái)了解下所有 socket 函數(shù)的應(yīng)用:

服務(wù)端 server.pl 代碼:

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

use strict;
use Socket;

# 使用端口 7890 作為默認(rèn)值
my $port = shift || 7890;
my $proto = getprotobyname('tcp');
my $server = "localhost";  # 設(shè)置本地地址

# 創(chuàng)建 socket, 端口可重復(fù)使用,創(chuàng)建多個(gè)連接
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
   or die "無(wú)法打開(kāi) socket $!\n";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, 1)
   or die "無(wú)法設(shè)置 SO_REUSEADDR $!\n";

# 綁定端口并監(jiān)聽(tīng)
bind( SOCKET, pack_sockaddr_in($port, inet_aton($server)))
   or die "無(wú)法綁定端口 $port! \n";

listen(SOCKET, 5) or die "listen: $!";
print "訪問(wèn)啟動(dòng):$port\n";

# 接收請(qǐng)求
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 "我是來(lái)自服務(wù)端的信息";
   print "Connection recieved from $name\n";
   close NEW_SOCKET;
}

打開(kāi)一個(gè)終端,執(zhí)行以下代碼:

$ perl sever.pl
訪問(wèn)啟動(dòng):7890

客戶端 client.pl 代碼:

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

use strict;
use Socket;

# 初始化地址與端口
my $host = shift || 'localhost';
my $port = shift || 7890;
my $server = "localhost";  # 主機(jī)地址

# 創(chuàng)建 socket 并連接
socket(SOCKET,PF_INET,SOCK_STREAM,(getprotobyname('tcp'))[2])
   or die "無(wú)法創(chuàng)建 socket $!\n";
connect( SOCKET, pack_sockaddr_in($port, inet_aton($server)))
   or die "無(wú)法連接:port $port! \n";

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

打開(kāi)另外一個(gè)終端,執(zhí)行以下代碼:

$ perl client.pl
我是來(lái)自服務(wù)端的信息