An example of the NetComm library usage in my robot-battle school project,
this is a screenshot of the server, 4 clients are connected to it.
Introduction
The NetComm library allows you to connect multiple clients to the same server. This allows you to do the following:
- Transfer personal messages to each other
- Send a message to all of the users in the same server (public messages)
- Communicate with each client using only ID (a unique string), without the need of knowing each other IP, the clients only need to know the host IP address.
The NetComm library handles all of the complicated work, it works in a separate thread from your application UI - the NetComm library will raise an event when a new message arrived.
Why is this useful? Well, this allows you to create a multi-user chat easily - or any other kind of application that needs to handle multiple users communication.
With the use of this library, you can create this kind of complicated communication easily.
This article is written with C# but I attached a VB.NET example for VB users (my favorite language). This library was built in VB.NET.
Background
I developed this library for my school project - I created a server that keeps track of 2 robots which are being remotely controlled by 2 remote PCs. This project simulates a robot-battle between 2 teams.
I connected the clients (4 clients) to the same server. The server handled all the game rules, each robot life points, bullets, starting ammo, weapons. The main role of the server is making both of the teams follow the same rules. The server is a place where game calculations were taken. It's pretty much the "center" of the game, viewers could look at the server UI and see the scoreboard, each team's robot life points, bullets remaining and more... The NetComm library made it a lot more easier for both of the teams to communicate.
The Concept (or, How It Works? For More Advanced Users)
I built this library with VB.NET. I used the TCPClient and TCPListener (System.Net.Sockets
) which Microsoft created as a base for this library. The whole library relies on these 2 classes.
To let the UI stay responsive, I created the whole communication on another thread, when a new message arrived, I needed to raise an event to the UI thread to notify him about the new message. I used the SynchronizationContext class (System.Threading
) to get the UI thread context and post the new messages to the UI thread message queue.
To be able to use IDs (so clients could transfer messages using strings, like names without the need knowing of IPs), I used the AsciiEncoding class to convert the bytes into string
s for the unique IDs.
Read Before "Using the Code" (Important Information)
In this library, all clients connect to the same server which means the following:
- All clients should use the same port as the server uses.
- All clients must know the server IP address.
- All clients can communicate between one another using IDs, an ID describes who the client is that the message was pointed to. It allows clients to communicate with one another. For example: an ID could be a name like "Jack", if a client with an ID "Jack" wants to send a message to "ElvisPresley" he simply uses his ID ("ElvisPresley") to send him the message. Those messages are being transferred via the host, which manages to analyze them and sends them to the recipient:
Jack wants to send ElivsPresley a message, he uses his ID (ElivsPresley)
to send that message, the host receives the message,
analyze it and sends it over to ElvisPresley
- The host and clients can send and receive messages. They can send private messages to each other. The host can broadcast messages (send a public message to all participating clients).
- The host ID is always "" (null), to send a private message to the host we use the SendData method with a "" string. (If you still don't understand, you will get it in the end).
- The NetComm library can send and receive an array of bytes. If you are planning to use this library for chat purposes, you can use the following function to convert bytes to string and string to bytes:
string ConvertBytesToString(byte[] bytes)
{
return ASCIIEncoding.ASCII.GetString(bytes);
}
byte[] ConvertStringToBytes(string str)
{
return ASCIIEncoding.ASCII.GetBytes(str);
}
These 2 functions use the AsciiEncoding class, which uses the Ascii table to convert a char
to its corresponding numeric value. (Each byte represents a character in a string
).
The VB.NET example attached - a C# example is also attached
Using the Code
To start up, create your project and do the following:
- Open up your project.
- Right click on "References" and "Add Reference".
- Select the "Browse" tab and select the extracted "NetComm.dll".
- Accept all dialogs.
Creating the Host
To start our first step, we need to create a host that listens to any incoming client. The host himself can send and receive messages.
On your Form
class global declaration, add the following declaration:
NetComm.Host Server;
On the Form_Load
event handling method, add the following code:
Server = new NetComm.Host(2020);
Server.StartConnection();
2020 is the port number, you can use whatever port number you want, but make sure the client uses the same one.
The 2nd line tells the Server
object to start listening to any incoming client.
The Host Class Includes the Following Events
onConnection(string id)
Every time a user connected, this event will raise to inform the application. The id argument includes the ID of the client connected.lostConnection(string id)
Every time a user disconnected, this event will raise to inform the application. The id argument includes the ID of the disconnected client.errEncounter(Execption ex)
Every time an error occurs, this event will raise to inform the application. The ex argument includes the exception object.ConnectionLost()
This event will raise when the server connection was closed (stopped listening).DataReceived(string ID, byte[] Data)
Every time a message arrived, this event will raise to inform the application. The ID argument holds the ID of the client who sent the message. The Data argument holds the array of bytes that client sent.DataTransferred(string Sender, string Recipient, byte[] Data)
Every time a message was transferred from client to client (using the Client.SendData
method), this event will raise to inform the application. The Sender holds the ID of the client who sent the message. The Recipient holds the ID of the client who receives the message. The Data argument holds the data being transferred from client to client.
I created a textbox
, and changed its name
property to "Log
", this textbox
will log everything that occurred on the server.
Let's add the following lines to the Form_Load
event handling method (below the previous lines):
Server.onConnection += new NetComm.Host.onConnectionEventHandler(Server_onConnection);
Server.lostConnection += new NetComm.Host.lostConnectionEventHandler
(Server_lostConnection);
Server.DataReceived += new NetComm.Host.DataReceivedEventHandler(Server_DataReceived);
We need to create some methods that will handle this events, for the onConnection
event I have added the following method:
void Server_onConnection(string id)
{
Log.AppendText(id + " connected!" +
Environment.NewLine);
}
Every time a user join the room, your application goes to this method, and will update the log when a user enters the server.
The log keeps being updated, to notify us about new users joining the room. If you noticed the onConnection
method has the id argument, this argument includes the ID (name \ unique string) of the user who joined the server.
Now let's add the Server_lostConnection
method:
void Server_lostConnection(string id)
{
Log.AppendText(id + " disconnected" +
Environment.NewLine);
}
Everytime a user leave the room, your application will go to this method, and will update the log when a user leaves the server.
Let's create the DataReceived
method, to handle new messages:
void Server_DataReceived(string ID, byte[] Data)
{
Log.AppendText(ID + ": " + ConvertBytesToString(Data) +
Environment.NewLine);
}
This is a little bit tricky. Everytime a new message was received, your application will go into this method. If Jack (Jack is the ID) sent us a message saying "Hello!" the Log textbox will get updated with the following text: "Jack: Hello!".
But why does the DataReceived method have the Data
variable set to bytes? Why is it not a string
? Well, as said, the NetComm
class can transfer bytes from client to client, so we use the ConvertBytesToString function. This function is written at the top of this article.
As I mentioned earlier, the client can send messages and receive messages too, in order to send a message, we use the following method:
Server.SendData("Jack", Data);
As mentioned, "Jack
" is the ID of the client we want to send the data to. The Data variable is an array of bytes we want to transfer. If you want to transfer a string
, you should use the AsciiEncoding
class to convert the bytes
into a string
. Or just keep reading, we will build a function that translates a string
into an array of bytes
later on.
To send a message to all participating clients, we use the Brodcast
method:
Server.Brodcast(Data);
Again the Data
variable is a set of bytes we want to send to each client.
To kick a specific client, we use the DisconnectUser
method:
Server.DisconnectUser("Jack");
To get a list of all connected users, we use the User
property:
List<string> usersList = Server.Users;
Every time you close the application, you should close the Server
object connection. On your Form_Closing
event handling method, add the following code:
Server.CloseConnection();
The Host
class (or the Server
object we created) has other properties and methods we have not discussed about, but you can always explore the Host
class and find out for yourself.
Creating the Client
The second step is to create the client, if you haven't read the "Creating the host" part of this article, you might consider going back - We will use some of the information we gathered earlier on the client side too.
Add the following variable to your client application global declarations:
NetComm.Client client;
The client variables holds all of the communication handling, events, and methods.
Add the following line to the Form_Load
event handling method:
client = new NetComm.Client();
The Client Class Includes the Following Events
Connected()
When the client connected successfully to the host, this event will raise to inform the application.Disconnected()
When the client got disconnected from the host, this event will raise to inform the application.errEncounter(Execption ex)
Every time an error occurs, this event will raise to inform the application. The ex argument includes the exception object.DataReceived(byte[] Data, string ID)
Every time a message arrived, this event will raise to inform the application. The ID argument holds the ID of the client who sent the message. The Data argument holds the array of bytes that client sent.
We need to handle the client object events (Connected
, Disconnected
, DataReceived
, etc.). This allows us to respond to them and update our application according to them, put the following code on the Form_Load
event handling method:
client.Connected += new NetComm.Client.ConnectedEventHandler(client_Connected);
client.Disconnected += new NetComm.Client.DisconnectedEventHandler(client_Disconnected);
client.DataReceived += new NetComm.Client.DataReceivedEventHandler(client_DataReceived);
Now we should add the methods which will respond to those events.
Let's create the Connected
method:
void client_Connected()
{
Log.AppendText("Connected successfully!" +
Environment.NewLine);
}
Every time the client connected successfully, your application will go into this method, then the log textbox
will update its text saying "Connected successfully!
"
Now we will add the Disconnected
method:
void client_Disconnected()
{
Log.AppendText("Disconnected from host!" +
Environment.NewLine);
}
Every time the client got disconnected from the host, your application will get into this method, then the log textbox
will update its text saying "Disconnected from host!
".
Let's add the DataReceived
method:
void client_DataReceived(byte[] Data, string ID)
{
Log.AppendText(ID + ": " + ConvertBytesToString(Data) +
Environment.NewLine);
}
Every time the client will receive a new data, your application will go into this method, then the log textbox
will update its text saying the ID of the client who sent the data and the string
of the data. The ID argument holds the ID (unique string
) of the client who sent the data. The Data argument is an array of bytes containing the data that client sent.
The ConvertBytesToString function is written at the top of this article.
We need to get the IP address of the host and the ID that the user wants, to do that we create a simple form to gather the information, but only for the purpose of this article, we will use the ID "Jack
" and the IP address "localhost
", which means the IP address of the computer we are using (we will run the example on the same computer).
Because this is an example, we will put up the communication setup code on the Form_Load
event handling method, but in your project you should create a connection form that will gather this information.
Setting up the Connection (Connecting the Host)
client.Connect("localhost", 2020, "Jack");
If the connection was successfully made, your application should go to the Connected
method we built earlier.
Sending Messages is the "main core" of the communication. This is why we needed to create a communication in the first place. Let's say we want to send a string
to another client, to do this, we should add a textbox
on our project, I named it "ChatMessage
". Now create a button, I named it "SendButton
". When the user finished writing the message at the "ChatMessage
" textbox, he will click on the "SendButton
" we created earlier. So we should write this code at the SendButton_Click
event handling method:
private void SendButton_Click(object sender, EventArgs e)
{
client.SendData(ConvertStringToBytes(ChatMessage.Text), "Jack");
}
Every time we will click on the SendButton
, we will send a private
message to "Jack
" with the text appears on the textbox
. Remember we need to convert the string
into an array of bytes
, so we should use the following function:
byte[] ConvertStringToBytes(string str)
{
return ASCIIEncoding.ASCII.GetBytes(str);
}
Read the explanation at the top of this article to know more about this function.
To send a private
message to the host, we simply use the SendData
method:
client.SendData(ConvertStringToBytes(ChatMessage.Text));
And finally, to close up the communication, we use the following method on our Form_Closing
event handling method:
if (client.isConnected) client.Disconnect();
Speeding up the Communication
To speed up the communication, you can use the NoDelay
, SendBufferSize
and ReceiveBufferSize
properties. Here is a short code that should speed up the communication:
Server.SendBufferSize = 400;
Server.ReceiveBufferSize = 50;
Server.NoDelay = True;
Put this code on your Form_Load
event handling method. Both host and client should use this code lines in order to make the connection faster.
Making Clients Broadcast Messages
I only now noticed that I haven't had any "Broadcast
" method with the Client
class. Here is a way of making clients broadcast messages which I haven't yet tested.
As you read, the host is the only one capable of broadcasting messages, in order to make a client broadcast a message we can send the message over to the host (a private message), and tell the host to broadcast that message to everyone. One way is just by adding the following code to the host DataReceived
event handling method:
void Server_DataReceived(string ID, byte[] Data)
{
foreach (string clientID in Server.Users)
{
if (ID != clientID) Server.SendData(clientID, Data);
}
}
In other words (not code "words"), we simulate the host Broadcast
method, but this time we ignore the client who sent the message, so he won't receive the message he sent.
The client uses the SendData
method to send the message to the host:
client.SendData(Data);
An ID Issue - The "D" Character
When using names like "David
" or "Dan
", the NetComm
library does not work well for some reason. For now, the only solution is to use other IDs that do not start with the "D
" character.
Points of Interest
This library was targeted for my school project (a robot battle), but I decided to share it with everyone.
Writing it was fun, and challenging (getting to know the TCP protocol for the first time). I wrote this library to create a communication between 5 computers, one of them is the server which manages the game rules, life points, and more... Other 2 computers control the 2 robots from another room, all of the messages are being transmitted through the NetComm
library. Use this library for whatever you want!
History
- 15.10.2010 - Added the ID issue information, added images
- 16.10.2010 - Fixed wrong information, added demonstration picture, removed extra information, added a reference link for each class