Hello There, Guest!
View New Posts  |  View Today's Posts
Telnet Client

  • 0 Vote(s) - 0 Average


05-15-2016, 03:40 PM #1
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

Telnet Client
So I created my own minimal Telnet client for sending commands to some controller that I have in my house and I was having issues with the server sending back chunked data that was separated into random lines.

I realized after some time that the server was sending back an additional '\x0D\x0A' to every chunk, and after trimming that off the output was consistently "normal" every time.

This can be heavily expanded upon and probably improved, but here's what I had written in the last 5 mins so far:
Code:
internal class TelnetClient : IDisposable
    {
        private readonly TcpClient _tcpClient;
        private NetworkStream _tcpStream;

        public int DefaultTelnetPort => 23;
        public IPAddress IP { get; }

        public event ConnectedEventHandler Connected;
        public delegate void ConnectedEventHandler(IPEndPoint ip);

        public event DisconnectedEventHandler Disconnected;
        public delegate void DisconnectedEventHandler(IPEndPoint ip);

        public event ReceivedDataEventHandler ReceivedData;
        public delegate void ReceivedDataEventHandler(byte[] buffer, int receivedBytes);

        public event SentDataEventHandler SentData;
        public delegate void SentDataEventHandler(byte[] buffer, int receivedBytes);

        public TelnetClient(IPAddress ip)
        {
            IP = ip;
            _tcpClient = new TcpClient(AddressFamily.InterNetwork);
        }

        public async Task ConnectAsync()
        {
            await _tcpClient.ConnectAsync(IP, DefaultTelnetPort);
            _tcpStream = _tcpClient.GetStream();
            OnConnected(new IPEndPoint(IP, DefaultTelnetPort));
        }

        public async void ListenAsync()
        {
            for (;;)
            {
                if (_tcpStream == null) break;
                var buffer = new byte[4096];
                var receivedBytes = await _tcpStream.ReadAsync(buffer, 0, buffer.Length);
                if (receivedBytes > 0)
                {
                    OnDataReceived(buffer, receivedBytes);
                }
            }
        }

        public async Task SendBytesAsync(byte[] buffer)
        {
            await _tcpStream.WriteAsync(buffer, 0, buffer.Length);
            await _tcpStream.FlushAsync();
            OnDataSent(buffer, buffer.Length);
        }

        private void OnConnected(IPEndPoint ip)
        {
            Connected?.Invoke(ip);
        }

        private void OnDisconnected(IPEndPoint ip)
        {
            Disconnected?.Invoke(ip);
        }

        private void OnDataReceived(byte[] buffer, int numBytes)
        {
            ReceivedData?.Invoke(buffer, numBytes);
        }

        private void OnDataSent(byte[] buffer, int numBytes)
        {
            SentData?.Invoke(buffer, numBytes);
        }

        private void Cleanup()
        {
            OnDisconnected(new IPEndPoint(IP, DefaultTelnetPort));
            _tcpClient.Close();
            _tcpStream?.Dispose();
            _tcpStream = null;
        }

        public void Dispose()
        {
            Cleanup();
        }
    }

I can send data, and follow that with 'BYE' to terminate the session:
Code:
await telnetClient.SendBytesAsync(Encoding.ASCII.GetBytes("BYE\x0D"));

And after listening, receiving data with something like:
Code:
telnetClient.ReceivedData += (buffer, count) =>
            {
                var data = Encoding.ASCII.GetString(buffer).TrimEnd('\0', '\r', '\n');
                data = Regex.Replace(data, @"\n.+?>\s?", "\n");
                if (data.Contains("Disconnecting. Bye."))
                {
                    telnetClient.Dispose();
                    return;
                }
                Console.WriteLine(data);
            }

I wasn't entirely sure what the best way would be but after I send the BYE command, this particular server sends back the "Disconnecting. Bye." string so I just used that because I didn't want it part of the output, and I also didn't want the generic "SERVER_NAME>" to show before each command so I used some very basic regex which again I wasn't sure of, but it works for my particular project.

Thought someone might find this useful though :).

NOTE: Yes I know I could have used "\r\n" I just wanted it to stick out a bit more so I used hex instead to help me understand that it's required. I should have had a separate function which always appends this to the command string, but this is just a basic PoC for me so far until I had it fully working. Soon I will work on refactoring everything.

cheers
This post was last modified: 05-15-2016, 03:42 PM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

05-15-2016, 07:33 PM #2
Mazzn
ლ(ಠ益ಠლ)
*******
Administrators
Posts: 199 Threads:16 Joined: Sep 2013 Reputation: 19

RE: Telnet Client
I'd usually tell a person writing their own telnet class to "just use PuTTY", but where's the fun in that? We (as coders) may not have to reinvent the wheel, but it's so much fun doing it! And you learn so much in the process, too.

Don't think I'll have or want to use telnet for anything - unless the WiFi lamps I plan on getting eventually are driven by telnet commands - but I still enjoyed reading through the code. I found it interesting that you used for(;;) instead of while(true) or a similar form. Since you simply used a break the result would be the same, right? Any particular reason for that?


Looking forward to being able to code more myself, I noticed I really miss doing it...
Visit me at mazzn.net & blog.mazzn.net!
//This is very important :)

Self.KeepImproving(true);


05-15-2016, 08:09 PM #3
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Telnet Client
Well the reason I wrote my own is because I already have UDP broadcast and SSDP as well as USB discovery soon for the devices I'm interested in on my network. They use telnet for command-line interaction however, so the idea was to discover a certain device, connect to it via telnet and issue a set of commands all from the same program automatically.

The only WiFi lights I've dealt with are Philips Hue, and those use TCP rather than UDP and they use standard port 80 for communication with a REST API hosted on the bridge which is attached to the network. The bridge communicates with the lights for you via ZigBee.

And the reason I use for(;;) is partially old practice. It's preferred in C/C++ but also more optimized than while(true) in C# code that is not optimized. NOTE: By default the debug configuration disables optimization and the release configuration enables it. for(;;) from what I recall is the same as a simple non-conditional jump whether it's optimized or not. It also has more verbosity to me because of my style, that it's intended to be an infinite loop for the most part. I don't abuse while() loops to do this and reserve them for proper conditions/expressions that can be manipulated because that's what they are really intended for in my opinion.

edit: I guess initially I didn't have that null check, but I suppose now it should be changed to:
Code:
while (_tcpStream != null)

I think the reason for the broadcast was for legacy device support lol. Because I have no idea why anyone would use broadcast messages for device discovery, that's what multicast is for, particularly SSDP in a lot of cases. Less network bandwidth too, and there's lots of support built in for avoiding or allowing multicast loopback with sockets. You can't do that with broadcast as far as I know, so you'll still receive the message that you sent, and you have to filter it manually.

As a sidenote: VERY lame that the development for their stuff requires VS 2008, and in addition to that, they use the .NET compact framework (v 3.5), and they also don't let you do much with the framework directly, you have to use their limited wrapper around an already minimalistic framework, and they'll sandbox you away from using sockets directly. Sad thing is that their library would have a lot of memory leaks it if weren't for the GC lol. I found out today that they don't give you a UDPClient class in their wrapper either so that throws any implementation of SSDP out the window.

I was trying to use their library for implementing SSDP today and that's what I concluded.
This post was last modified: 05-15-2016, 08:24 PM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

05-16-2016, 02:21 AM #4
Mazzn
ლ(ಠ益ಠლ)
*******
Administrators
Posts: 199 Threads:16 Joined: Sep 2013 Reputation: 19

RE: Telnet Client
Ah, that sounds like a fun project! May I ask what kind of devices you're using?

I also thought about your change there but forgot about it while typing (was pretty late for me), using while (_tcpStream != null) instead of if (_tcpStream == null) break; seems reasonable for readability if nothing else. One of my programming teachers always said if we used breaks in our code we'd lose points in tests for bad coding style. I don't entirely agree on that and do use them when I see fit, but in this case... Yeah, I think it's better :P
Didn't know about how for(;;) and while(true) behave though, but it makes sense what you're saying. Never learned these things in school...

About the Philips Hue, I don't know if I'm going for them. Heard they're pretty expensive, but then again I didn't look for alternatives yet. We'll see! Need an apartment to live in first ;)

The limitations sound pretty rough, hope you can figure something out somehow! Good luck!
Visit me at mazzn.net & blog.mazzn.net!
//This is very important :)

Self.KeepImproving(true);


05-17-2016, 05:18 PM #5
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Telnet Client
This telnet implementation actually came in handy for troubleshooting I did today.

The devices are Crestron network devices. Embedded systems.

The problem with standards like *that* is the fact that if you think that way 100% of the time then you're more worried about conforming to some subjective coding standard rather than writing maintainable and in some cases readable code. I would never agree with a professor telling me that it's bad practice. Features of a language are there for a reason, you use whatever you see as most fitting if you have a reason behind it. In lots of cases the output will probably be the same due to code optimization too so why would I care that I'm using a break statement over some odd flow that I may not be used to?

Philips hue is expensive because the device itself is a smart device, but for that reason, and because it's an LED bulb, it's going to last you a very long time, and it doesn't require you to do any damage to existing infrastructure because it's all wireless.

There's people that say goto's are bad, but if you use them wisely they are a tool to be used IMO. In C for instance, lots of code written by the guys who initially wrote for Linux use goto's for a readable and clean way of cleaning up; freeing data from the heap, releasing locks, etc... If you had a bunch of nested if statements and conditions instead, lots of that code would be scattered and nested.

To be honest, most profs have never been in the real world like most other programmers from what I have known. This means that they teach the concepts, and most of what they teach is based on the textbook rules, concepts, and theories, not real world learned practices that work based on statistics or solutions derived from certain coding disasters. That's dangerous IMO.

Engineers are in the same boat. I've heard stories of new up and coming Engineers starting their first job, and not knowing really what to do because they learn the concepts but don't understand how to put them into practice.

The education system is really flawed.
This post was last modified: 05-17-2016, 05:24 PM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

06-05-2016, 07:48 PM #6
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Telnet Client
See I already tried to re-write this using a smaller .NET micro framework, and the things that I knew I was missing out on this raw TCP telnet client implementation had affected my ability to write a telnet client for communicating with a server for controlling it.

https://support.biamp.com/Tesira/Control..._in_Tesira

And boom:
Quote:A standard Telnet client would be able to negotiate the session options without problem, but several third-party controllers do not implement a Telnet client by default. Instead, they implement control over TCP/IP using what’s commonly known as a ‘RAW’ connection. If the Control System does not respond to the Telnet session options negotiations, the session will not go ahead. As such, the control system will have to be programmed to negotiate the Telnet options with Tesira’s Telnet server.

So now I've written code that will parse and respond to the negotiation as such, and hopefully it'll work in the test environment this coming Monday. :)


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲




Forum Jump:


Possibly Related Threads...
Thread Author Replies Views Last Post
   Create your own IRC Client [C#/VB.Net] KoBE 185 121,240 10-01-2016, 06:57 PM
Last Post: AceInfinity
   IRC Client Class KoBE 110 75,029 10-01-2016, 06:52 PM
Last Post: AceInfinity
Star  Multi-Client Server Async Socket Example KoBE 165 169,473 09-10-2016, 05:17 AM
Last Post: R4TK3N
   Twitch chat bot (irc client) dmgvol 11 19,425 08-24-2015, 11:59 PM
Last Post: Signal_20
   Justin.tv and Twitch.tv chat client. KoBE 41 22,831 07-13-2015, 11:02 PM
Last Post: KoBE


Users browsing this thread: 1 Guest(s)