Results 1 to 14 of 14

Thread: [RESOLVED] Make TCPListener wait for full command

  1. #1

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Resolved [RESOLVED] Make TCPListener wait for full command

    OK I am probably going to completely fail but I thought I would just try making an SMTP server (which came from another thread on here I was posting in earlier). At the moment I am just concentrating on RECEIVING emails via SMTP, I dont care about sending them yet.

    So the first thing I want to do is just make my server program be able to receive a simple HELO command from Telnet on port 25. Simple enough eh? Apparently not

    The problem I have is that my program starts reading the data as soon as 1 character is typed into telnet, rather than it waiting for the telnet user to press Enter before it starts to process the command. I've had a look around and looked at Athiest's examples but cant figure out how to make it wait. Here is what I have so far:

    vb.net Code:
    1. Private Sub StartBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartBtn.Click
    2.         Dim bgthread As New Threading.Thread(AddressOf StartServer)
    3.         bgthread.IsBackground = True
    4.         bgthread.Start()
    5.     End Sub
    6.  
    7.     ''' <summary>
    8.     ''' Starts listening on port 25
    9.     ''' </summary>
    10.     Private Sub StartServer()
    11.         Dim TListener As New TcpListener(Net.IPAddress.Any, 25)
    12.         TListener.Start()
    13.         Dim TClient As TcpClient
    14.  
    15.         Do
    16.             TClient = TListener.AcceptTcpClient()
    17.             Invoke(New Action(Of String)(AddressOf WriteToLog), TClient.Client.RemoteEndPoint.ToString & " Connected")
    18.             WriteToClientStream(TClient.GetStream, "Hello from SMTP Server!")
    19.  
    20.             '<This is where I am struggling>
    21.             Dim DataAsBytes(1024) As Byte
    22.             TClient.GetStream.Read(DataAsBytes, 0, 1024) '<-- This line executes as soon as one character is typed in telnet
    23.             Dim DataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes)
    24.             Invoke(New Action(Of String)(AddressOf WriteToLog), DataAsString)
    25.             '</Struggle>
    26.         Loop
    27.  
    28.     End Sub
    29.  
    30.     ''' <summary>
    31.     ''' Writes a message back to the client's stream.
    32.     ''' If the client is a telnet client then this message will appear in the telnet console
    33.     ''' </summary>
    34.     ''' <param name="stream">The underlying stream to write to</param>
    35.     ''' <param name="msg">The string to write to the stream</param>
    36.     Private Sub WriteToClientStream(ByVal stream As IO.Stream, ByVal msg As String)
    37.         Dim Writer As New IO.StreamWriter(stream)
    38.         Writer.WriteLine(msg)
    39.         Writer.Flush()
    40.     End Sub
    41.  
    42.     ''' <summary>
    43.     ''' Writes to the log file (currently just an RTB on screen)
    44.     ''' </summary>
    45.     ''' <param name="msg">The string to write to the log</param>
    46.     Private Sub WriteToLog(ByVal msg As String)
    47.         logbox.AppendText(msg & vbNewLine)
    48.     End Sub

    See the comments in the StartServer sub for info on where exactly the problem is.

    Any ideas?

    Thanks
    Chris
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  2. #2
    PowerPoster 2.0 Negative0's Avatar
    Join Date
    Jun 2000
    Location
    Southeastern MI
    Posts
    4,367

    Re: Make TCPListener wait for full command

    In your loop you are waiting for a new connection each time. Do something like this:
    Code:
        Private Sub StartServer()
            Dim TListener As New TcpListener(Net.IPAddress.Any, 25)
            TListener.Start()
            Dim TClient As TcpClient
    
    
            TClient = TListener.AcceptTcpClient()
            Invoke(New Action(Of String)(AddressOf WriteToLog), TClient.Client.RemoteEndPoint.ToString & " Connected")
            WriteToClientStream(TClient.GetStream, "Hello from SMTP Server!")
    
            Do
                '<This is where I am struggling>
                Dim DataAsBytes(1024) As Byte
                TClient.GetStream.Read(DataAsBytes, 0, 1024) '<-- This line executes as soon as one character is typed in telnet
                Dim DataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes)
                Invoke(New Action(Of String)(AddressOf WriteToLog), DataAsString)
                '</Struggle>
            Loop
    
        End Sub

  3. #3

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    OK I'll give that a go thanks, but I do need the original loop as well because once one client has finished its session I need it to go back to waiting for another client to connect.
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  4. #4
    PowerPoster 2.0 Negative0's Avatar
    Join Date
    Jun 2000
    Location
    Southeastern MI
    Posts
    4,367

    Re: Make TCPListener wait for full command

    Probably what you want to do is have one thread that has a loop that is just listening for clients. When a client connects you launch a new thread to handle the connection for that client. This way you can handle an arbitrary number of connections coming in. If you only had a single loop, you could not accept new connections while another server was connected. If they were sending a large file via email, that could prevent other servers from connecting for a significant period of time.

  5. #5

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    OK that makes it read every character thanks It also makes me understand why when you send something to an SMTP server via telnet, if you make a typo and delete it then retype it then the command doesnt work - because it has already stored those values you typed before.
    Anywho, next problem is getting it to recognise that the user pressed the Enter key. I thought this would just be like:
    vb Code:
    1. Dim DataAsBytes(255) As Byte
    2.                 TClient.GetStream.Read(DataAsBytes, 0, 255)
    3.                 Dim ShortDataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes)
    4.  
    5.                 If ShortDataAsString = vbCrLf Then
    6.                     Debug.WriteLine("end of command")
    7.                     Exit Do
    8.                 End If
    but that IF statement always evaluates false. I tried vbCr and vbNewLine but nothing. When I step through the code and look at the value of ShortDataAsString it just shows a single speech mark ( " not ' ) and I have no idea how to match this to something I can produce in code... I'll keep working on it but if anyone has any suggestions that would be good
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  6. #6

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    Quote Originally Posted by Negative0 View Post
    Probably what you want to do is have one thread that has a loop that is just listening for clients. When a client connects you launch a new thread to handle the connection for that client. This way you can handle an arbitrary number of connections coming in. If you only had a single loop, you could not accept new connections while another server was connected. If they were sending a large file via email, that could prevent other servers from connecting for a significant period of time.
    yeah I'm going to do that when I get it all working but for now I just want to have one thread to keep things simple
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  7. #7

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    OK I figured I can just do this (and it works) but can someone just confirm this is a reliable way of checking for the Enter key being pressed and sent to my server?

    vb Code:
    1. Dim DataAsBytes(8) As Byte
    2. TClient.GetStream.Read(DataAsBytes, 0, 8)
    3. If DataAsBytes(0) = 13 AndAlso DataAsBytes(1) = 10 Then
    4.          Debug.WriteLine("end of command")
    5.          Exit Do
    6. End If
    I dont know much about Byte arrays / characters so I dont know if its possible for some other character (or character combination) to possibly have 13 as the first byte and 10 as the second byte as well...
    Also, I assume I can have a really small byte array like the 8 byte long array I have created in the above code because it is only one character being passed to the server at a time?

    EDIT: apoligies for the tripple post! and for starting pretty much every post with "OK"
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  8. #8
    PowerPoster 2.0 Negative0's Avatar
    Join Date
    Jun 2000
    Location
    Southeastern MI
    Posts
    4,367

    Re: Make TCPListener wait for full command

    You are probably better off getting the size from the Read method and then only reading those characters in:

    Code:
    size = TClient.GetStream.Read(DataAsBytes, 0, 20) '<-- This line executes as soon as one character is typed in telnet
                Dim DataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes, 0, size)
    If you don't specify the size, then it will convert the non read bytes to null strings, and your string would always be 1024 characters. The code above trims it down just to the size of the data read.

  9. #9
    Fanatic Member TokersBall_CDXX's Avatar
    Join Date
    Mar 2003
    Location
    America
    Posts
    571

    Re: Make TCPListener wait for full command

    Code:
    An SMTP data (or d8ta) line is a sequence of 0 to 1000 octets,
    including the sequence 0x0C (CR) 0x0A (LF) which ends the line.  A CR
    without a following LF, or an LF without a preceeding CR, is not a
    line delimiter, and must not be interpreted as one, regardless of the
    local system's line delimiter convention.
    
    SMTP clients MUST NOT transmit lines of length greater than 1000
    octets.  It is RECOMMENDED that SMTP server implementations impose no
    limit on the maximum number of octets in a line.
    Build your own personalized flash based chat room for your webpage for FREE! http://www.4computerheaven.com

  10. #10

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    That is the DATA command it is talking about. I havent got that far yet. DATA is the command you type after you have used RCPT TO, FROM and SUBJECT to specify the sender, recipient and subject of your email to the SMTP server - you type DATA and then type your email message, followed by two CrLF's to signal that you have finished typing the email body.

    Its going quite well so far though, clients can connect, send the HELO message and the server will respond, or they can type QUIT and it will drop their connection

    Current code for any interested parties (havent finished commenting it yet but oh well) :

    vb.net Code:
    1. Private Sub StartBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartBtn.Click
    2.         Dim bgthread As New Threading.Thread(AddressOf StartServer)
    3.         bgthread.IsBackground = True
    4.         bgthread.Start() 'Start new background thread that runs the StartServer Sub
    5.  
    6.         StartBtn.Enabled = False
    7.         Stopbtn.Enabled = True
    8.     End Sub
    9.  
    10.     ''' <summary>
    11.     ''' Starts listening on port 25 and passes incoming requests to a ThreadPool thread for processing
    12.     ''' </summary>
    13.     Private Sub StartServer()
    14.         Threading.ThreadPool.SetMaxThreads(40, 300) 'Set the maximum amount of threads the ThreadPool can create for us simultaneously
    15.         Dim TListener As New TcpListener(Net.IPAddress.Any, 25) 'Create the listener that will accept connections on port 25
    16.         TListener.Start() 'Start the listener
    17.  
    18.         'When a new client connection is accepted, start the StartSession Sub on a new ThreadPool thread
    19.         'and then loop back to get ready for new client to connect
    20.         Do
    21.             Threading.ThreadPool.QueueUserWorkItem(AddressOf StartSession, TListener.AcceptTcpClient)
    22.         Loop
    23.     End Sub
    24.  
    25.     ''' <summary>
    26.     ''' Handles the session and command processing for each connected client
    27.     ''' </summary>
    28.     ''' <param name="AcceptedTcpClient">The TcpClient object that represents the connected client</param>
    29.     Private Sub StartSession(ByVal AcceptedTcpClient As Object)
    30.         Dim TClient As TcpClient = DirectCast(AcceptedTcpClient, TcpClient)
    31.         Dim RemoteIP As String = TClient.Client.RemoteEndPoint.ToString
    32.         WriteToClientStream(TClient.GetStream, Now.ToShortTimeString & " - Prototype SMTP Server")
    33.         BG_WriteToLog(RemoteIP & " connected at " & Now.ToString)
    34.         Dim strbuild As New StringBuilder
    35.         Dim ExitSession As Boolean = False
    36.         Dim CommandEntered As String = String.Empty
    37.         Do While ExitSession = False
    38.             Do
    39.                 Dim size As Int32 = 0
    40.                 Dim DataAsBytes(10) As Byte
    41.                 size = TClient.GetStream.Read(DataAsBytes, 0, 10)
    42.                 If DataAsBytes(0) = 13 AndAlso DataAsBytes(1) = 10 Then
    43.                     Exit Do
    44.                 End If
    45.                 Dim ShortDataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes, 0, size)
    46.                 strbuild.Append(ShortDataAsString)
    47.             Loop 'Start listening for new data on the stream again
    48.             CommandEntered = strbuild.ToString
    49.             BG_WriteToLog(RemoteIP & " : " & CommandEntered)
    50.             If String.Compare(CommandEntered, "HELO", True) = 0 Then
    51.                 WriteToClientStream(TClient.GetStream, "HELO From " & TClient.Client.LocalEndPoint.ToString)
    52.             ElseIf String.Compare(CommandEntered, "QUIT", True) = 0 Then
    53.                 ExitSession = True
    54.                 TClient.Close()
    55.             Else
    56.                 WriteToClientStream(TClient.GetStream, "Unknown Command: " & CommandEntered)
    57.             End If
    58.             strbuild.Remove(0, strbuild.Length) 'Clear out the command string ready for next command
    59.         Loop 'Start processing commands that are sent from the client again
    60.     End Sub
    61.  
    62.     ''' <summary>
    63.     ''' Writes a message back to the client's stream.
    64.     ''' If the client is a telnet client then this message will appear in the telnet console
    65.     ''' </summary>
    66.     ''' <param name="stream">The underlying stream to write to</param>
    67.     ''' <param name="msg">The string to write to the stream</param>
    68.     Private Sub WriteToClientStream(ByVal stream As IO.Stream, ByVal msg As String)
    69.         Dim Writer As New IO.StreamWriter(stream)
    70.         Writer.WriteLine(msg)
    71.         Writer.Flush()
    72.     End Sub
    73.  
    74.     ''' <summary>
    75.     ''' Writes to the log file and to the textbox on the screen.
    76.     ''' Intended to be executed on the UI thread
    77.     ''' </summary>
    78.     ''' <param name="msg">The string to write to the log</param>
    79.     Private Sub UI_WriteToLog(ByVal msg As String)
    80.         logbox.AppendText(msg & vbNewLine)
    81.     End Sub
    82.  
    83.     ''' <summary>
    84.     ''' Invokes a delegate to execute the UI_WriteToLog Sub.
    85.     ''' Intended to be executed from a background thread.
    86.     ''' </summary>
    87.     ''' <param name="msg">The string to write to the log</param>
    88.     Private Sub BG_WriteToLog(ByVal msg As String)
    89.         Me.Invoke(New Action(Of String)(AddressOf UI_WriteToLog), msg)
    90.     End Sub
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  11. #11
    Fanatic Member TokersBall_CDXX's Avatar
    Join Date
    Mar 2003
    Location
    America
    Posts
    571

    Re: Make TCPListener wait for full command

    correct,

    was a snippet of the standard labeled 'smtp line delimiter'.
    each line should ended in this fashion.
    Build your own personalized flash based chat room for your webpage for FREE! http://www.4computerheaven.com

  12. #12

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Make TCPListener wait for full command

    Yes but thats only the DATA command part... Thats nothing to do with what I was talking about earlier with not being able to detect the user pressing Enter after each command (which I assume is why you posted it, after all you didnt really give any explanation or anything as to what it was relating to..)
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  13. #13
    Fanatic Member TokersBall_CDXX's Avatar
    Join Date
    Mar 2003
    Location
    America
    Posts
    571

    Re: Make TCPListener wait for full command

    Am I incorrect in understanding the RFC indicating \r\n for every line including HELO, this would coincidentally be the enter key you were searching for earlier?
    Build your own personalized flash based chat room for your webpage for FREE! http://www.4computerheaven.com

  14. #14

    Thread Starter
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: [RESOLVED] Make TCPListener wait for full command

    Marking this as resolved as the original question was resolved by Negative0 - Thanks!
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width