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.