ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Coro/Event/Socket.pm
Revision: 1.40
Committed: Sun Nov 5 02:01:24 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
Changes since 1.39: +36 -43 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 Coro::Socket - non-blocking socket-io
4
5 =head1 SYNOPSIS
6
7 use Coro::Socket;
8
9 # listen on an ipv4 socket
10 my $socket = new Coro::Socket PeerHost => "localhost",
11 PeerPort => 'finger';
12
13 # listen on any other type of socket
14 my $socket = Coro::Socket->new_from_fh
15 (IO::Socket::UNIX->new
16 Local => "/tmp/socket",
17 Type => SOCK_STREAM,
18 );
19
20 =head1 DESCRIPTION
21
22 This module implements socket-handles in a coroutine-compatible way,
23 that is, other coroutines can run while reads or writes block on the
24 handle. L<Coro::Handle>.
25
26 =over 4
27
28 =cut
29
30 package Coro::Socket;
31
32 no warnings "uninitialized";
33
34 use strict;
35
36 use Errno ();
37 use Carp qw(croak);
38 use Socket;
39
40 use Coro::Util ();
41
42 use base 'Coro::Handle';
43
44 our $VERSION = 1.9;
45
46 our (%_proto, %_port);
47
48 sub _proto($) {
49 $_proto{$_[0]} ||= do {
50 ((getprotobyname $_[0])[2] || (getprotobynumber $_[0])[2])
51 or croak "unsupported protocol: $_[0]";
52 };
53 }
54
55 sub _port($$) {
56 $_port{$_[0],$_[1]} ||= do {
57 return $_[0] if $_[0] =~ /^\d+$/;
58
59 $_[0] =~ /([^(]+)\s*(?:\((\d+)\))?/x
60 or croak "unparsable port number: $_[0]";
61 ((getservbyname $1, $_[1])[2]
62 || (getservbyport $1, $_[1])[2]
63 || $2)
64 or croak "unknown port: $_[0]";
65 };
66 }
67
68 sub _sa($$$) {
69 my ($host, $port, $proto) = @_;
70 $port or $host =~ s/:([^:]+)$// and $port = $1;
71 my $_proto = _proto($proto);
72 my $_port = _port($port, $proto);
73
74 # optimize this a bit for a common case
75 if (Coro::Util::dotted_quad $host) {
76 return pack_sockaddr_in ($_port, inet_aton $host);
77 } else {
78 my (undef, undef, undef, undef, @host) = Coro::Util::gethostbyname $host
79 or croak "unknown host: $host";
80 map pack_sockaddr_in ($_port,$_), @host;
81 }
82 }
83
84 =item $fh = new Coro::Socket param => value, ...
85
86 Create a new non-blocking tcp handle and connect to the given host
87 and port. The parameter names and values are mostly the same as in
88 IO::Socket::INET (as ugly as I think they are).
89
90 If the host is unreachable or otherwise cannot be connected to this method
91 returns undef. On all other errors ot croak's.
92
93 Multihomed is always enabled.
94
95 $fh = new Coro::Socket PeerHost => "localhost", PeerPort => 'finger';
96
97 =cut
98
99 sub _prepare_socket {
100 my ($self, $arg) = @_;
101
102 $self
103 }
104
105 sub new {
106 my ($class, %arg) = @_;
107
108 $arg{Proto} ||= 'tcp';
109 $arg{LocalHost} ||= delete $arg{LocalAddr};
110 $arg{PeerHost} ||= delete $arg{PeerAddr};
111 defined ($arg{Type}) or $arg{Type} = $arg{Proto} eq "tcp" ? SOCK_STREAM : SOCK_DGRAM;
112
113 socket my $fh, PF_INET, $arg{Type}, _proto ($arg{Proto})
114 or return;
115
116 my $self = bless Coro::Handle->new_from_fh (
117 $fh,
118 timeout => $arg{Timeout},
119 forward_class => $arg{forward_class},
120 ), $class
121 or return;
122
123 $self->configure (\%arg)
124 }
125
126 sub configure {
127 my ($self, $arg) = @_;
128
129 if ($arg->{ReuseAddr}) {
130 $self->setsockopt (SOL_SOCKET, SO_REUSEADDR, 1)
131 or croak "setsockopt(SO_REUSEADDR): $!";
132 }
133
134 if ($arg->{ReusePort}) {
135 $self->setsockopt (SOL_SOCKET, SO_REUSEPORT, 1)
136 or croak "setsockopt(SO_REUSEPORT): $!";
137 }
138
139 if ($arg->{LocalPort} || $arg->{LocalHost}) {
140 my @sa = _sa($arg->{LocalHost} || "0.0.0.0", $arg->{LocalPort} || 0, $arg->{Proto});
141 $self->bind ($sa[0])
142 or croak "bind($arg->{LocalHost}:$arg->{LocalPort}): $!";
143 }
144
145 if ($arg->{PeerHost}) {
146 my @sa = _sa($arg->{PeerHost}, $arg->{PeerPort}, $arg->{Proto});
147
148 for (@sa) {
149 $! = 0;
150
151 if ($self->connect ($_)) {
152 next unless writable $self;
153 $! = unpack "i", $self->getsockopt(SOL_SOCKET, SO_ERROR);
154 }
155
156 $! or last;
157
158 $!{ECONNREFUSED} or $!{ENETUNREACH} or $!{ETIMEDOUT} or $!{EHOSTUNREACH}
159 or return;
160 }
161 } else {
162 if (exists $arg->{Listen}) {
163 $self->listen ($arg->{Listen})
164 or return;
165 }
166 }
167
168 $self
169 }
170
171 =item connect, listen, bind, getsockopt, setsockopt,
172 send, recv, peername, sockname, shutdown
173
174 Do the same thing as the perl builtins or IO::Socket methods (but return
175 true on EINPROGRESS). Remember that these must be method calls.
176
177 =cut
178
179 sub connect { connect tied(${$_[0]})->[0], $_[1] or $! == Errno::EINPROGRESS }
180 sub bind { bind tied(${$_[0]})->[0], $_[1] }
181 sub listen { listen tied(${$_[0]})->[0], $_[1] }
182 sub getsockopt { getsockopt tied(${$_[0]})->[0], $_[1], $_[2] }
183 sub setsockopt { setsockopt tied(${$_[0]})->[0], $_[1], $_[2], $_[3] }
184 sub send { send tied(${$_[0]})->[0], $_[1], $_[2], @_ > 2 ? $_[3] : () }
185 sub recv { recv tied(${$_[0]})->[0], $_[1], $_[2], @_ > 2 ? $_[3] : () }
186 sub sockname { getsockname tied(${$_[0]})->[0] }
187 sub peername { getpeername tied(${$_[0]})->[0] }
188 sub shutdown { shutdown tied(${$_[0]})->[0], $_[1] }
189
190 =item ($fh, $peername) = $listen_fh->accept
191
192 In scalar context, returns the newly accepted socket (or undef) and in
193 list context return the ($fh, $peername) pair (or nothing).
194
195 =cut
196
197 sub accept {
198 my ($peername, $fh);
199 while () {
200 $peername = accept $fh, tied(${$_[0]})->[0]
201 and return wantarray
202 ? ($_[0]->new_from_fh($fh), $peername)
203 : $_[0]->new_from_fh($fh);
204
205 return unless $!{EAGAIN};
206
207 $_[0]->readable or return;
208 }
209 }
210
211 1;
212
213 =back
214
215 =head1 AUTHOR
216
217 Marc Lehmann <schmorp@schmorp.de>
218 http://home.schmorp.de/
219
220 =cut
221