Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Languages / C# / January 2008

Tip: Looking for answers? Try searching our database.

Telnet: Grabbing Bytes Previously Written During Next Read?

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
pbd22 - 16 Jan 2008 17:57 GMT
Hi.

I am building a custom telnet interface and my problem is that I
want to read the user input along with the previously written stream.

Right now I am logging the user.

I have

Login: Bill

Login is written by:

buffer = ASCII.GetBytes("Login: ");
_clientStream.Write(buffer, 0, buffer.Length);

Bill is entered by the user.

At the next return, I want to read:

"Login: Bill".

This is how I know its a Login read
and not a Password: read.

I am reading input with the following loop:

Code:

           while (true)
           {

               _bytesRead = 0;

               try
               {
                   //blocks until a client sends a message
                   _bytesRead = _clientStream.Read(message,
0,4096); //message

               }
               catch
               {
                   //a socket error has occured
                   break;
               }

               if (_bytesRead == 0)
               {
                   break;
               }

               statusMessage += ASCII.GetString(message, 0,
_bytesRead);

But, my problem is that I get:

"Bill".

when the user submits his string.

How do I get both the written "Login: " and
the read "Bill" on the same read?

Please let me know if i need to provide more information,
otherwsie, thanks a lot for your response!
Peter Duniho - 16 Jan 2008 18:13 GMT
> [...]
> At the next return, I want to read:
[quoted text clipped - 13 lines]
> How do I get both the written "Login: " and
> the read "Bill" on the same read?

Maybe it's been too long and I'm misremembering how the telnet protocol  
works, but my recollection is that the telnet client is not supposed to  
echo the data you send it.

In other words, there will never be a "Login: " for your telnet server to  
read.  You need to track the state of the connection explicitly, by  
knowing what you've sent to the client, as it relates to what input you've  
received back.

For example: assuming you are sure you've processed all user input up to  
this point (for a login, this should be trivial :) ), then when you send a  
login prompt, you need to set some local state so that you know the next  
thing you should receive from the user is the login ID.

Once you have successfully processed the user's login ID response, then  
you would send the password prompt and set some local state so that you  
know the next thing you should receive from the user is a login password.

The "state" could as simple as an enum that describes the various stages  
of a client connection:

    enum ClientState
    {
        LoginIDPrompt,
        LoginPasswordPrompt,
        Connected
    }

and then a switch() statement where you process user input that uses a  
variable associated with the client assigned to a value from the enum to  
decide what to do with the input:

    // at this point, having read an entire line of client
    // input and stored it in a variable (say, "strUserInput"
    // for the sake of discussion):
    switch (clientData.ClientState)
    {
    case ClientState.LoginIDPrompt:
        strLoginID = strUserInput;
        clientData.ClientState = ClientState.LoginPasswordPrompt;
        break;
    case ClientState.LoginPasswordPrompt;
        if (CheckPassword(strLoginID, strUserInput))
        {
            clientData.ClientState = ClientState.Connected;
        }
        else
        {
            SendLoginPrompt();
            clientData.ClientState = ClientState.LoginIDPrompt;
        }
        break;
    case ClientState.Connected:
        // handle regular user input here
        break;
    }

That's just the basic idea.  There are lots of ways to actually implement  
it.

Pete
pbd22 - 16 Jan 2008 18:43 GMT
Thanks Pete.

I am making my way though your code.
Could you explain what the "clientData" of "clientData.ClientState"
is?

Sorry - not too familiar with enums.

Thanks,
Peter
pbd22 - 16 Jan 2008 18:43 GMT
Thanks Pete.

I am making my way though your code.
Could you explain what the "clientData" of "clientData.ClientState"
is?

Sorry - not too familiar with enums.

Thanks,
Peter
pbd22 - 16 Jan 2008 18:43 GMT
Thanks Pete.

I am making my way though your code.
Could you explain what the "clientData" of "clientData.ClientState"
is?

Sorry - not too familiar with enums.

Thanks,
Peter
Peter Duniho - 16 Jan 2008 19:24 GMT
> Thanks Pete.
>
> I am making my way though your code.
> Could you explain what the "clientData" of "clientData.ClientState"
> is?

"clientData" is just a variable name I made up.  You should have in your  
own code some specific variable that contains state information about each  
client connection (such as the _clientStream variable).  You may in fact  
put code similar to what I posted _inside_ the class that contains this  
state (the fact that the _clientStream variable is unqualified suggests  
you may already be doing this), in which case you wouldn't need the  
"clientData" part at all.  You'd just refer to the "ClientState" member  
directly (or whatever you choose to call it).

> Sorry - not too familiar with enums.

I apologize for any confusion.  The "clientData" doesn't really have  
anything to do with the enum aspect.  The "ClientState" is simply a member  
of a data structure (probably class, but could be a struct instead) that  
has the type ClientState, and "clientData" would simply be a reference to  
an instance of that data structure.

For that matter, don't get too hung up on the enum.  I used an enum  
because it's a convenient, readable way to describe simple state like  
this, but you can manage the state however you like.

Pete
pbd22 - 16 Jan 2008 19:36 GMT
Yes, I think I am already handling state information at the head of my
TheConnectionHandler
with the following lines of code:

           _telnetSocket = (TcpClient)connectionQueue.Dequeue();
           _clientStream = _telnetSocket.GetStream();

Here is the core of the handler and the Login method per your
suggestion. Does this look right?

Thanks again.

      public void TheConnectionHandler()
       {

           _telnetSocket = (TcpClient)connectionQueue.Dequeue();
           _clientStream = _telnetSocket.GetStream();

           //if (IsLoggedIn == false)
           //{
           //    buffer = ASCII.GetBytes("Login: ");
           //    _clientStream.Write(buffer, 0, buffer.Length);
           //}

           while (true)
           {

               _bytesRead = 0;

               try
               {
                   //blocks until a client sends a message
                   _bytesRead = _clientStream.Read(message,
0,4096); //message

               }
               catch
               {
                   //a socket error has occured
                   break;
               }

               if (_bytesRead == 0)
               {
                   break;
               }

               statusMessage += ASCII.GetString(message, 0,
_bytesRead);

               if (statusMessage.LastIndexOf(ENDOFLINE) > 0)
               {

                       buffer = ASCII.GetBytes(Login(statusMessage));

               }

           }

           _telnetSocket.Close();

       }

       protected string Login(string clientInput)
       {

            switch (ClientState)
            {
                case ClientState.LoginIDPrompt:
                   strLoginID = clientInput;
                   clientData.ClientState =
ClientState.LoginPasswordPrompt;
                   statusMessage = "";
                   break;
                case ClientState.LoginPasswordPrompt:
                   if (CheckPassword(strLoginID, clientInput))
                   {
                       clientData.ClientState =
ClientState.Connected;
                   }
                   else
                   {
                       SendLoginPrompt();
                       clientData.ClientState =
ClientState.LoginIDPrompt;
                   }
                   break;
                case ClientState.Connected:
                   // handle regular user input here
                   break;
            }
Peter Duniho - 16 Jan 2008 19:47 GMT
> Yes, I think I am already handling state information at the head of my
> TheConnectionHandler
[quoted text clipped - 5 lines]
> Here is the core of the handler and the Login method per your
> suggestion. Does this look right?

Mostly.  Some suggestions:

    * Don't have the Login() method manage the "statusMessage" variable.  
Your "TheConnectionHandler()" method is a more appropriate place to do  
that, as it's what is manipulating it otherwise.

    * You should use the index of the EOL for your user input to create a  
substring that's passed to the Login() method, to ensure that that method  
gets _only_ the one line of user input.  TCP being what it is, it's  
possible to receive additional data after the EOL as part of the same  
chunk of read data.  In the user login scenario hopefully the chances are  
less (presuambly it'd require the client to type ahead, entering both the  
login ID and the password all at once, before any of that data actually  
gets sent...not impossible, but probably unlikely).  But you shouldn't  
leave something like that to chance.

    * And combining the two above, obviously this means that you shouldn't  
just clear the "statusMessage" variable to an empty string.  You should  
extract the data you're ready to process, and reassign the remaining data  
to the variable (so basically two calls to Substring() to split up the  
string).

Of course, you left in the "clientData" variable from my original  
suggestion...if you're just storing a ClientState field or property, then  
anywhere you've written "clientData.ClientState", you probably just want  
"ClientState".  I assume that's just an oversight that would be addressed  
in the actual code (since it wouldn't compile otherwise).

Pete
pbd22 - 16 Jan 2008 20:25 GMT
Thanks Pete.

I feel a bit thick on this.

I am still struggling with ClientSate in the switch statement.

Left alone, I get

"ClientState is a type but is used like a variable".

When I do something like

ClientSate state = new ClientState();

or

state = new ClientState();

and then

switch (ClientState)

the compiler still throws the error.

shouldn't i be instantiating the connection
as a new ClientSate? Or something like that?

What am I not getting?

ps -

I have put:

enum ClientState
       {
           LoginIDPrompt,
           LoginPasswordPrompt,
           Connected
       }

inside my class next to my
variable declarations.
Peter Duniho - 16 Jan 2008 20:44 GMT
> Thanks Pete.
>
> I feel a bit thick on this.

That's okay.  I know how hard it can be to try to write code on a Monday.  
:)

> I am still struggling with ClientSate in the switch statement.
>
> Left alone, I get
>
> "ClientState is a type but is used like a variable".

That's because you (apparently) do not have a variable named "ClientState".

> When I do something like
>
> ClientSate state = new ClientState();

Variable name is "state", not "ClientState".

> or
>
> state = new ClientState();

See above.

> and then
>
> switch (ClientState)
>
> the compiler still throws the error.

Sure, it would.  If you've named your variable "state" instead of  
"ClientState", then the switch statement should read "switch (state)"  
instead of "switch (ClientState)".

Again, this isn't unique to enums...don't get hung up the fact that it's  
an enum.  The point of using an enum is that they can be used like a  
regular simple value type.  So the rules for writing code using enums are  
the same as if you were writing code using, for example, an int or a char  
or a long, etc.

I'm sorry if using the same name for the variable as the type has confused  
things.  It's funny...I generally try _not_ to do that, but it's a common  
.NET convention for naming properties and public fields.  So when I had a  
data member of a data structure of type "ClientState" in my original  
example, I just reused the type name for the name of that data member.  
Things seem to have gone downhill from there.  :)

Pete
pbd22 - 16 Jan 2008 22:04 GMT
Hey Peter,

OK, I am starting to get the swing of things, thanks.
The last bit I am having probs with is the 2 substrings.
My current program is going screwy once it connects.
It either hangs, or says your connected after I type each
letter in the Password: field.

I think it has to do with the statusMessage variable.
I think this last bit will do it.

Thanks again.
Peter

       public void TheConnectionHandler()
       {

           _telnetSocket = (TcpClient)connectionQueue.Dequeue();
           _clientStream = _telnetSocket.GetStream();

           buffer = ASCII.GetBytes("Login: ");
           _clientStream.Write(buffer, 0, buffer.Length);

           while (true)
           {

               _bytesRead = 0;

               try
               {
                   //blocks until a client sends a message
                   _bytesRead = _clientStream.Read(message,
0,4096); //message

               }
               catch
               {
                   //a socket error has occured
                   break;
               }

               if (_bytesRead == 0)
               {
                   break;
               }

               statusMessage += ASCII.GetString(message, 0,
_bytesRead);

               if (statusMessage.LastIndexOf(ENDOFLINE) > 0)
               {

                   if (currentState != ClientState.Connected)
                   {
                       Login(statusMessage.Substring(0,
statusMessage.IndexOf(ENDOFLINE)));
                       //buffer = ASCII.GetBytes(statusMessage);
                   }
                   else
                   {
                       buffer = ASCII.GetBytes("You are connected!");
                       _clientStream.Write(buffer, 0, buffer.Length);
                       _clientStream.Flush();

                   }
               }

           }

           _telnetSocket.Close();

       }

       protected void Login(string clientInput)
       {

           switch (currentState)
           {
               case ClientState.LoginIDPrompt:
                   strLoginID = clientInput;
                   currentState = ClientState.LoginPasswordPrompt;
                   statusMessage =
statusMessage.Remove(statusMessage.IndexOf(clientInput),
statusMessage.LastIndexOf(clientInput)); < --- SHOULD I DO THIS HERE?
                   //statusMessage = "";
                   SendPrompt();
                   break;
               case ClientState.LoginPasswordPrompt:
                   if (CheckPassword(strLoginID, clientInput))
                   {
                       currentState = ClientState.Connected;
                   }
                   else
                   {
                       currentState = ClientState.LoginIDPrompt;
                   }
                   break;
               case ClientState.Connected:
                   {
                       break;
                   }
           }

       }

       private void SendPrompt()
       {
               buffer = ASCII.GetBytes("Password: ");
               _clientStream.Write(buffer, 0, buffer.Length);
               _clientStream.Flush();

       }

       public static bool CheckPassword(string name, string pass)
       {

           if (name == "First" && pass == "Last")
           {
               return true; // just for kicks. remove it.
           }
           else
           {
               return false;
           }

    }
Peter Duniho - 16 Jan 2008 22:29 GMT
> Hey Peter,
>
[quoted text clipped - 6 lines]
> I think it has to do with the statusMessage variable.
> I think this last bit will do it.

Well, this is related to my suggestion that you only manage the  
statusMessage variable from within the method "TheConnectionHandler()".  
The code you put in the Login() method to deal with updating the  
statusMessage variable is wrong in any case (IndexOf() and LastIndexOf()  
in that statement are both returning the same index...nothing's really  
getting removed from the string), but IMHO the code will be easier to  
understand and fix if you put that statement in the method dealing with  
i/o, rather than the one dealing with login credentials.

IMHO, you should remove the line that attempts to deal with updating the  
statusMessage variable from the Login() method.  Then the section of code  
in TheConnectionHandler() that deals with the data after it's been  
converted to a string should look more like this:

    // You want the first EOL in the string, not the last.
    int ichEOL = statusMessage.IndexOf(ENDOFLINE);

    // If there is an EOL character, then process the current line
    if (ichEOL >= 0)
    {
        // Extract the line of text, up to the EOL (but not including)
        string strLine = statusMessage.Substring(0, ichEOL++);

        // If there are characters after the EOL, set the current
        // string to those characters.  Otherwise, just reset it to
        // an empty string.
        if (ichEOL < statusMessage.Length)
        {
            statusMessage = statusMessage.Substring(ichEOL);
        }
        else
        {
            statusMessage = "";
        }

        if (currentState != ClientState.Connected)
        {
            Login(strLine);
        }
        else
        {
             buffer = ASCII.GetBytes("You are connected!");
            _clientStream.Write(buffer, 0, buffer.Length);
            _clientStream.Flush();
        }
    }

Now, all that said, I'll note that you haven't really got a genuine telnet  
implementation there.  As you saw when dealing with the entering of the  
password, data is sent as the user types it.  It's not being sent a line  
at a time, rather it's being sent a character at a time.

As long as the data is entered without errors, the above should work  
fine.  But as soon as you have a situation where the user wants to  
backspace, you've got a problem.  The backspace characters are just going  
to get added to the input string, and so of course when the user finally  
does send the EOL character, the string up to that point will be messed up.

IMHO, you would be better off refactoring the code so that you've got two  
layers: a low-level telnet i/o layer that can deal with backspaces and the  
line, and a higher level layer that deals only in complete lines.  The  
lower level wouldn't present a complete line to the higher level until it  
sees an EOL character.

Whether you split the code up like that or not, you definitely need to  
include some logic to deal with the user entering backspaces (and possibly  
other control characters as well).

Pete
pbd22 - 16 Jan 2008 23:16 GMT
Pete -

Thanks,

I agree with you about the "character-by-character" implementation.
When I enter my password things get screwy. I need to figure this
out.

Your code sort of worked. I get all sorts of garbage when I get to the
password ("/n") or ("/r") or ("r/n/r"). These are usually before the
actual
password. I think it may have to do with the fact that the ENDOFLINE
variable (that you can't see) is actually

const string ENDOFLINE = "\r\n";

Maybe this is messing things up?

Peter
pbd22 - 16 Jan 2008 23:30 GMT
OK,

You are right. I need to figure out how to get the Password line to
react to lines and not characters...
pbd22 - 16 Jan 2008 23:36 GMT
OK,

You are right. I need to figure out how to get the Password line to
react to lines and not characters...
Peter Duniho - 17 Jan 2008 00:01 GMT
> [...]
> I think it may have to do with the fact that the ENDOFLINE
[quoted text clipped - 3 lines]
>
> Maybe this is messing things up?

Sure, that's definitely going to be part of it.  I didn't realize that was  
a string instead of a character and the code I posted assumes it's a  
single character.  That's easy enough to change though.  Just change the  
"ichEOL + 1" to "ichEOL + EOL.Length".

As you've noted, there are other things you'll need to address as well.  
But that should at least fix that issue. :)

Pete
pbd22 - 17 Jan 2008 17:01 GMT
Thanks Pete.

I am getting there thanks to your help.

I have some nick-knacks that are, it seems,
not that trivial.

1) how do I highlight a line?
2) how do I change background color?
3) how do I change font color?
4) how do I clear the screen (aka. DOS "cls");

For anybody who cares to tackle 'em...

Thanks again for your help,
Peter
pbd22 - 17 Jan 2008 18:18 GMT
One more to the list for anybody who might know...

1) how do I highlight a line?
2) how do I change background color?
3) how do I change font color?
4) how do I clear the screen (aka. DOS "cls");
5) how to hide password entry (as clear-text or XXXXXXX)?

Thanks again,
Peter
christery@gmail.com - 26 Jan 2008 23:05 GMT
> 4) how do I clear the screen (aka. DOS "cls");

Aha, another one missing chr$(12)... in this case its some <esc>[2J or
something... who knows, who cares
what do I know... anyone seen chr$(12)
//CY
Peter Duniho - 17 Jan 2008 18:27 GMT
> Thanks Pete.
>
> I am getting there thanks to your help.

Glad it's working out.  You're welcome.

> I have some nick-knacks that are, it seems,
> not that trivial.
[quoted text clipped - 3 lines]
> 3) how do I change font color?
> 4) how do I clear the screen (aka. DOS "cls");

These are all new questions, and deserve an all-new thread.

Hint: when you repost those questions, you should be clear about how you  
are displaying the text.  There's a huge difference between doing those  
kinds of things in a console window and doing them in a normal Forms  
application window.  For the former, see the Console class (it has  
properties for setting font foreground and background, and a method for  
clearing).  For the latter, you can use a RichTextBox as a quick-and-dirty  
implementation, or you can handle all of the drawing yoursself (more  
complicated, but gives you more control and in the context of something  
like this where the text is constantly changing, generally will provide  
better visual quality and performance).

It may be you can get started with the above information.  If you need  
more help, start a new thread with specific questions.

Pete
pbd22 - 17 Jan 2008 21:40 GMT
OK, good point. I'll start another thread.

Thanks a ton Pete for your help!

Peter
pbd22 - 16 Jan 2008 23:29 GMT
Pete -

Thanks,

I agree with you about the "character-by-character" implementation.
When I enter my password things get screwy. I need to figure this
out.

Your code sort of worked. I get all sorts of garbage when I get to the
password ("/n") or ("/r") or ("r/n/r"). These are usually before the
actual
password. I think it may have to do with the fact that the ENDOFLINE
variable (that you can't see) is actually

const string ENDOFLINE = "\r\n";

Maybe this is messing things up?

Peter
pbd22 - 16 Jan 2008 18:46 GMT
Thanks Pete.

I am making my way though your code.
Could you explain what the "clientData" of "clientData.ClientState"
is?

Sorry - not too familiar with enums.

Thanks,
Peter

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.