in reply to Re: Using Threads For Simple SMTP Relay Server
in thread Using Threads For Simple SMTP Relay Server

It looks like ActiveState perl strikes again. Before I start reading from the IO::Socket in the Client3 I can write to it. Once I start reading from it in Client3::get_message() what I write to the socket is never sent to the other side of the socket. I guess it is blocking.
  • Comment on Re^2: Using Threads For Simple SMTP Relay Server

Replies are listed 'Best First'.
Re^3: Using Threads For Simple SMTP Relay Server
by BrowserUk (Patriarch) on Oct 29, 2007 at 18:55 UTC
    It looks like ActiveState perl strikes again.

    That strange, because I'm using ActiveState Perl also.

    I guess it is blocking.

    Sockets are blocking by default and nothing in the code you posted attempts to set them as non-blocking.

    Before I start reading from the IO::Socket in the Client3 I can write to it. Once I start reading from it in Client3::get_message() what I write to the socket is never sent to the other side of the socket.

    You cannot simultaneously read and write to a blocking socket. It's like using a walky-talky. There is no point in talking whilst the person on the other end is transmiting, because they will not hear you.

    But I don't see the need for a non-blocking socket for implementing the SMTP protocol?

    It is a command-response protocol. Your client3 code should work in the same way as the Client2 code. Wait for a command, and once you get one, make no more reads until it has finshed sending the complete response to that command.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      I used a poor choice of words. Blocking isn't right. What I meant to say was that once I start reading from the socket (one line at a time) I can't write to it and have it received. I have switched back to the Client2 code. The offending portion is in get_message(). If I run with the following code:

      # sub process { sub get_message { my $self = shift; my($cmd, @args); my $sock = $self->{SOCK}; $self->_reset0; print $sock "STUFF1\r\n"; while(<$sock>) { print $sock "STUFF2\r\n"; print "$$ command: $_"; ... rest of routine

      The output (from the perspective of the client is:

      Net::SMTP>>> Net::SMTP(2.31) Net::SMTP>>> Net::Cmd(2.29) Net::SMTP>>> Exporter(5.60) Net::SMTP>>> IO::Socket::INET(1.31) Net::SMTP>>> IO::Socket(1.30) Net::SMTP>>> IO::Handle(1.27) Net::SMTP=GLOB(0x20f758c)<<< 220 Debatable SMTP 0.2 Ready. Net::SMTP=GLOB(0x20f758c)>>> EHLO localhost.localdomain Net::SMTP=GLOB(0x20f758c)<<< STUFF1

      In other words it only writes to the client the first time (it never sends STUFF2 to the client). Once the socket is read from writes aren't happening in other words. It is perplexing.

        Looking at Net::SMTP::Server::Client2, subclassing it is non-trivial because it uses a lexical dispatch table to invoke most of the methods you would want to override. As the dispatch table is built at compile time, overriding those methods in the usual way doesn't work.

        Here's one way to do it, though I shall post a SoPW to see if anyone knows a better way.

        The first thing you need is access to the dispatch table from within the subclass. To facilitate that, I added a class method to Client2.pm:

        sub get_dispatch_table { \%_cmds; }

        Then a simple subclass that overrides the HELO and HELP commands looks like this:

        package Net::SMTP::Server::Client3; use base qw[ Net::SMTP::Server::Client2 ]; sub _hello { shift->okay( "Yeah! You want summat?" ); } sub _help { my $self = shift; $self->okay( "Jeez! There's only 10 commands of 4 letters each. Try to keep + up!" ); $self->SUPER::_help; } my $dispatch = Net::SMTP::Server::Client2::get_dispatch_table; $dispatch->{ HELO } = \&_hello; $dispatch->{ HELP } = \&_help; 1;

        Not hugely elegant, but it requires the minimum change to Client2.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        In other words it only writes to the client the first time (it never sends STUFF2 to the client). Once the socket is read from writes aren't happening in other words. It is perplexing.

        It's not perplexing at all. Your modifications are breaking the well defined SMTP protocol. You should not be modifying the get_message() method. And you certainly should not be writing to the socket within the read loop. The get_message() method is intended to, and *must*, be called as an atomic operation in order that the rules governing the protocol will be maintained.

        What is happening is that you are writing to the socket at inappropriate points within the protocol exchange. The client, which is expecting the server to follow those rules, is being sent data when it is not expecting it. That breaks the protocol and the whole exchange gets screwed up.

        In order to make the changes you require, you should be leaving the Client2 get_message() method untouched and only modify the command handlers. You could do this by C&Ping the whole module and then modifying the individial command handler subroutines, ie. those subs whose addresses are stored in the dispatch table:

        my %_cmds = ( DATA => \&_data, EXPN => \&_noway, HELO => \&_hello, HELP => \&_help, MAIL => \&_mail, NOOP => \&_noop, QUIT => \&_quit, RCPT => \&_receipt, RSET => \&_reset, VRFY => \&_noway );

        Alternatively, you could write your Client3 module as a subclass of the Client2 code and override (just) those routines you need to modify, through inheritance.

        Either way, the keys to that dispatch table are the only commands that the SMTP protocol understands and its values are the subroutines that will be invoked when a properly conformant command message is received from the client. And it is those routines that you should be modifying or overiding.

        Obviously, there are some changes to the get_message() method that might be appropriate, like sending log messages to a system log handler rather than the console, but even that could be achieved through redirecting STDOUT in a subclass. In general, you should leave the existing read loop untouched.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.