macdee_2 has asked for the wisdom of the Perl Monks concerning the following question:
I've been using the code from Recipe 17.13 in the Perl Cookbook (Non-Forking Servers) in one of my own applications.
This uses Non-blocking I/O to read and write sockets. I've included the code I believe to be incorrect below. Note: I've taken this code from IO::NonBlocking on CPAN, to ensure it's freely available. IO::NonBlocking is heavily based on the cookbook recipe.
My problem is that if you try to write a large amount of data at once to a socket, the code will incorrectly disconnect the client. On examination of $! and $rv, it would appear that send() managed to send some, but not all, of the data (ie $rv < length $outbuffer{$client}), but $! is not set. This is contrary to what the code below expects - it would seem to expect that if not all of the data was sent, then $! would be set to EWOULDBLOCK, and $rv would be set to the amount sent.
However, $! is closely tied to the errno() system call - ie it's an error. If you examind the man page for send(2), it suggests that errno() is only set if it's an Error condition - ie send() call returns -1. This equates to $rv being undefined in Perl code.
I infer that a partial send is not an error condition, and will not set errno() at all. I've checked this to the best of my ability using the Linux send(2) man page, and Unix Network Programming (1st edition was all I can lay my hands on).
Below here, I have posted my new code. This separates a successful send (partial or not), from an EWOULDBLOCK condition, and any other error conditions.# Buffers to flush? foreach $client ($select->can_write(1)) { # Skip this client if we have nothing to say next unless exists $outbuffer{$client}; eval{ $rv = $client->send($outbuffer{$client}, 0); }; push(@bad_client,$client),next if $@; unless (defined $rv) { # Whine, but move on. warn "I was told I could write, but I can't.\n"; next; } if ( $rv == length $outbuffer{$client} || $! == POSIX::EWOULDBLOCK) { substr($outbuffer{$client}, 0, $rv) = ''; delete $outbuffer{$client} unless length $outbuffer{$client}; } else { # Couldn't write all the data, and it wasn't because # it would have blocked. Shutdown and move on. $self->disconnect_client($client); next; } }
# Buffers to flush? foreach $client ($select->can_write(1)) { # Skip this client if we have nothing to say next unless exists $outbuffer{$client}; $rv = send($client, $outbuffer{$client}, 0); if ($! == POSIX::EWOULDBLOCK) { #caught a EWOULDBLOCK. Ignore. } elsif (! defined $rv) { # Whine, but move on. warn "I was told I could write, but I can't : $!\n"; next; } elsif ($rv) { substr($outbuffer{$client}, 0, $rv) = ''; warn "successfully wrote $rv bytes\n" if length $outbuffer{$client}; delete $outbuffer{$client} unless length $outbuffer{$client}; } else { # Couldn't write all the data, and it wasn't because # it would have blocked. Shutdown and move on. warn "Failed to write all data, \$rv was $rv, \$! was ".($!+0).", +length was ".(length $outbuffer{$client})."\n"; $self->disconnect_client($client); next; } }
I'd be obliged if any Monks more knowledgable in these matters than me could comment on my findings, and if possible test them. For a successful *partial* send to occur, I've discovered that you need to send about a 13 kilobyte string over an Ethernet connection (not localhost loopback). I've also been using a string with no new lines in it, but that's probably not a requirement.
Many thanks
Macdee
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Problem with non-blocking sockets example from Cookbook - send() vs EWOULDBLOCK
by Anonymous Monk on Jan 18, 2004 at 15:32 UTC | |
by macdee_2 (Initiate) on Jan 18, 2004 at 20:31 UTC | |
|
Re: Problem with non-blocking sockets example from Cookbook - send() vs EWOULDBLOCK
by gellyfish (Monsignor) on Jan 19, 2004 at 13:32 UTC | |
by macdee_2 (Initiate) on Jan 21, 2004 at 01:04 UTC |