Fog Creek Software
Discussion Board




Welcome! and rules

Joel on Software

Heavy lifting with System.Console

I'm working on a really basic command line telnet client as a first "teach myself C# project", but I'm running into a snag.

Is it possible to put the console into a mode that captures keypresses and then passes the keypresses to some other function to decide whether or not to display the character?  With System.Console.ReadLine(), if I press any key, it displays on the screen whether I want it to or not.  I've checked the docs for System.Console and can't find anything, and I'm beginning to wonder if it's even possible.

Thanks.

John Wilson
Friday, January 16, 2004

No, this is not possible with System.Console because this class emulates a teletype-like text I/O stream. You'd have to use unmanaged interop calls to the Win32 console functions which is probably out of the question for a learning project.

Chris Nahr
Saturday, January 17, 2004

"You'd have to use unmanaged interop calls"

what good is dotnet, then?

Arthur
Saturday, January 17, 2004

um, the framework doesn't do 100% of what Windows does.

sometimes it's frustrating what wasn't done. but they had to stop somewhere & ship the thing. not worrying about the Microsoft console calls, or creating a general 'curses' console sounds like a very reasonable decision for a largely general-purpose framework targeted at web and windowed applications.

there are limits, that's why interop exists. thankfully it's pretty easy to use, though you'll have to learn about marshalling for complex types.

maybe someone already wrote a class for the win32 console.

mb
Saturday, January 17, 2004

"You'd have to use unmanaged interop calls to the Win32 console functions..."

Hahaha... I keep forgetting about the unmanaged interop stuff.  I've never done any serious programming in windows before, so the .NET framework is all I really know at this point.

No, I'll just switch to making it graphical; I have a pretty good idea on how to modify a rich text box so I can use it as a display/input widget.  (And if I don't have a good idea, I guess I'm going to find out.)

John Wilson
Sunday, January 18, 2004

Oh, and thanks for the info, btw.

John Wilson
Sunday, January 18, 2004

It's not like the interop stuff is hard. Honest.

As for "what good is .net?", apparently it's no good to you. Just keep thinking that way. It helps the rest of us. :)

Brad Wilson (dotnetguy.techieswithcats.com)
Sunday, January 18, 2004

Follow up question:  would I have to use interop stuff for controlling the serial or parallel port?  If so, can anyone point me to 1. a good tutorial on how to use the .NET interop facilities, and 2. a good book on how to do serial/parallel programming?

John Wilson
Sunday, January 18, 2004

What about System.Console.Read in a loop that ends when it recieves a hard return?

Andrew Burton
Monday, January 19, 2004

Yeah, I stumbled on that solution early on.  There are several problems though. 

First off, System.Console.Read() does not return "until the read operation is terminated; for example, by the user pressing the enter key."  So even though Read() only returns a character at a time, it effectively blocks until the user presses enter.  If I'm typing a command at the command line, that isn't problem.  But let's say I start up pine.  I hit 'c' to compose an e-mail, but pine won't receive the c until I hit enter.  And then it will receive and process the enter as well, which I don't want.  If I ignore the hard return, then pine works, but the command line won't (indeed, I won't even be able to log in.)  I could always ignore the hard return and indicate my client to insert one whenever I type "\n".  But now not only do I have to check the input stream for "\n", but I have to remember to escape any natural occurances of "\n" with "\\n", and it only gets worse from there:  imagine if I was typing this explanation in an e-mail.  (If I want "\\n" to show up in the final e-mail, is that supposed to be "\\\n" or "\\\\n"?)  And so on.

The only "solution" to using pine, such as it is, is to memorize the command sequence up until the point where it doesn't matter if pine processes a hard return.  But again, that's not really a solution either.

Even if that problem was magically solved, there is always a local echo with System.Console.Read(), so there is no way to hide passwords when logging in to a regular telnet server. 

Since my forward momentum on the console version has stalled, I'm spending some time cleaning up the code I have (adding log4net debug statements, cleaning up exception handling, and the like) and plotting my next move.

John Wilson
Tuesday, January 20, 2004

What you're looking for is Console.In, which happens to be a TextReader.  The TextReader class is the abstract parent of StreamReader and StringReader.

TextReader supports both Read and ReadBlock, in addition to ReadLine and ReadToEnd.  You can also do Peek calls into the TextReader to determine if there's anything of interest.

You should very easily be able to build a telnet client with this and Console.Out.

Jeffrey D. Panici
Tuesday, January 20, 2004

John: I don't know much about Telnet as a protocol, so this may be bunk.  Wouldn't you need two sockets for what you want to do?  One to write to the server, one to  read from it?

Andrew Burton
Tuesday, January 20, 2004

A socket is bi-directional. You both read from it and write to it.

Brad Wilson (dotnetguy.techieswithcats.com)
Tuesday, January 20, 2004

Jeffrey Panici:  No dice.  ReadBlock blocks in the same way Read does.  Even if all I want to do is read one character in at a time, it will block until I type enter.  Also, there's still no way of turning off the local echo of characters, which is a huge problem in and of itself.

Andrew:  Brad is right, you only need one bi-directional socket for telnet.  Telnet is a very simple protocol.  You scan the input stream one byte at a time.  If you see FF, then a telnet command will follow.  FF is known as IAC, for some reason that escapes me at the moment but would probably make more sense if I looked at the RFC again.  If you need to actually transmit FF, then you escape it by sending it twice.

There are 15 telnet commands (including NOP, heh.)  Some commands are followed by options, and the options themselves may have options, if I remember correctly.  The client and server then haggle over which options they will or won't support.  A client or server has to implement a minimal set of options to be considered an NVT (Network Virtual Terminal), and then any additional, fancy options can be negotiated based on what the client or server actually supports.  These are things like passing environment variables back and forth, agreeing to act like various kinds of terminals, changing the character set for "multi-lingual telnet", etc.

Conceptually, the telnet protocol is symmetrical, as both client and server have to implement an NVT.  The set of functions which have to be implemented to provide NVT support is indeed, very minimal.  And the haggling process is quite simple.  A client can request that an optional service be provided (IAC DO <option>), it can indicate its desire that a service be provided (IAC WILL <option>, this is also the confirmation of a DO), it can indicate its refusal (IAC WONT <option>), and it can request that the server not do some option (IAC DONT <option>).  If both client and server send the same request to each other, then each takes the other end's request as confirmation of their original request.

So once you implement the NVT, you can ignore everything else by sending IAC WONT <option> for any non-NVT request you get.  And if you make some assumptions about how sophisticated the server is that your client is talking to, you can negotiate some options that make it even easier to program your client (or server), but at the expense of backwards compatibility with older (read: line-printer based) systems.  Also, if you don't actually implement the NVT, you break the RFC, essentially. 

While it's relatively easy to get it right, there are plenty of opportunities to screw things up through inattention to detail or laziness.  These screwups will largely be hidden from you if you use any modern telnet client to connect to your homegrown server or vice versa.  As an example, I implemented a "quick and dirty" fake telnet server to test my client with when I was working on my laptop away from an internet connection.  But I forgot to keep track of whether or not I had received confirmation of my various IAC commands, which resulted in a glorious infinite network loop.

John Wilson
Wednesday, January 21, 2004

John, the problem with the console input blocking is caused by the fact that a console is created by default in Line Input Mode so there isn't anything in the buffer to read until the <Enter>. Changing the console mode to character input mode and to turn off automatic echo is simple with a couple of interop calls to GetConsoleMode and SetConsoleMode. This might help:

http://tinyurl.com/ypmgo

Stephen Martin
Friday, January 23, 2004

*  Recent Topics

*  Fog Creek Home