package Test::SPVM::Sys::Socket::Server; use strict; use warnings; use Carp (); use Socket; use IO::Socket::IP; use IO::Socket::UNIX; # Fields sub socket_domain { shift->{socket_domain} } sub socket_type { shift->{socket_type} } sub socket_protocol { shift->{socket_protocol} } sub io_socket { shift->{io_socket} } sub listen_backlog { shift->{listen_backlog} } sub host { shift->{host} } sub port { shift->{port} } sub path { shift->{path} } sub loop_cb { shift->{loop_cb} } sub server_options { shift->{server_options} } # Class Methods sub new { my $class = shift; my $self = { listen_backlog => SOMAXCONN, server_options => {}, @_ }; bless $self, ref $class || $class; return $self; } sub new_echo_server_ipv4_tcp { my $class = shift; my $loop_cb = \&_echo_server_accept_loop; my %options = ( socket_domain => AF_INET, socket_type => SOCK_STREAM, host => '127.0.0.1', loop_cb => $loop_cb, @_, ); my $self = $class->new(%options); my $host = $self->{host}; my $port = $self->{port}; unless (defined $port) { Carp::confess("\"port\" option must be defined."); } my $listen_backlog = $self->{listen_backlog}; my $socket_domain = $self->{socket_domain}; my $socket_type = $self->{socket_type}; my $io_socket = IO::Socket::IP->new( LocalAddr => $host, LocalPort => $port, Listen => $listen_backlog, Domain => $socket_domain, Type => $socket_type, ReuseAddr => 1, ); unless ($io_socket) { Carp::confess("Can't create a server socket:$@"); } $self->{io_socket} = $io_socket; return $self; } sub new_echo_server_ipv6_tcp { my $class = shift; my $loop_cb = \&_echo_server_accept_loop; my %options = ( socket_domain => AF_INET6, socket_type => SOCK_STREAM, host => '::1', loop_cb => $loop_cb, @_, ); my $self = $class->new(%options); my $host = $self->{host}; my $port = $self->{port}; unless (defined $port) { Carp::confess("\"port\" option must be defined."); } my $listen_backlog = $self->{listen_backlog}; my $socket_domain = $self->{socket_domain}; my $socket_type = $self->{socket_type}; my $io_socket = IO::Socket::IP->new( LocalAddr => $host, LocalPort => $port, Listen => $listen_backlog, Domain => $socket_domain, Type => $socket_type, ReuseAddr => 1, V6Only => 1, ); unless ($io_socket) { Carp::confess("Can't create a server socket:$@"); } $self->{io_socket} = $io_socket; return $self; } sub new_echo_server_unix_tcp { my $class = shift; my $loop_cb = \&_echo_server_accept_loop; my %options = ( socket_type => SOCK_STREAM, loop_cb => $loop_cb, @_, ); my $self = $class->new(%options); my $host = $self->{host}; my $path = $self->{path}; unless (defined $path) { Carp::confess("\"path\" option must be defined."); } my $listen_backlog = $self->{listen_backlog}; my $socket_type = $self->{socket_type}; my $io_socket = IO::Socket::UNIX->new( Type => $socket_type, Local => $path, Listen => $listen_backlog, ); unless ($io_socket) { Carp::confess("Can't create a server socket:$@"); } $self->{io_socket} = $io_socket; return $self; } sub _echo_server_accept_loop { my ($server_manager) = @_; my $io_socket = $server_manager->{io_socket}; my $read_buffer_length = $server_manager->{server_options}{read_buffer_length} // 1024; while (1) { my $client_socket = $io_socket->accept; while (1) { my $buffer; my $read_length = $client_socket->sysread($buffer, $read_buffer_length); if ($read_length) { $client_socket->syswrite($buffer, $read_length); } else { last; } } } } # Instance Methods sub start { my ($self) = @_; my $loop_cb = $self->{loop_cb}; $loop_cb->($self); } 1; =head1 Name Test::SPVM::Sys::Socket::Server - Servers for tests for SPVM::Sys::Socket =head1 Description Test::SPVM::Sys::Socket::Server class has methods to start servers for tests for L<SPVM::Sys::Socket>. =head1 Usage =head1 Fields =head2 socket_domain my $socket_domain = $self->socket_domain; A socket domain. =head2 socket_type my $socket_type = $self->socket_type; A socket type. =head2 socket_protocol my $socket_protocol = $self->socket_protocol; A socket protocol. =head2 io_socket my $io_socket = $self->io_socket; An L<IO::Socket> object. =head2 listen_backlog my $listen_backlog = $self->listen_backlog; The length of listen backlog. =head2 host my $host = $self->host; A host name for intenet domain sockets. =head2 port my $port = $self->port; A port number for intenet domain sockets. =head2 path my $path = $self->path; A path for UNIX domain sockets. =head2 loop_cb my $loop_cb = $self->loop_cb; An anon subroutine for server main loop. =head2 server_options my $server_options = $self->server_options; Server options. This should be an hash reference. =head1 Class Methods =head2 new my $server_manager = Test::SPVM::Sys::Socket::Server->new(%options); Creates a new L<Test::SPVM::Sys::Socket::Server> object and returns it. Options: =over 2 =item * C<socket_domain> Sets L</"socket_domain"> field to this value. =item * C<socket_type> my $socket_type = $self->socket_type; Sets L</"socket_type"> field to this value. =item * C<socket_protocol> my $socket_protocol = $self->socket_protocol; Sets L</"socket_protocol"> field to this value. =item * C<listen_backlog> my $listen_backlog = $self->listen_backlog; Sets L</"listen_backlog"> field to this value. =item * C<host> my $host = $self->host; Sets L</"host"> field to this value. =item * C<port> my $port = $self->port; Sets L</"port"> field to this value. =item * C<path> my $path = $self->path; Sets L</"path"> field to this value. =item * C<loop_cb> my $loop_cb = $self->loop_cb; Sets L</"loop_cb"> field to this value. =item * C<server_options> my $server_options = $self->server_options; Sets L</"server_options"> field to this value. This value must be a hash reference if specified. If this option is not defined, the field is set to an emtpy hash reference. =back =head2 new_echo_server_ipv4_tcp my $server_manager = Test::SPVM::Sys::Socket::Server->new_echo_server_ipv4_tcp(%options); Creates a new a new L<Test::SPVM::Sys::Socket::Server> object that has the features for an IPv4-TCP echo server and returns it. An L<IO::Socket::IP> object is created and L</"io_socket"> field is set to an L<IO::Socket::IP> object. A client can signal to the echo server that it is done writing with C<SHUT_WR>. use Sys::Socket; use Sys::Socket::Constant as SOCKET; Sys::Socket->shutdown($socket, SOCKET->SHUT_WR); The options %options are the same as ones of L</"new"> method. =head1 Instance Methods =head2 start $server_manager->start; Starts the server. This method call a subroutine stored in L</"loop_cb"> field given the L<Test::SPVM::Sys::Socket::Server> object at 1th argument.