Abstract:
WinChat For .NET is a simple peer-to-peer chatting program that functions very similarly to the WinChat program provided by Windows 2000. It provides all the functionalities that the original WinChat program provides. WinChat For. NETs GUI (Graphical User Interface) is a bit different than the original one simply because of personal taste.
WinChat For .NET is based on many basic yet powerful techniques provided by the .NET platform, including Multi-Threading, Event-Driven, Windows Form Control and Socket Programming, etc.
Usage of the program:
You can find the WinChat.exe executable in the directory:
<Installation Dir>\WinChat For .NET\WinChat\bin\Release
WinChat For .Net Platform is based on .Net platform beta 2 and is written in C#. You can use the program in any way you want. However, I provide absolutely NO GUARANTEE in any sense for this program.
In order to chat to a person, you have to notify him/her before the chat can begin. The notification can be done by typing the hostname or IP address of the workstation (where the person is located) you want to chat into the Remote Sides Hostname or IP Address TextBox, and press the Notify button. 4 possible things can happen after this:
- The called person is not involved in another chat session and is willing to chat with you. He/She will acknowledge your notification, and the chat session can now begin;
- The called person is not involved in another chat session, but he/she is not willing to chat with you. He/She will acknowledge your notification negatively. The chat session cannot begin. However, you can try the notification again, or you can try to notify another person;
- The called person is already involved in another chat session. You will get a negative acknowledgement in this case. You have to attempt to chat with this particular person again later;
- The remote side doesnt respond to the notification within 30 seconds. You will get a time-out acknowledgement. The chatting session cannot begin.
If you get a positive acknowledgement from the called person, and want to start chatting, you can start typing your message (less than 512 characters per message) in the Type Your Message Here TextBox, then press the Send button. You will see your messages in the Your Message TextBox, and the called persons messages in the Remote Sides Message TextBox.
After the chatting is done, either side can terminate the call by pressing the Hangup button. After that, both sides can start another chat session with any other person.
When you are finished using this program, you can exit the program by pressing the Exit button.
Basic Architecture of the program:
The whole architecture of WinChat For .NET is very simple. All it involves is 3 classes:
- TcpUdpServer -- handles all the server functionalities like listening to connections and receiving messages;
- TcpUdpClient -- handles all the client functionalities including call request and call initiation;
- WinChatFormLibrary -- handles all the Windows Form Controls, and also acts as a middle agent for the communication between TcpUdpClient and TcpUdpServer.
This project makes heavy use of the Multi-Threading, Delegate and Socket programming techniques in communication. It also depends on some protocol-like handshakes to setup the chatting sessions. Fig. 1 depicts the procedures a call is set-up.
When either side of the party wants to initiate a call request, the following procedures will take place before both parties can start the chat session.
- The calling side gives the remote sides (called sides) hostname to the WinChatFormLibrary and press the Notify button;
- The calling sides TcpUdpClient sends the Notify Request to the called sides TcpUdpServer;
- The called sides TcpUdpServer returns the Notify Accept message back to the calling side;
- The calling sides TcpUdpClient sets up a TCP connection to the called sides TcpUdpServer;
- The calling sides TcpUdpClient notifies its own TcpUdpServer that it is already involved in a chat session (through raising an event), so that the TcpUdpServer will reject all the additional call requests until this on-going call ends;
- The called sides TcpUdpServer notifies its own WinChatFormLibrary that a call has been setup from the calling side (through raising an event);
- A delegate function in the called sides WinChatFormLibrary is invoked by the call initiation event;
- The called sides TcpUdpClient, invoked by the delegate function in its own WinChatFormLibrary, will setup a TCP connection to the calling sides TcpUdpServer based on the hostname obtained from the TcpUdpServer.
It is noteworthy that two TCP connections are created for a call, one for each direction. I could have done it with only one connection for a call, but I chose this way. The reason is that the architecture of the program contains one client and one server that are quite independent to each other, I feel that its more logical to have both sides (the calling and the called sides) do similar things to each other (kind of like a mirror to each other). Thus, I decided to have each side create one TCP connection to the other.
Fig. 2 depicts the procedures a call is terminated.
When either side wants to terminate the call, the following procedures will take place (Note: either the calling side or the called side can terminate the call at any time):
- When the terminating side wants to terminate an on-going call, he/she will press the Hangup button in the WinChatFormLibrary;
- The terminating sides TcpUdpClient will send a Notify Hang-up Request message to the terminated sides TcpUdpServer;
- The terminated sides TcpUdpServer will reply with a Notify Hang-up Accept message to the terminating sides TcpUdpClient;
- The terminating sides TcpUdpClient will send a TCP message Notify Last Message to the terminated sides TcpUdpServer, so that the terminated sides TcpUdpServer will close the TCP socket for this connection;
- The terminating sides TcpUdpClient will now close the TcpClients socket;
- The terminating side will notify its own TcpUdpServer (through raising an event) that the on-going call has ended, so that the TcpUdpServer can accept incoming call requests;
- The terminated sides TcpUdpServer will notify the WinChatFormLibrary that the terminating side has already ended the on-going call (through raising an event);
- A delegate function in the WinChatFormLibrary will be invoked by the call termination event;
- The terminated sides TcpUdpClient, invoked by the delegate function in its own WinChatFormLibrary, will send the TCP message Notify Last Message to the terminating sides TcpUdpServer, so that the terminating sides TcpUdpServer will close the TCP socket for this connection;
- The terminated sides TcpUdpClient will now close the TcpClients socket;
Thus, the call setup and termination can easily be handled by a few very simple steps based on the Socket and Delegate facilities provided by the .NET architecture. Its noteworthy that, although the above steps are described in procedural form, some of the steps actually happen simultaneously.
A few points to discuss:
In this simple project, I have made use of only a few namespaces:
System.Windows.Forms;
System.Net.Sockets;
System.Threading;
System.Timers;
I found that these namespaces really make the project much simpler than it would have been if it is done in C++. In addition, the delegate concept also makes the event-driven model much easier to handle than C++.
Most of the program flow and techniques used are already documented inside the code as comments. However, there are a few points that I think will be worth discussing here.
First of all, I think the System.Net.Sockets provides a lot of great classes that make the Socket programming a joy to work with, especially the TcpListener, TcpClient and UdpClient classes. They take care of most of the low-level socket programming under the hood and make their operation transparent to the client code. For example, to create a TCP server and make it listen to the port TCP_PORT, all I have to do is only 1 line of code:
TcpListener tcpListener = new TcpListener(TCP_PORT).
And to create a TCP client and make a connection to the TCP server SERVER_NAME at TCP_PORT, again, 1 line of code will do the job:
TcpClient tcpClient = new TcpClient(SERVER_NAME, TCP_PORT).
Although all these classes still perform the traditional socket operations under the hood, hiding all of them from the client application does help simplify the overall program structure. However, for those programmers who are familiar with Winsock programming, I still suggest to use the traditional Socket class because its still more powerful then these convenient siblings.
Second, this project has made use of the Multi-Threading facilities provided by the .NET platform. The UDP server and the TCP server are running on different threads so that they can receive and process incoming messages simultaneously. The following code segments show how the TCP and UDP threads can be started and aborted:
public void start_servers()
{
//Starting the TCP Listener thread (StartListen).
sampleTcpThread = new Thread(new
ThreadStart(this.StartListen));
sampleTcpThread.Start();
//Starting the UDP Notify thread (receiveNotify).
udpNotifyThread = new Thread(new
ThreadStart(this.receiveNotify));
udpNotifyThread.Start();
}
To start the server threads:
start_servers();
To abort them:
sampleTcpThread.Abort();
udpNotifyThread.Abort();
This is it! Of course, you might want to add some try/catch blocks to handle some exceptions that might happen in the codes.
The third thing that I want to discuss is the delegate model of event handling. In this project, there are quite a few places that I can make use of delegates. For example, in Fig 1, step 6, after the calling side connects to the called sides TcpUdpServer, the TcpUdpServer will raise an event. The event will then call the delegate defined in the WinChatFormLibrary class, which will, through the TcpUdpClient, make a connection to the calling sides TcpUdpServer. Here is the code segment that does what I described.
In WinChatFormLibrary class, we first have to make the TcpUdpServer listen to the event peerNotify():
TcpUdpServer.peerNotify += new
TcpUdpServer.NotificationHandler(this.OnPeerNotify);
Defining the OnPeerNotify delegate (with most details
deleted):
public void OnPeerNotify()
{
this.tcpUdpClient.createTcpClientConnection(remoteHostName};
}
In the TcpUdpServer, all we have to do is to call the event peerNotify() when it receives a connection from the remote peer. And its done!
Last but not least, I also want to briefly mention the timer handling in the .NET platform. I have made use of a few timers in this project. To start a timer, all I have to do is just 2 steps:
1. Add the event listener to the code where you want to start the timer:
receiveNotifyTimer = new
System.Timers.Timer(30000);
receiveNotifyTimer.Elapsed += new
ElapsedEventHandler(this.OnTimedEvent);
2. Define the delegate OnTimeEvent():
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
//Do something when the timer expires.
}
And stopping a timer is just a matter of calling someTimer.Stop(). Thats it!!
Conclusion:
I have used WinChat in some occasions before, and thought that it might be a good starting point to learn C# by rewriting this little utility with it. I hope this program will be useful for someone who needs it, or at least useful as a learning tool.
Many articles I have read in these few months have compared C# to Java or C++ regarding to the easiness of writing useful programs, I am getting very curious about this language. After writing C# codes for a while (basically since Beta 2 was released), I am following in love with it. The class library is incredibly useful. And learning to write codes with this totally new language is really a joy ride. Although the performance is still an issue that Microsoft has to figure out before the final release, I think the Beta 2 release is already pretty close to what a next generation language ought to be like.
This is the first serious project that I have written in C#. Although I have tried my best to QA the program myself, its still bound to be buggy. Please let me know of any bugs, and any suggestions you have for me to improve the program.
Finally, I have to thank many people from the dotnet discussion group and the dotnet newsgroup who have answered many of my questions. Those answers are really helpful, and they really ease my .NET learning in many ways.
Thanks very much in advance for any suggestion you have for me. I can be contacted at [email protected].