2016-02-25 197 views
1

我已經在Perl中實現了一個小型web服務器。它正在使用IO :: Socket :: INET和IO :: Socket :: SSL並行進行偵聽。Perl,IO :: Socket :: SSL,多線程

如果連接出現在HTTP端口,我啓動一個線程並處理IO :: Socket :: INET引用。

由於Net :: SSLeay(IO :: Socket :: SSL中的線程限制,在文檔中表示不是線程安全的,低於1.43),我沒有對SSL進行並行化。我只是在相同的上下文中調用處理函數。 在HTTP的並行化情況下,處理函數是線程函數。

所有這些按預期工作時間更長。

我現在已經更新了我的系統。現在我的Net :: SSLeay是1.72,我也嘗試了SSL的同步 - 就像我用HTTP做的一樣。但是我在第一次閱讀時遇到了分段錯誤。

use strict; 
use warnings; 
use IO::Handle; 
use Fcntl ("F_GETFL", "F_SETFL", "O_NONBLOCK"); 
use Time::HiRes ("usleep"); 
use Socket; 
use IO::Socket::SSL; 
use threads; 
STDOUT->autoflush(); 

my $port = "4433"; 
my $cer = "cer.cer"; 
my $key = "key.key"; 

my $sock = IO::Socket::SSL->new (Listen => SOMAXCONN, LocalPort => $port, 
Blocking => 0, Timeout => 0, ReuseAddr => 1, SSL_server => 1, 
SSL_cert_file => $cer, SSL_key_file => $key) or die [email protected]; 

my $WITH_THREADS = 0;  # the switch!! 

for (;;) 
{ 
    eval 
    { 
    my $cl = $sock->accept(); 
    if ($cl) 
    { 
     print ("\nssl connect"); 

      if ($WITH_THREADS == 0) 
      { 
      # this is no multi-threading 
      client ($cl); 
      } 
      else {  
      # with multithreading 
      my $th = threads->create (\&client, $cl); 
      $th->detach(); 
     } 
    } 
}; # eval 
if ([email protected]) 
{ 
     print "ex: [email protected]"; 
     exit (1); 
} 
usleep (100000);   
} # forever 


sub client # worker 
{ 
    my $cl = shift; 

    # unblock 
    my $flags = fcntl ($cl, F_GETFL, 0) or die $!; 
    fcntl ($cl, F_SETFL, $flags | O_NONBLOCK) or die $!; 

    print "\n" . $cl->peerhost . "/" . $cl->peerport; 

my $ret = ""; 

for (my $i = 0; $i < 100; $i ++) 
{ 
    $ret = $cl->read (my $recv, 5000);  
    # faults here if with threads! 

    if (defined ($ret) && length ($recv) > 0) 
    { 
     print "\nreceived $ret bytes"; 
    } 
    else 
    { 
     print "\nno data"; 

    } 
    usleep (200000); 
    } 
    print "\nend client"; 
    $cl->close(); 
} 

我也看過一些帖子,他們說IO ::插座:: SSL是不是線程安全的,但我不知道這仍是如此。

有誰知道這是可能的嗎?或者,也許這是可能的,但我處理了錯誤的方式...

謝謝, 克里斯

編輯:我使用Debian 8.3用Perl 5.20.2。 Net :: SSLeay是1.72,IO :: Socket :: SSL是2.024。 OpenSSL 1.0.1k

編輯:更改代碼示例爲功能齊全的小樣本程序。

+0

我不熟悉的prefork的想法。你能不能簡單地解釋一下你如何處理你必須在一個地方監聽的問題(socket監聽器),然後把它推送給一個工作者。 我可以想象一個後叉(在接受之後),但在接受之前我該怎麼做? – chris01

+0

對不起,請忽略我的意見 – ikegami

+0

只看了一下httpd,它的prefork背後有什麼想法。如果它不適用於perl線程,也許我會考慮它。但我會優先考慮當前的想法。 – chris01

回答

2

TL; TR:
不會將已建立的SSL套接字複製到另一個線程中。

詳細信息:
您接受主線程中的SSL套接字到$cl,然後創建一個新套接字的新線程。實際上,這意味着你具有相同的文件描述符(內核),相同的OpenSSL數據結構(用戶空間),但使用這種單一數據結構的兩個Perl變量(perl線程無共享 - 因此Perl部分被複制)。

這不僅造成麻煩,因爲你那麼含蓄地關閉套接字在主($cl失控的範圍),但繼續在客戶端線程使用它。主線程中的close會導致SSL關閉,然後釋放底層的OpenSSL結構。因此客戶端線程中的$cl指向導致崩潰的一些釋放的內存。實際上,你得到這樣的事情(不崩潰),如果你還使用分叉,而不是線程,因爲還有在主過程中所做的SSL關機所以同行認爲插座關閉,孩子將不能夠進一步利用插座。

而不是做了SSL在主線程接受的,你應該每隔SSL活動進入客戶端線程。這將通過執行接受正常的插座對象上,然後將其升級到SSL客戶端線程來完成。這是反正看到Basic SSL server的IO ::插座::對於細節SSL文檔中的首選方式。

最終您的代碼將被改變這樣的:

my $port = "4433"; 
my $cer = "cer.cer"; 
my $key = "key.key"; 

# don't create a SSL socket but an INET socket 
my $sock = IO::Socket::IP->new (
    Listen => SOMAXCONN, LocalPort => $port, Blocking => 0, ReuseAddr => 1 
) or die $!; 

my $WITH_THREADS = 1;  # the switch!! 
.... 
sub client # worker 
{ 
    my $cl = shift; 
    # upgrade INET socket to SSL 
    $cl = IO::Socket::SSL->start_SSL($cl, 
    SSL_server => 1, 
    SSL_cert_file => $cer, 
    SSL_key_file => $key 
) or die [email protected]; 
+0

噢......我明白了。這是完美的!非常感謝你!! – chris01