-
IRC, reading the stream
I decided that I've finally reached the point in my C# knowledge where I could attempt a very simplistic IRC console client. I'm not trying to reach a level to compete with mIRC or anything like that, this is just me trying to learn how sockets and network streams work and interrelate. And so far.. well, I can get it to connect to the IRC network and I can get it to join a channel but it won't display anything on the incoming side of things. I haven't really written the code for that yet, mainly because I'm not entirely sure how.
Er, well, let me say this instead. I know how to get a line from the stream and send it to the console:
Console.WriteLine(StreamReader.ReadLine());
But what I don't understand is how to make it display ALL of the incoming information from the server. IRC constantly sends information (whether it be people talking, ctcp, server messages, whatever) and I don't understand how to collect and display all of that information continiouslly.
Here's what I have so far. It's split into two files: Program.cs and IRC.cs.
Program.cs
Code:
using System;
using System.Collections.Generic;
using System.Text;
namespace IRCtesting
{
class Program
{
static void Main(string[] args)
{
System.Console.WriteLine("Starting the client..\n");
IRC irc;
if (args.Length == 0)
{
// Create a new IRC connection using the default constructor
irc = new IRC();
}
else
{
// Code to seperate and get the arguments later
irc = new IRC();
}
while (irc.canQuit == false)
{
irc.getCommand();
}
System.Console.WriteLine("Press any key to close the client...\n");
System.Console.ReadLine();
}
}
}
IRC.cs
Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace IRCtesting
{
class IRC
{
#region variables
// Public variables
public bool canQuit = false;
public bool connected = false;
public static StreamWriter writer;
public static StreamReader reader;
// Private variables
private int ircPort = 6667;
private string ircServer = "irc.esper.net";
private string nickNameMain = "CSharpIRC";
private string nickNameBckup = "CSharpIRCbck";
private string user = "USER CSharp 1 * :Dont look at me !!!";
private string channel = "";
private TcpClient ircClient;
private NetworkStream stream;
private Thread pingSender;
// Private constants
private const string CRLF = "\r\n";
private const string PING = "PING :";
#endregion
#region constructors
////////////////////////////////////////////////
//////////////CONSTRUCTORS//////////////////////
// There are six constructors which allow you //
// to specify some or all the information //
// necessary to connecting. //
////////////////////////////////////////////////
public IRC()
{
// Connect to IRC
connect();
}
public IRC(string server)
{
ircServer = server;
// Connect to IRC
connect();
}
public IRC(int port)
{
ircPort = port;
}
public IRC(string server, int port)
{
ircServer = server;
ircPort = port;
// Connect
connect();
}
public IRC(string server, int port, string mainNick)
{
ircServer = server;
ircPort = port;
nickNameMain = mainNick;
}
public IRC(string server, int port, string mainNick, string backupNick)
{
ircServer = server;
ircPort = port;
nickNameMain = mainNick;
nickNameBckup = backupNick;
// Connect to IRC using the settings
connect();
}
#endregion
private void connect()
{
// Tell people we're going to connect
System.Console.WriteLine("Connecting to " + ircServer + ":" + ircPort + " ...\n");
try
{
// Try to make a connection to the server
ircClient = new TcpClient(ircServer, ircPort);
stream = ircClient.GetStream();
reader = new StreamReader(stream);
writer = new StreamWriter(stream);
// Start up our ping handler
PingControl();
// Send the ident information to the server (i.e. our user and who we are)
writer.WriteLine(user);
writer.Flush();
writer.WriteLine("NICK " + nickNameMain);
writer.Flush();
// We're connected, so let's set our bool to true.
connected = true;
}
catch (Exception e)
{
// Show the exception
Console.WriteLine(e.ToString() + CRLF);
// Sleep, before we try again
Thread.Sleep(5000);
connect();
}
}
private void quit()
{
// Message that we're quitting
writer.WriteLine("QUIT ");
writer.Flush();
// Exit out of the program.
quit_program();
}
private void quit(string message)
{
// Message that we're quitting
writer.WriteLine("QUIT " + ":" + message);
writer.Flush();
// Exit out of the program.
quit_program();
}
private void quit_program()
{
// Close everything down
stream.Close();
writer.Close();
reader.Close();
ircClient.Close();
// Tell the console that we can quit and release control
connected = false;
canQuit = true;
}
// Join a channel, no key
private void join(string chan)
{
if (channel == "")
{
channel = chan;
writer.WriteLine("JOIN " + channel);
writer.Flush();
}
else
{
Console.WriteLine("This client only supports one channel right now.\n");
}
}
// Join a channel with a key
private void join(string channel, string key)
{
}
// Send something to the channel
private void sendToChannel(string message)
{
writer.WriteLine("PRIVMSG " + channel + " :" + message);
writer.Flush();
}
////////////////////////////////////////////////
///////////////COMMAND PARSER///////////////////
// The command parser takes the incoming text //
// from the console and splits it up into //
// tokens as well as determines what command //
// has been used and what function should be //
// called. //
////////////////////////////////////////////////
public void getCommand()
{
// Get the command string from the console
string rawCommandString = Console.ReadLine();
// Break the command into their tokenized parts so we can determine what to do
string[] commandString;
commandString = rawCommandString.Split(new char[] { ' ' });
// Get the first element of the command string and convert it to upper case
string command = commandString[0].ToString();
command = command.ToUpper();
///////////////////////////////////////////////////
// The QUIT command. Used to disconnect from the//
// server. It'll also cause our console to stop.//
///////////////////////////////////////////////////
if ((command == "QUIT") || (command == "/QUIT"))
{
// We need to check to see if the person has typed a quit message
int commandLength = commandString.Length;
if (commandLength >= 2)
{
// Determine what the message is.
commandLength = commandString.Length - 1;
string message = "";
for (int i = 1; i <= commandLength; i++)
{
message = message + commandString[i] + " ";
}
// Let's quit with our message
quit(message);
}
else
quit(); // Quit with a default message of your nick.
}
///////////////////////////////////////////////////
// The JOIN command. Used to join an IRC channel//
// and may have a key (if the channel is locked) //
///////////////////////////////////////////////////
if (((command == "JOIN") || (command == "/JOIN") || (command == "/J")))
{
// Set our channel
string chan = commandString[1].ToString();
// Does the channel have the necessary #?
if (!chan.StartsWith("#"))
{
// We'll add it here.
chan = "#" + commandString[1].ToString();
}
// Does this join command have a key for the channel?
// Check for the max number of words in the array first.
int commandLength = commandString.Length;
if (commandLength >= 3)
{
// Is the next word a space or null?
if (commandString[2].ToString() != "")
{
// We have a key! Let's get the key and join the channel.
string key = commandString[2].ToString();
join(chan, key);
}
}
// No, it doesn't, so just join a regular channel
else
join(chan);
}
///////////////////////////////////////////////////
// The "Say" command to send a message to the //
// active channel. //
///////////////////////////////////////////////////
if ((command == "SAY") || (command == "/SAY"))
{
if (channel != "")
{
// Determine what the message is.
int commandLength = commandString.Length -1;
string message = "";
for (int i = 1; i <= commandLength; i++)
{
message = message + commandString[i] + " ";
}
// Send the message to the command
sendToChannel(message);
}
else
{
Console.WriteLine("You must first join a channel.\n");
}
}
}
////////////////////////////////////////////////
////////////////PING CONROL/////////////////////
////////////////////////////////////////////////
// PING control is a set of two functions that//
// will ping the server every 15 seconds on a //
// seperate thread until we are no longer //
// connected to the server. //
////////////////////////////////////////////////
private void PingControl()
{
// Start a new thread for the Ping Control
pingSender = new Thread (new ThreadStart(PingRun));
pingSender.Start();
// Begin running the control
PingRun();
}
// Send PING to irc server every 15 seconds
private void PingRun()
{
// Is the client still running? If so, we need to ping the server
while ((canQuit == false) && (connected == true))
{
writer.WriteLine(PING + ircServer);
writer.Flush();
Thread.Sleep(15000);
}
}
}
}
I should add that once I get the incoming information to display I plan to scrap the console version and start on a GUI version. But I really need some help on learning how to make it display everything. Even some basic ideas and concepts will help me out.
Thanks guys.
-
You know how to connect to the server using a username etc...
But you don't know how to split the data in the different formats, as in you need to know what data represents what ...
If thats the case:
Use a program like Ethereal to check whatever is coming in from the server.
Have you checked the IRC RFC ? A quick google came up with http://www.irchelp.org/irchelp/rfc/rfc.html , actually http://www.irchelp.org/irchelp/rfc/index.html is better , pick your weapon :).
Once you know how each kind of message is delivered you can start building your classes for each sort of message, whenever you read data from the stream you can then try to Deserialize the data to whatever kind of object you need.
I've only fiddled with sockets and networkstreams in C# for a short while in college when we were building a FTP client and server. But the system is the same, I think the representation of the data is where your problem lies...
If not then consider this post useless :).
-
I'm not too concerned about what the actual incoming data is, at the moment. When I am, those links you provided will be the first place I go to read.
What I'm confused about is how to constantly display the lines that are incoming. I know when you're reading from a file you can use a WHILE statement to go through each piece of the stream unti you reach the end of the file.
I guess what I mean to ask.. is there a command or a function setting that basically goes:
ON incoming data..... process and display to the console.
Because I can't imagine setting up a method that will be running 100% of the time using a while loop to go through the StreamReader. Plus I've already tried something similar to this and it froze the program.
I also can't imagine having a dataRead() call after every little thing you do in the code. Plus I don't think that would work too well for when you're idle in a channel and want to watch what people type.
I hope that makes some kind of sense.
-
Makes perfect sense, create a method that will listen for any incoming data, and yes use a while loop in that method with a boolean value that represents wether the stream is valid or not ( connection alive or not ).
Then have this method run in a seperate thread, something like:
listenerThread = new Thread(new ThreadStart(connection.ReceiveInfo));
Where ReceiveInfo is the method with the while loop ;).
And in the ReceiveInfo the only thing you do is a readLine from the stream , whenever data is available it will be read, if not the thread will stop at that point untill data is available to be read.
Keep in mind that if you want to use a GUI afterwards you will probably going to need delegates, and while this is not necesarry , as in 2 threads can write to the same console without any problem. It will be a problem when you get to the GUI. If thread 1 creates the GUI part where info is going to be displayed, then thread 2 will not be able to modify its content. But then delegates come to the rescue, but still, I would first implement it that it works on the console. Then try to get it working with a GUI, but keep in mind when working on the console version that you are still working with threads.
( on a side note: I have encountered this thing , I don't know how it comes but anyway, when dealing with threads in Visual Studio 2005 I get alot of CrossThreadExceptions while debugging or even when simply running the program from the VS environment, as soon as I just open the .exe the Exceptions disappear... sometimes they happen, sometimes not, and untill now I've not found a way around. And because of this I've been chasing ghosts for along while I guess when debugging :( )
I also like to ad that the use of the using statement is very handy when dealing with streams, at least I find them good.
If you don't know about the statement yet : http://msdn2.microsoft.com/en-US/library/yh598w02.aspx
At least I won't forget to close any streams that are still open + I can clearly see the scope of where the stream will be available and not disposed of.
-
All right. That makes sense. I'll go and try to implement it here in just a little bit, but I had one more question first.
On sending streams you can use the flush command to empty the stream (unless I'm misundering what Flush really does). Is there an equilivant to reading the stream? Or will that not be necessary? I'm afraid that once I set up a while statement it'll continue to spit back the same lines over and over in an infinite loop.
-
The way I see flush in C# is like this:
Imagine a Stream is a tube. When I do stream.WriteLine("bla"); I merely pour content into the tube at one end. At the other end of the tube the receiver is waiting for this data. But it will never receive the data since there's this lid keeping the water ( data ) from being flushed. A explicit flush will make sure, the tube ( buffer ) is empty and all data that was still residing inside the tube is being send to whatever it is that is on the other end.
Basically simply put you don't need the flush at the receiving end in this situation, although there are cases where one would need to flush first.
Just the readline will be enough ( at least that was enough when implementing my FTP server / client application ).
-
Yeah, you're right. It works!
Thanks a lot. And I'll try to keep it in mind that when I switch to GUI it'll be a bit different.