Like Share Discussion Bookmark Smile

J.J. Huang   2021-06-11   Perl   瀏覽次數:

Perl - 第二十一章 | Perl Socket 編程

Socket又稱”套接字”,應用程序通常通過”套接字”向網絡發出請求或者應答網絡請求,使主機間或者一台計算機上的進程間可以通訊。

  • 創建服務端

    • 使用 socket 函數來創建 socket服務。
    • 使用 bind 函數綁定端口。
    • 使用 listen 函數監聽端口。
    • 使用 accept 函數接收客戶端請求。
  • 創建客戶端

    • 使用 socket 函數來創建 socket 服務。
    • 使用 connect 函數連接到 socket 服務端。

下圖顯示了客戶端和服務器用於相互通信的調用的完整序列:

服務端 socket 函數

socket 函數

Perl 中,我們用 socket()函數來創建套接字,語法格式如下:

1
socket( SOCKET, DOMAIN, TYPE, PROTOCOL );

參數解析:

  • DOMAIN:創建的套接字指定協議集。例如:
    • AF_INET:表示IPv4網絡協議
    • AF_INET6:表示IPv6
    • AF_UNIX:表示本地套接字(使用一個文件)
  • TYPE:套接字類型可以根據是面向連接的還是非連接分為SOCK_STREAM或SOCK_DGRAM
  • PROTOCOL:應該是 (getprotobyname(‘tcp’))[2]。指定實際使用的傳輸協議。

所以 socket 函數調用方式如下:

1
2
3
use Socket # 定義了 PF_INET 和 SOCK_STREAM

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

bind() 函數

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

1
bind( SOCKET, ADDRESS );

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

  • 地址族(對於 TCP/IP,即 AF_INET,在你的系統上可能為 2)。
  • 端口號(例如 21)。
  • 計算機的 Internet 地址(例如 10.12.12.168)。

使用socket()創建套接字後,只賦予其所使用的協議,並未分配地址。在接受其它主機的連接前,必須先調用bind()為套接字分配一個地址。

簡單範例如下:

1
2
3
4
5
6
use Socket # 定義了 PF_INET 和 SOCK_STREAM

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

or die在綁定地址失敗後執行。
通過設置 setsockopt() 可選項 SO_REUSEADDR 設置端口可立即重複使用。
pack_sockaddr_in()函數將地址轉換為二進制格式。


listen() 函數

當socket和一個地址綁定之後,​​listen()函數會開始監聽可能的連接請求。然而,這只能在有可靠資料流保證的時候使用:

1
listen( SOCKET, QUEUESIZE );

SOCKET : 一個socket的描述符。
QUEUESIZE : 是一個決定監聽隊列大小的整數,當有一個連接請求到來,就會進入此監聽隊列;當一個連接請求被accept()接受,則從監聽隊列中移出;當隊列滿後,新的連接請求會返回錯誤。

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


accept() 函數

accept() 函數接受請求的socket連接。如果成功則返回壓縮形式的網絡地址,否則返回FALSE:

1
accept( NEW_SOCKET, SOCKET );

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

  • 地址族(對於 TCP/IP,即 AF_INET,在你的系統上可能為 2)。
  • 端口號(例如 21)。
  • 計算機的 Internet 地址(例如 10.12.12.168)。

accept() 通常應用在無限循環當中:

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

以上範例可以實時監聽客戶端的請求。

客戶端函數

connect() 函數

connect()系統調用為一個套接字設置連接,參數有文件描述符和主機地址。

1
connect( SOCKET, ADDRESS );

以下創建一個連接到服務端 socket 的範例:

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

完整範例

接下來我們通過一個完整範例來了解下所有 socket 函數的應用:

  • 服務端 server.pl 程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/perl -w
# Filename : server.pl

use strict;
use Socket;

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

# 創建 socket, 端口可重複使用,創建多個連接
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
or die "無法打開 socket $!\n";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, 1)
or die "無法設置 SO_REUSEADDR $!\n";

# 綁定端口並監聽
bind( SOCKET, pack_sockaddr_in($port, inet_aton($server)))
or die "無法綁定端口 $port! \n";

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

# 接收請求
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 "我是來自服務端的信息";
print "Connection recieved from $name\n";
close NEW_SOCKET;
}

打開一個終端,執行以下程式碼:

1
2
$ perl sever.pl
訪問啟動:7890
  • 客戶端 client.pl 程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/perl -w
# Filename : client.pl

use strict;
use Socket;

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

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

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

打開另外一個終端,執行以下程式碼:

1
2
$ perl client.pl
我是來自服務端的信息

註:以上參考了
Tutorialspoint, Perl - Socket Programming