I was writing a Perl-based proxy for line-based SMTP protocol. The main reason doing this is because I receive unwanted e-mail bounces that are not filtered out by my SpamAssassin. The idea was to hook into the mail delivery chain and to collect e-mail addresses that I use. The proxy can later then filter out any bounce message that was not originated by myself.
I decided to use the Net::Daemon module which has a quite fancy interface. One just writes a single function which handles the client connection. As I didn’t want to learn every detail of SMTP protocol, I simply use non-blocking sockets. So whoever of the two parties wants to talk, it can do so and my proxy will just listen to the chat. The IO::Select documentation tells you to do this when you have multiple sockets to react on:
1 2 3 4 5 6 7 8 9 10 11 12 | # Prepare selecting $select = new IO::Select(); $select->add($socket1); $select->add($socket2); # Run until timed out / select socket while (@CANREAD = $select->can_read(30)) { foreach $s (@CANREAD) { #... read the socket and do your stuff } } # Whenever there is a problem (like talk ended) the loop exits here |
However, this code doesn’t work as expected. The can_read()
function will not return an empty list when the sockets closed. It still returns any socket and the loop goes on forever. In fact, as we are in non-blocking mode, the script now eats up CPU time.
There are two solutions to it. The first is to check whether the given socket is still connected and then exit the loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # Prepare selecting $select = new IO::Select(); $select->add($socket1); $select->add($socket2); # Run until timed out / select socket while (@CANREAD = $select->can_read(30)) { foreach $s (@CANREAD) { if (!$s->connected()) { return; } #... It's safe now to read the socket and do your stuff } } |
The second and more clean method is just to remove the closed socket from the IO::Select object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # Prepare selecting $select = new IO::Select(); $select->add($socket1); $select->add($socket2); # Run until timed out / select socket while (@CANREAD = $select->can_read(30)) { foreach $s (@CANREAD) { if (!$s->connected()) { $select->remove($s); next; } #... It's safe now to read the socket and do your stuff } } |
Then the selector runs empty and will exit the loop as well.