2016-02-25 194 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