如何从文件描述符创建IO::Socket::INET对象?解决参数长度错误
Socket::unpack_sockaddr_in 参数长度错误(systemd套接字激活Plack应用场景)
问题现象
通过systemd套接字激活Plack应用后,客户端发送HTTP请求无响应,查看journalctl日志出现以下错误:
systemd: Started Socket-activated hello world service. plackup: HTTP::Server::PSGI: Accepting connections at http://0:5000/ plackup: Bad arg length for Socket::unpack_sockaddr_in, length is 28, should be 16 at …perl/5.42.2/lib/5.42.2/x86_64-linux-ld/Socket.pm line 858. systemd: demo.service: Main process exited, code=exited, status=29/n/a systemd: demo.service: Failed with result 'exit-code'.
问题根源
补丁中使用IO::Socket::INET->new_from_fd(3, 'r+')获取systemd传递的套接字,但IO::Socket::INET是仅支持IPv4的套接字类。而systemd默认创建的是IPv4/IPv6双栈兼容套接字,其地址结构为28字节的IPv6格式,unpack_sockaddr_in只能解析16字节的IPv4地址结构,因此触发长度不匹配错误。
解决方案
将IO::Socket::INET替换为支持双栈的IO::Socket::IP,修改后的补丁如下:
diff --git a/lib/HTTP/Server/PSGI.pm b/lib/HTTP/Server/PSGI.pm index 225d4ca..f060463 100644 --- a/lib/HTTP/Server/PSGI.pm +++ b/lib/HTTP/Server/PSGI.pm @@ -90,6 +90,12 @@ sub prepare_socket_class { sub setup_listener { my $self = shift; + # TO DO: also handle LISTEN_PID LISTEN_PIDFDID + if ($ENV{LISTEN_FDS} && 1 == $ENV{LISTEN_FDS}) { + $self->{listen_sock} ||= IO::Socket::IP->new_from_fd(3, 'r+'); + goto DONE; + } + $self->{listen_sock} ||= do { my %args = ( Listen => SOMAXCONN, @@ -104,6 +110,7 @@ sub setup_listener { or die "failed to listen to port $self->{port}: $!"; }; +DONE: $self->{server_ready}->({ %$self, proto => $self->{ssl} ? 'https' : 'http' }); }
补充说明
- 确保已安装
IO::Socket::IP模块,可通过命令安装:cpanm IO::Socket::IP - 若需强制使用IPv4(放弃IPv6支持),可在创建套接字时指定域:
$self->{listen_sock} ||= IO::Socket::INET->new_from_fd(3, 'r+', Domain => AF_INET);
验证步骤
- 重新应用修改后的补丁并安装Plack
- 重启服务:
systemctl --user stop demo.service systemctl --user restart demo.socket - 再次发送HTTP请求,确认服务正常响应
内容的提问来源于stack exchange,提问作者daxim
相关产品推荐
相关产品推荐

