package IO::Socket::TIPC; use IO::Socket::TIPC::Sockaddr ':all'; use strict; use Carp; use IO::Socket; use Scalar::Util qw(looks_like_number); use AutoLoader; use Exporter; our @ISA = qw(Exporter IO::Socket); our $VERSION = '1.08'; =head1 NAME IO::Socket::TIPC - TIPC sockets for Perl =head1 SYNOPSIS use IO::Socket::TIPC; my $sock = IO::Socket::TIPC->new( SocketType => "stream", Peer => "{1000, 100}" ); die "Could not connect to {1000, 100}: $!\n" unless $sock; More in-depth examples are available in the B section, below. =head1 DESCRIPTION TIPC stands for Transparent Inter-Process Communication. See http://tipc.sf.net/ for details. This perl module subclasses IO::Socket, in order to use TIPC sockets in the customary (and convenient) Perl fashion. TIPC supports 4 types of socket: I, I, I and I. These are all available through this perl API, though the usage varies depending on which kind of socket you use. I and I are connection-based sockets. These sockets are strictly client/server. For servers, B() will call B() for you, to bind to a I* name, and you then B() connections from clients, each of which get their own socket (returned from B). For clients, B() will call B() for you, to connect to the specified I* name, and once that succeeds, you can do I/O on the socket directly. In this respect, usage details are very similar to I over I. See the B section, for an example of connection-based socket use. I and I are connectionless sockets. You cannot use the normal send/recv/print/getline methods on them, because the network stack will not know which host on the network to send or receive from. Instead, once you have called B() to create the socket, you use B and B to send and receive individual packets to/from a specified peer, indicated using an IO::Socket::TIPC::Sockaddr class object. Connectionless sockets (I and I) are often bind()ed to a particular I or I address, in order to allow them to listen for packets sent to a well-known destination (the I). You can use I or I parameters to B(), to select a name or name-sequence to bind to. As above, these parameters internally become I and I arguments to IO::Socket::TIPC::Sockaddr->B(), and the result is passed to B(). This is very similar to typical uses of I over I. Since connectionless sockets are not linked to a particular peer, you can use B to send a packet to some peer with a given Name in the network, and B to receive replies from a peer in the network who sends a packet to your I (or I). You can also use I addressses to send multicast packets to *every* peer with a given name. Please see the I document (linked in B) for more details. See the B section, for an example of connection-less socket use. =cut sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() # XS function. my $constname; our $AUTOLOAD; ($constname = $AUTOLOAD) =~ s/.*:://; croak "&IO::Socket::TIPC::constant not defined" if $constname eq 'constant'; my ($error, $val) = constant($constname); if ($error) { $val = undef; } # undefined constants just return undef. { no strict 'refs'; *$AUTOLOAD = sub { $val }; } goto &$AUTOLOAD; } =head1 CONSTRUCTOR B returns a TIPC socket object. This object inherits from IO::Socket, and thus inherits all the methods of that class. This module was modeled specifically after I, and shares some things in common with that class. Specifically, the I parameter, the I* and Il* nomenclature, and the behind-the-scenes calls to B(), B(), B(), B(), and so on. Connection-based sockets (I and I) come in "server" and "client" varieties. To create a server socket, specify the I argument to B(). You can bind a name to the socket, thus making your server easier to find, by providing one or more I* parameters. To create a client socket, do B provide the I argument. Instead, provide one or more I* parameters, and B will call B() for you. All I* parameters are passed directly to IO::Socket::TIPC::Sockaddr->B(), minus the "Local" prefix, and the resulting sockaddr is passed to B(). Similarly, all I* parameters are passed directly to IO::Socket::TIPC::Sockaddr->B(), minus the "Peer" prefix, and the result is passed to B(). The keywords I and I themselves become the first string parameter to IO::Socket::TIPC::Sockaddr->B(); see the IO::Socket::TIPC::Sockaddr documentation for details. =head2 ARGUMENTS to new() =head2 SocketType This field is B. It tells the system what type of socket to use. The following constants will work, if they were imported: I, I, I, or I. Otherwise, you can just use the following text strings: "stream", "seqpacket", "rdm", or "dgram". =head2 Listen This field is only valid for connection-based Bs. Its existence specifies that this is a server socket. It is common to also specify some I* arguments, so B() can B your shiny new server socket to a well-known name. =head2 Importance This field informs the TIPC network stack of what priority it should consider delivering your messages to be. It corresponds to the I option, from B. If you provide this field, ->B() will call B for you to set the value. Default is I. Valid arguments are any of: TIPC_LOW_IMPORTANCE TIPC_MEDIUM_IMPORTANCE TIPC_HIGH_IMPORTANCE TIPC_CRITICAL_IMPORTANCE See I (linked in B) for details. =head2 ConnectTimeout This field specifies the B() timeout, in milliseconds. If you provide this field, ->B() will call B to set the I value on your socket. See I (linked in B) for details. B: I should not be confused with I, which is handled internally by IO::Socket and means something else. =head2 Local* This field is valid (and recommended) for all connectionless socket types, and for all servers using connection-type sockets. The I* parameter(s) determine which address your socket will get B()ed to. Any arguments prefixed with "Local" will be passed to IO::Socket::TIPC::Sockaddr->B(), with the "Local" prefix removed. If you specify the word I, itself, the argument will be passed as the first string parameter to IO::Socket::TIPC::Sockaddr->B(); all other I* arguments end up in the hash parameter list. See the documentation for IO::Socket::TIPC::Sockaddr, for details. Also, skip to the B section to see what this stuff looks like. =head2 Peer* This field is only valid for B (as opposed to B) using connection-type sockets, and is required for this case. The I* parameter(s) determine which address your socket will get B()ed to. Any arguments prefixed with "Peer" will be passed to IO::Socket::TIPC::Sockaddr->B(), with the "Peer" prefix removed. If you specify the word I, itself, the argument will be passed as the first string parameter to IO::Socket::TIPC::Sockaddr->B(); all other I* arguments end up in the hash parameter list. See the documentation for IO::Socket::TIPC::Sockaddr, for details. Also, skip to the B section to see what this stuff looks like. =cut sub new { # pass it down to IO::Socket my $class = shift; return IO::Socket::new($class,@_); } sub configure { # IO::Socket calls us back via this method call, from IO::Socket->new(). my($socket, $args) = @_; my (%local, %peer, $local, $peer); # move Local* args into %local, Peer* args into %peer. # keys "Local" and "Peer" themselves go into $local and $peer. # These become arguments to IO::Socket::TIPC::Sockaddr->new(). foreach my $key (sort keys %$args) { if($key =~ /^local/i) { my $newkey = substr($key,5); if(length($newkey)) { $local{$newkey} = $$args{$key}; } else { $local = $$args{$key}; } delete($$args{$key}); } if($key =~ /^peer/i) { my $newkey = substr($key,4); if(length($newkey)) { $peer{$newkey} = $$args{$key}; } else { $peer = $$args{$key}; } delete($$args{$key}); } } return undef unless fixup_args($args); return undef unless enforce_required_args($args); my $connectionless = 0; my $listener = 0; my $connector = (scalar keys %peer) || (defined $peer); my $binder = (scalar keys %local) || (defined $local); $listener = 1 if(exists($$args{Listen}) && $$args{Listen}); unless(looks_like_number($$args{SocketType})) { my %socket_types = ( stream => SOCK_STREAM, seqpacket => SOCK_SEQPACKET, rdm => SOCK_RDM, dgram => SOCK_DGRAM, ); if(exists($socket_types{lc($$args{SocketType})})) { $$args{SocketType} = $socket_types{lc($$args{SocketType})}; } else { croak "unknown SocketType $$args{SocketType}!"; } $connectionless = 1 if $$args{SocketType} == SOCK_RDM; $connectionless = 1 if $$args{SocketType} == SOCK_DGRAM; } croak "Connectionless socket types cannot listen(), but you've told me to Listen." if($connectionless && $listener); croak "Connectionless socket types cannot connect(), but you've given me a Peer address." if($connectionless && $connector); croak "Listener sockets cannot connect, but you've given me a Peer address." if($listener && $connector); croak "Connect()ing sockets cannot bind, but you've given me a Local address." if($connector && $binder); # If we've gotten this far, I figure everything is ok. # unless Sockaddr barfs, of course. $socket->socket(PF_TIPC(), $$args{SocketType}, 0) or croak "Could not create socket: $!"; # setsockopt/fcntl stuff goes here. if(exists $$args{ConnectTimeout}) { $socket->setsockopt(SOL_TIPC(), TIPC_CONN_TIMEOUT(), $$args{ConnectTimeout}) or croak "TIPC_CONN_TIMEOUT: $!"; } if(exists $$args{Importance}) { $socket->setsockopt(SOL_TIPC(), TIPC_IMPORTANCE() , $$args{Importance}) or croak "TIPC_IMPORTANCE: $!"; } if($binder) { my $baddr; if(defined($local)) { if(ref($local) && ref($local) eq "IO::Socket::TIPC::Sockaddr") { $baddr = $local; } else { $baddr = IO::Socket::TIPC::Sockaddr->new($local, %local); } } else { $baddr = IO::Socket::TIPC::Sockaddr->new(%local); } $socket->bind($baddr) or croak "Could not bind socket: $!"; } if($connector) { my $caddr; if(defined($peer)) { if(ref($peer) && ref($peer) eq "IO::Socket::TIPC::Sockaddr") { $caddr = $peer; } else { $caddr = IO::Socket::TIPC::Sockaddr->new($peer, %peer); } } else { $caddr = IO::Socket::TIPC::Sockaddr->new(%peer); } $socket->connect($caddr) or croak "Could not connect socket: $!"; } if($listener) { $socket->listen() or croak "Could not listen: $!"; } return $socket; } # a "0" denotes an optional value. a "1" is required. my %valid_args = ( Listen => 0, SocketType => 1, Importance => 0, ConnectTimeout => 0, ); sub enforce_required_args { my $args = shift; foreach my $key (sort keys %$args) { if($valid_args{$key}) { # argument is required. unless(exists($$args{$key})) { # argument not provided! croak "argument $key is REQUIRED."; } } } return 1; } sub fixup_args { my $args = shift; # Validate hash-key arguments to IO::Socket::TIPC->new() foreach my $key (sort keys %$args) { if(!exists($valid_args{$key})) { # This key needs to be fixed up. Search for it. my $lckey = lc($key); my $fixed = 0; foreach my $goodkey (sort keys %valid_args) { if($lckey eq lc($goodkey)) { # Found it. Fix it up. $$args{$goodkey} = $$args{$key}; delete($$args{$key}); $fixed = 1; last; } } croak("unknown argument $key") unless $fixed; } } return 1; } =head1 METHODS =head2 sendto(addr, message [, flags]) B is used with connectionless sockets, to send a message to a given address. The addr parameter should be an IO::Socket::TIPC::Sockaddr object. my $addr = IO::Socket::TIPC::Sockaddr->new("{4242, 100}"); $sock->sendto($addr, "Hello there!\n"); The third parameter, I, defaults to 0 when not specified. The TIPC I says: "TIPC supports the I flag when sending; all other flags are ignored." You may have noticed that B and the B builtin do more or less the same thing with the order of arguments changed. The main reason to use B is because you can pass it a IO::Socket::TIPC::Sockaddr object directly, where B requires you to dereference the blessed reference to get at the raw binary "struct sockaddr_tipc" bits. So, B is just a matter of convenience. Ironically, this B method calls the B builtin, which in turn calls the C B function. =cut sub sendto { my ($self, $addr, $message, $flags) = @_; croak "sendto given an undef message" unless defined $message; croak "sendto given a non-address?" unless ref($addr) eq "IO::Socket::TIPC::Sockaddr"; $flags = 0 unless defined $flags; return $self->send($message, $flags, $$addr); } =head2 recvfrom(buffer [, length [, flags]]) B is used with connectionless sockets, to receive a message from a peer. It returns a IO::Socket::TIPC::Sockaddr object, containing the address of whoever sent the message. It will write the received packet (up to $length bytes) in $buffer. my $buffer; my $sender = $sock->recvfrom($buffer, 30); $sock->sendto($sender, "I got your message."); The second parameter, I, defaults to I when not specified. The third parameter, I, defaults to 0 when not specified. The TIPC I says: "TIPC supports the I flag when receiving, as well as the I flag when receiving on a I socket; all other flags are ignored." You may have noticed that B and the B builtin do more or less the same thing with the order of arguments changed. The main reason to use B is because it will return a IO::Socket::TIPC::Sockaddr object, where B just returns a binary blob containing the C "struct sockaddr_tipc" data, which, by itself, cannot be inspected or modified. So, B is just a matter of convenience. Ironically, this B method calls the B builtin, which in turn calls the C B function. =cut sub recvfrom { # note: the $buffer argument is written to by recv(). my ($self, $buffer, $length, $flags) = @_; $flags = 0 unless defined $flags; $length = TIPC_MAX_USER_MSG_SIZE() unless defined $length; croak "how am I supposed to recvfrom() a packet of length 0?" unless $length > 0; my $rv = $self->recv($_[1], $length, $flags); return IO::Socket::TIPC::Sockaddr->new_from_data($rv); } =head2 bind(addr) B attaches a well-known "name" to an otherwise random (and hard to find) socket port. It is possible to bind more than one name to a socket. B is useful for all connectionless sockets, and for "server" sockets (the one you get from B(I => 1), not the ones returned from B). This method is really just a wrapper around the Perl B builtin, which dereferences IO::Socket::TIPC::Sockaddr class instances when necessary. =cut sub bind { my ($sock, $addr) = @_; $addr = $$addr while ref $addr; return $sock->SUPER::bind($addr); } =head2 connect(addr) B seeks out a server socket (which was Bed to a well-known "name") and connects to it. B is only valid for connection-type sockets which have not already had B or B called on them. In practice, you should not ever need this method; B calls it for you when you specify one or more I arguments. This method is really just a wrapper around the Perl B builtin, which dereferences IO::Socket::TIPC::Sockaddr class instances when necessary. =cut sub connect { my ($sock, $addr) = @_; $addr = $$addr while ref $addr; return $sock->SUPER::connect($addr); } =head2 getpeername B returns the sockaddr of the peer you're connected to. Compare B. Use this if you've just B()ed a new connection, and you're curious who you're talking to. my $client = $server->accept(); my $caddr = $client->getpeername(); print("Got connection from ", $caddr->stringify(), "\n"); B doesn't actually return a I sockaddr, it returns an I. Thus, B is an alias for B, to aid readability. I has the following comment: The use of "name" in getpeername() can be confusing, as the routine does not actually return the TIPC names or name sequences that have been bound to the peer socket. This method is really just a wrapper around the Perl B builtin, to wrap return values into IO::Socket::TIPC::Sockaddr class instances for you. =cut sub getpeername { my ($self) = @_; my $rv = CORE::getpeername($self); return $rv unless defined $rv; return IO::Socket::TIPC::Sockaddr->new_from_data($rv); } sub getpeerid { my $self = shift; return $self->getpeername(@_) }; =head2 getsockname B returns the sockaddr of your own socket, this is the address your peer sees you coming from. Compare B. my $client = $server->accept(); my $caddr = $client->getsockname(); print("The client connected to me as ", $caddr->stringify(), "\n"); B doesn't actually return a I sockaddr, it returns an I. Thus, B is an alias for B, to aid readability. I has the following comment: The use of "name" in getsockname() can be confusing, as the routine does not actually return the TIPC names or name sequences that have been bound to the peer socket. This method is really just a wrapper around the Perl B builtin, to wrap return values into IO::Socket::TIPC::Sockaddr class instances for you. =cut sub getsockname { my ($self) = @_; my $rv = CORE::getsockname($self); return $rv unless defined $rv; return IO::Socket::TIPC::Sockaddr->new_from_data($rv); } sub getsockid { my $self = shift; return $self->getsockname(@_) }; =head2 listen B tells the operating system that this is a server socket, and that you will be B()ing client connections on it. It is only valid for connection-type sockets, and only if B has not been called on it. It is much more useful if you have Bed the socket to a well-known name; otherwise, most clients will have difficulty knowing what to B to. This module does not actually implement a B method; when you call it, you are really just calling the Perl builtin. See the perlfunc manpage for more details. =head2 accept B asks the operating system to return a session socket, for communicating with a client which has just Bed to you. It is only valid for connection-type sockets, which you have previously called B on. This module does not actually implement an B method; when you call it, you are really just calling the Perl builtin. See the perlfunc manpage for more details. =head2 getsockopt(level, optname) Query a socket option. For TIPC-level stuff, I should be I. The TIPC I (linked in B) says: - TIPC does not currently support socket options for level SOL_SOCKET, such as SO_SNDBUF. - TIPC does not currently support socket options for level IPPROTO_TCP, such as TCP_MAXSEG. Attempting to get the value of these options on a SOCK_STREAM socket returns the value 0. See B(), below, for a list of I options. This module does not actually implement a B method; when you call it, you are really just calling the Perl builtin. See the perlfunc manpage for more details. =head2 setsockopt(level, optname, optval) Set a socket option. For TIPC-level stuff, I should be I. The TIPC I (linked in B) says: - TIPC does not currently support socket options for level SOL_SOCKET, such as SO_SNDBUF. - TIPC does not currently support socket options for level IPPROTO_TCP, such as TCP_MAXSEG. Attempting to get the value of these options on a SOCK_STREAM socket returns the value 0. For level I, the following options are available: TIPC_IMPORTANCE TIPC_SRC_DROPPABLE TIPC_DEST_DROPPABLE TIPC_CONN_TIMEOUT These are documented in detail in I. See also, ->B()'s I and I options. This module does not actually implement a B method; when you call it, you are really just calling the Perl builtin. See the perlfunc manpage for more details. =head2 detect() B determines whether TIPC is usable on your system. It will return a true value if it detects TIPC support has been loaded into your operating system kernel, and will return 0 otherwise. =cut sub detect { if($^O eq 'linux') { return 1 if `grep -c ^TIPC /proc/net/protocols` == 1; } elsif($^O eq 'solaris') { return 1 if `modinfo -c | grep -w tipc | grep -cv UNLOADED` == 1; } return 0; } =head1 EXAMPLES Examples of connection-based socket use: # SERVER PROCESS # create a server listening on Name {4242, 100}. $sock1 = IO::Socket::TIPC->new( SocketType => "seqpacket", Listen => 1, Local => "{4242, 100}", LocalScope => "zone", ); $client = $sock1->accept(); # Wait for the client to say something intelligent $something_intelligent = $client->getline(); # CLIENT PROCESS # connect to the above server $sock2 = IO::Socket::TIPC->new( SocketType => "seqpacket", Peer => "{4242, 100}", ); # Say something intelligent $sock2->print("Dah, he is Olle, you are Sven.\n"); Examples of connectionless socket use: # NODE 1 # Create a server listening on Name {4242, 101}. $sock1 = IO::Socket::TIPC->new( SocketType => "rdm", Local => "{4242, 101}", LocalScope => "zone", ); $data = "TAG! You are \"it\".\n"; # send a hello packet from sock1 to sock2 $addr2 = IO::Socket::TIPC::Sockaddr->new("{4242, 102}"); $sock1->sendto($addr2, $data); # NODE 2 # Create another server listening on Name {4242, 102}. $sock2 = IO::Socket::TIPC->new( SocketType => "rdm", Local => "{4242, 102}", LocalScope => "zone", ); # receive that first hello packet $sender = $sock2->recvfrom($rxdata, 256); # Reply $sock2->sendto($sender, "Me too.\n"); # send a multicast packet to all sock1s in the world $maddr1 = IO::Socket::TIPC::Sockaddr->new("{4242,101,101}"); $sock2->sendto($maddr2, "My brain hurts!\n"); =head1 EXPORT None by default. =head2 Exportable constants and macros ":tipc" tag (defines from tipc.h, loosely grouped by function): AF_TIPC, PF_TIPC, SOL_TIPC TIPC_ADDR_ID, TIPC_ADDR_MCAST, TIPC_ADDR_NAME, TIPC_ADDR_NAMESEQ TIPC_ZONE_SCOPE, TIPC_CLUSTER_SCOPE, TIPC_NODE_SCOPE TIPC_ERRINFO, TIPC_RETDATA, TIPC_DESTNAME TIPC_IMPORTANCE, TIPC_SRC_DROPPABLE, TIPC_DEST_DROPPABLE, TIPC_CONN_TIMEOUT TIPC_LOW_IMPORTANCE, TIPC_MEDIUM_IMPORTANCE, TIPC_HIGH_IMPORTANCE, TIPC_CRITICAL_IMPORTANCE TIPC_MAX_USER_MSG_SIZE TIPC_OK, TIPC_ERR_NO_NAME, TIPC_ERR_NO_NODE, TIPC_ERR_NO_PORT, TIPC_ERR_OVERLOAD, TIPC_CONN_SHUTDOWN TIPC_PUBLISHED, TIPC_WITHDRAWN, TIPC_SUBSCR_TIMEOUT TIPC_SUB_NO_BIND_EVTS, TIPC_SUB_NO_UNBIND_EVTS, TIPC_SUB_PORTS, TIPC_SUB_SERVICE, TIPC_SUB_SINGLE_EVT TIPC_CFG_SRV, TIPC_TOP_SRV, TIPC_RESERVED_TYPES TIPC_WAIT_FOREVER tipc_addr, tipc_zone, tipc_cluster, tipc_node (Those last 4 are re-exports from the Sockaddr module. See the IO::Socket::TIPC::Sockaddr documentation.) ":sock" tag (re-exports from IO::Socket): SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RDM MSG_DONTWAIT, MSG_PEEK, MSG_WAITALL, MSG_CTRUNC To get all of the above constants, say: use IO::Socket::TIPC ":all"; To get all of the tipc stuff, say: use IO::Socket::TIPC ":tipc"; To get only the socket stuff, say: use IO::Socket::TIPC ":sock"; To get only the constants you plan to use, say something like: use IO::Socket::TIPC qw(SOCK_RDM TIPC_NODE_SCOPE TIPC_ADDR_NAMESEQ); Despite supporting all the above constants, please note that some effort was made so normal users will never actually need any of them. For instance, in place of the I* socktypes, you can just specify "stream", "dgram", "seqpacket" or "rdm". In place of the I*I<_SCOPE> defines, given to IO::Socket::TIPC::Sockaddr->B() as the I parameter, you can simply say I<"zone">, I<"cluster"> or I<"node">. =cut use XSLoader; XSLoader::load('IO::Socket::TIPC', $VERSION); IO::Socket::TIPC->register_domain(PF_TIPC()); my @TIPC_STUFF = ( qw( AF_TIPC PF_TIPC SOL_TIPC TIPC_ADDR_ID TIPC_ADDR_MCAST TIPC_ADDR_NAME TIPC_ADDR_NAMESEQ TIPC_CFG_SRV TIPC_CLUSTER_SCOPE TIPC_CONN_SHUTDOWN TIPC_CONN_TIMEOUT TIPC_CRITICAL_IMPORTANCE TIPC_DESTNAME TIPC_DEST_DROPPABLE TIPC_ERRINFO TIPC_ERR_NO_NAME TIPC_ERR_NO_NODE TIPC_ERR_NO_PORT TIPC_ERR_OVERLOAD TIPC_HIGH_IMPORTANCE TIPC_IMPORTANCE TIPC_LOW_IMPORTANCE TIPC_MAX_USER_MSG_SIZE TIPC_MEDIUM_IMPORTANCE TIPC_NODE_SCOPE TIPC_OK TIPC_PUBLISHED TIPC_RESERVED_TYPES TIPC_RETDATA TIPC_SRC_DROPPABLE TIPC_SUBSCR_TIMEOUT TIPC_SUB_NO_BIND_EVTS TIPC_SUB_NO_UNBIND_EVTS TIPC_SUB_PORTS TIPC_SUB_SERVICE TIPC_SUB_SINGLE_EVT TIPC_TOP_SRV TIPC_WAIT_FOREVER TIPC_WITHDRAWN TIPC_ZONE_SCOPE tipc_addr tipc_zone tipc_cluster tipc_node ) ); my @SOCK_STUFF = ( qw( SOCK_STREAM SOCK_DGRAM SOCK_SEQPACKET SOCK_RDM MSG_DONTWAIT MSG_PEEK MSG_WAITALL MSG_CTRUNC ) ); our @EXPORT = qw(); our @EXPORT_OK = qw(); our %EXPORT_TAGS = ( 'all' => [ @TIPC_STUFF, @SOCK_STUFF ], 'tipc' => [ @TIPC_STUFF ], 'sock' => [ @SOCK_STUFF ], ); Exporter::export_ok_tags('all'); 1; __END__ =head1 BUGS Probably many. Please report any bugs you find to the author. A TODO file exists, which lists known unimplemented and broken stuff. =head1 REFERENCES See also: IO::Socket, IO::Socket::TIPC::Sockaddr, http://tipc.sf.net/. The I is particularly helpful, and is available off the SourceForge site. See http://tipc.sf.net/doc/Programmers_Guide.txt, or http://tipc.sf.net/documentation.html. =head1 AUTHOR Mark Glines =head1 ACKNOWLEDGEMENTS Thanks to Ericcson and Wind River, of course, for open-sourcing their (very useful!) network code, and performing the enormous maintenance task of getting it into the stock Linux kernel. Respect. More specifically, thanks to the TIPC maintainers, for doing all the work bringing TIPC to linux, and making all of this possible. And thanks especially to Allan Stephens for patiently testing all of my pathetic, bug-ridden alpha releases. :) Thanks to Renaud Metrich and Sun Microsystems for bringing TIPC to Solaris, and testing even more of my pathetic, bug-ridden patches. =head1 COPYRIGHT AND LICENSE This module is licensed under a dual BSD/GPL license, the same terms as TIPC itself. =cut