Perl,IO :: Socket :: SSL,多线程

时间:2016-02-25 16:10:04

标签: perl sockets ssl

我在Perl中实现了一个小型网络服务器。它正在侦听IO :: Socket :: INET和IO :: Socket :: SSL并行。

如果在HTTP端口出现连接,我启动一个线程并处理IO :: Socket :: INET引用。

由于Net :: SSLeay中的线程限制(文档中的IO :: Socket :: SSL表示在1.43以下不是线程安全的)我没有并行化SSL。我只是在同一个上下文中调用handler-function。 在HTTP的并行化情况下,handler-function是线程函数。

这一切都按预期工作了很长时间。

我现在已经更新了我的系统。现在我的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 $@;

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 ($@)
 {
        print "ex: $@";
        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 :: Socket :: SSL不是线程安全的,但我不确定是否仍然如此。

有谁知道这样可能吗?或者也许它是可能的,但我以错误的方式处理它......

谢谢, 克里斯

编辑:我在Perl 5.20.2中使用Debian 8.3。 Net :: SSLeay是1.72,IO :: Socket :: SSL是2.024。 OpenSSL 1.0.1k

编辑:将代码示例更改为功能齐全的小样本程序。

1 个答案:

答案 0 :(得分:2)

TL; TR:
不要将已建立的SSL套接字复制到另一个线程中。

详细说明:
您将主线程中的SSL套接字接受到$cl,然后创建一个适用于新套接字的新线程。实际上,这意味着您具有相同的文件描述符(内核),相同的OpenSSL数据结构(用户空间),但使用此单一数据结构的两个Perl变量(Perl线程没有共享 - 因此Perl部分是重复的。)

这只会导致麻烦,因为您隐式关闭主服务器中的套接字($cl超出范围),但继续在客户端线程中使用它。主线程中的关闭导致SSL关闭,然后释放底层的OpenSSL结构。因此,客户端线程中的$cl指向一些导致崩溃的释放内存。如果你使用分叉而不是线程,你实际上也会得到这样的东西(没有崩溃),因为在主进程中仍然有SSL关闭,因此对等体认为套接字已关闭且子进程无法进一步使用套接字。

您应该将每个SSL活动移动到客户端线程中,而不是在主线程中执行SSL接受。这将通过在普通套接字对象上执行accept然后在客户端线程中将其升级到SSL来完成。无论如何,这是首选方法,有关详细信息,请参阅IO :: Socket :: SSL文档中的Basic SSL server

最后,您的代码将会更改为:

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 $@;