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

Perl,IO::Socket::SSL,多线程,perl,sockets,ssl,Perl,Sockets,Ssl,我已经用Perl实现了一个小型Web服务器。它正在使用IO::Socket::INET和IO::Socket::SSL并行进行侦听 如果HTTP端口出现连接,我将启动一个线程并处理IO::Socket::INET引用 由于Net::SSLeay中的线程限制(文档中的IO::Socket::SSL表示,1.43以下的线程不安全),我没有并行化SSL。我只是在相同的上下文中调用处理函数。 在HTTP的并行化情况下,处理程序函数是threads函数 所有这些在更长的时间内都像预期的那样起作用 我现在已

我已经用Perl实现了一个小型Web服务器。它正在使用IO::Socket::INET和IO::Socket::SSL并行进行侦听

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

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

所有这些在更长的时间内都像预期的那样起作用

我现在已经更新了我的系统。现在,我的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::Handle;
使用Fcntl(“F_GETFL”、“F_SETFL”、“O_NONBLOCK”);
使用时间:雇佣(“usleep”);
使用插座;
使用IO::Socket::SSL;
使用线程;
标准输出->自动刷新();
my$port=“4433”;
我的$cer=“cer.cer”;
my$key=“key.key”;
我的$sock=IO::Socket::SSL->new(Listen=>SOMAXCONN,LocalPort=>$port,
阻塞=>0,超时=>0,ReuseAddr=>1,SSL\U服务器=>1,
SSL\U证书文件=>$cer,SSL\U密钥文件=>$key)或die$@;
我的$WITH_THREADS=0;#开关!!
对于(;;)
{
评估
{
my$cl=$sock->accept();
如果($cl)
{   
打印(“\nssl connect”);
如果($WITH_THREADS==0)
{
#这不是多线程
客户(cl);
}
否则{
#使用多线程
my$th=threads->create(\&client,$cl);
$th->detach();
}
}
}##评估
如果($@)
{
打印“ex:$@”;
出口(1);
}
美国LEEP(100000);
}永远
次级客户#工人
{
我的$cl=班次;
#解除封锁
我的$flags=fcntl($cl,F_GETFL,0)或die$!;
fcntl($cl,F|U SETFL,$flags | O|U NONBLOCK)或die$!;
打印“\n”。$cl->peerhost.”/“$cl->peerport;
我的$ret=“”;
对于(我的$i=0;$i<100;$i++)
{
$ret=$cl->read(my$recv,5000);
#如果使用线程,则此处出现错误!
if(定义($ret)和长度($recv)>0)
{   
打印“\n收到的$ret字节”;
}
其他的
{
打印“\n无数据”;
}   
usleep(200000);
}
打印“\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

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

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

详细信息:
您将主线程中的SSL套接字接受为
$cl
,然后创建一个在新套接字上工作的新线程。实际上,这意味着您拥有相同的文件描述符(内核)、相同的OpenSSL数据结构(用户空间),但有两个使用此单一数据结构的Perl变量(Perl线程不共享任何内容,因此Perl部分是重复的)

这只会导致问题,因为您随后会隐式关闭主线程中的套接字(
$cl
超出范围),但会继续在客户端线程中使用它。主线程中的关闭会导致SSL关闭,然后释放底层OpenSSL结构。因此,客户机线程中的
$cl
指向导致崩溃的一些已释放内存。如果使用forking而不是threading,实际上也会得到类似的结果(没有崩溃),因为在主进程中仍然存在SSL关闭,因此对等方认为套接字已关闭,子进程将无法进一步使用套接字

您不应该在主线程中执行SSL accept,而应该将每个SSL活动都移动到客户端线程中。这将通过在普通套接字对象上执行accept,然后在客户端线程中将其升级到SSL来完成。无论如何,这是首选方法,有关详细信息,请参阅IO::Socket::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 $@;

我对工作前的想法不熟悉。你能简单地解释一下你是如何处理这个问题的吗?你必须在一个地方(套接字侦听器)进行监听,然后把它转发给一个工作人员。我可以想象在接受之后(接受之后)会有一个fork,但是在接受之前我该怎么做呢?对不起,请忽略我的评论。我刚刚看过httpd,它的前期工作背后的想法是什么。如果它不能与perl线程一起工作,也许我会考虑它。但我更喜欢当前的想法。@chris:我是IO::Socket::SSL的维护者。我不知道线程支持有任何问题。您可以添加OS、Perl版本和IO::Socket::SSL版本吗?如果你能提供一个最小但完整的例子来重现这个问题,我可以仔细看看。谢谢你,太好了。我可以把详细信息发送到你的数据包维护者地址吗。。。我懂了。太完美了!非常感谢你!!