我在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
编辑:将代码示例更改为功能齐全的小样本程序。
答案 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 $@;