I'm a 'reasonably' decent VB.net programmer who has wandered into the murky depths of dealing with connections to MUD servers for the first time. In my searching of the net on how to actually connect to a server "Idiots guide thereof" it seems that the information at hand either is very specific to a connection issue that someone is having or assumes some basic level of knowledge that I don't have. (some people it seems, are born with Telnet protocals genetically imprinted) I can set up the sockets but any of the commands that I issue either get the finger from the server or appear out of a black hole on the other side of the known universe. My specific request is to build ( yet another MUD client ) mainly for myself but will make the code available for any who want it, with a few features that I find lacking in the collection of clients that I have used. Specifically the Triggers, Aliases and programmable Macros that all seem to have some limitation that I find personally frustrating - the ability for the client to issue stat/score commands on your behalf and then parse the results into a GUI representation - ability to validate current inventory and let you know where things are especially for games that allow you to have multiple containers - I can never remember where I put my sandwiches *sigh* - general things along those lines. If someone can either tell me or point me in the right direction for information I would be most grateful I would also package this professionally into a MSI for those who install on the dark side.
So you have the socket connection part down, it sounds like. Your next step is to deal with the telnet protocol, assuming you want to talk to other people's MUDs.
I'll rustle up some links for you. How's your ability to read C#? I have some Telnet code that you may find useful, but you'll have to translate it to VB.NET yourself, it shouldn't be hard since I mostly used Dictionaries.
Translation from C# is not a problem and have done some of that before - as long as the structure is based around the .net framework you can 'almost' do a cut & paste with some minor clean up. Even so , I can read the logic most of the times and make the translation - I don't mind doing the hard yards as long as I know which direction the the race is being run.
I can set up the sockets but any of the commands that I issue either get the finger from the server or appear out of a black hole on the other side of the known universe.
You might be sending non-terminated (or C-style zero-terminated strings). The Telnet spec calls for CR + LF to terminate input. See RFC 845 at: http://www.faqs.org/rfcs/rfc854.html . In which case, the server is simply waiting for you to finish your input. Try tacking "\n\r" to each line of text before sending it out the socket.
BTW, this does not apply to Telnet command sequences like "IAC WILL ECHO" or "IAC DO NAWS". I'm taking about just getting player input to the mud server.
I used to have a tutorial online for implementing TELNET. Not sure what happened to it.
The gist of it is pretty simple. TELNET is best implemented as a state machine, where you have the following states: TEXT, IAC, DO, DONT, WILL, WONT, SB, SB_IAC.
For each byte you receive you look at the current state and decide what to do, easily implemented as a switch() statement in C#. The state transitions are as follows:
STATE : BYTE -> NEW STATE (action) TEXT : IAC -> IAC (begin telnet command) TEXT : anything -> TEXT (regular text input from the remote end) IAC : IAC -> TEXT (escaped IAC byte) iAC : DO -> DO (start DO negotiation) DO : anything -> TEXT (DO option code, process) IAC : DONT -> DONT (start DONT negotiation) DONT : anything -> TEXT (DONT option code, process) IAC : WILL -> WILL (start WILL negotiation) WILL : anything -> TEXT (WILL option code, process) IAC : WONT -> WONT (start WONT negotiation) WONT : anything -> TEXT (WONT option code, process) IAC : SB -> SB (begin sub-request) SB : IAC -> SB_IAC (sub-request end/escape sequence) SB_IAC : IAC -> SB (escaped IAC byte) SB_IAC : SE -> TEXT (completed subrequest, process data) SB_IAC : anything -> TEXT (invalid sub-request, abort) IAC : anything -> TEXT (one of several IAC commands, most are not used in MUDs, safely ignored usually)
At taht point it's just a matter of figurinig out which options you support and responding to them appropriately.
The biggest mistak eI see is that people try to implement TELNET as something other than a state machine, which is wrong. It's not technically mandatory to use a state machine, but implementing it any other way is going to be very difficult to do correctly. Far too many MUDs try to process a whole TELNET commands inside the current socket read buffer in one go, which is clearly wrong. Go for the state machine and it's easy to get right.
A lot of things are best represented as state machines: telnet, parsers for fairly simple languages, player connections to a MUD, … I'm not sure why state machines are so rare; I guess it's because a lot of people just aren't aware of exactly what they are.
I remember it because for some reason CRLF just sounds better than LFCR, and then I remember that CR (\r) means go to the beginning of the line. Maybe this way of thinking isn't helpful for other people, though… :wink:
27 Feb, 2009, quixadhal wrote in the 13th comment:
Hey! My favorite topic! CRLF *rabblerabblerabble* :)
I'm old enough to remember real typewriters (both IBM Selectric and good old manual typewriters that don't need electricity!). When you hit the big old lever on the left-hand side, it does it the "wrong" way, in that it first ratchets the platen (which feeds the paper), and then pushes the platen all the way right (which positions the keys/ball/etc at the left edge of the paper).
So, just remember, TELNET is *NOT* a typewriter, and you're all set.
EDIT: Oh, and thanks for the state machine layout elanthis! I think it's quite handy to see the transitions set out in one place. Much easier than gouging your eyes out on the RFC specs anyways. :)
The C# implementation I have is basically based on the python implementation that elanthis had done, so if you just use what he said you will become strong. I realized after I offered you the code that my current telnet classes are kind of meant to work with the handler system my codebase has so unfortunately it has a lot of dependencies that keep them from being copy&paste useful to you. However, I put the main three files up in the paste-bin as you may find something useful to cannibalize. The biggest 'problem' is that the TelnetProtocol class, which does the actual filtering, assumes that it owns a connection object and a TelnetSession object.
Kind of late for me, so forgive me if I don't make any sense. Here are the links:
http://www.mudbytes.net/pastebin-17728 - This is the TelnetOption and the TelnetSession class. The TelnetOption class simply represents an option, and whether that option is enabled and\or supported. Supported meaning that the server is willing to negotiate it. TelnetSession uses TelnetOption classes to keep tabs on what is enabled or supported where. I've been told it's overkill, but it seems to work fine for what I want. It's overly documented since I tend to get busy for months at a time before I come back to my code.
http://www.mudbytes.net/pastebin-17726 - This is the TelnetProtocol class, which contains the state machine and the filtering (i.e. reject non-printable ASCII input.) I haven't updated it for UTF yet, so the filtering strips out any non-printable ascii characters (but only when in 'normal' mode so they are preserved in IAC or Subnegotations, etc)
I realize I'm just throwing code at you so I apologize, I haven't had time to prepare an actual library yet. Also, there's quite a bit of console output on there that I used for debugging when building it, eventually that will get replaced with conditional logging tracers via log4net.
Greetings and thanks for this discussion and information everyone. I've progressed more in the last 48 hours from links and info here than in about 2 weeks of googling this to death. I'm going to go through this bit by bit and with a bit of trial and error (mmmmm make that a bit of trial and lots of errors ) , I will piece all this together. I'll keep an eye on this thread to see if anyone else has something to add but I've got enough to go on with for now. I'll let you know of my successes ( hopefully ) in the next week or so ( being optimistic ).
In particular I am having trouble with your last state transition: SB_IAC : anything -> TEXT (invalid sub-request, abort)
Given that you are in a subnegotiation, and you have (after the IAC SB <type>) zero or more bytes followed by IAC x, where x is NOT either IAC or SE, what is your justification for saying "(invalid sub-request, abort)"?
As I explain in some length on the above link, you could make out a case for:
Processing the subnegotiation as received so far
Treating the "lone" IAC as part of the subnegotiation (I know it should be doubled, but it also should not be there *not* followed by an SE)
Dropping the IAC and proceeding with the subnegotiation
Dropping IAC x and proceeding with the subnegotiation
Treating the IAC x as starting a *new* negotiation, and thus terminating the previous one and starting a new one - possibly processing the previous negotiation as valid, and possibly not.
Even if one follows your suggested behaviour of treating IAC x as invalid, and aborting the subnegotiation, do we now process x as part of the new text phase, or do we drop *both* the IAC and the x? And if so, on what grounds?
I'm not trying to be difficult, at least your list is about the only one I have found so far that addresses the problem, but a simple assertion on a forum isn't really a standard, so I would be pleased if you could refer to where this action is justified.