You can find updates here.
Introduction
This is a simple TCP/IP chat sample. The solution is made up of three different projects. The main project is "Listener", where you can find the core of this program. In Rete.cs, there are the main methods used to make this program work.
Please note that source code comments (//) are written in Italian.
The most important class is Poller
, in rete.cs. In Main.cs is created a Poller
object, that is used to connect, poll and write the socket.
In Main.cs is started a thread that has to refresh the GUI; the thread executes:
public void StartGUIRefreshThread()
{
ThreadStart GuiRefreshDelegate=new ThreadStart(this.GUIRefreshThread);
GUIRefresh= new Thread(GuiRefreshDelegate);
GUIRefresh.Name="GUIRefresh";
GUIRefresh.Start();
}
This is a simple example of using threads. Starting a new thread means running different portions of the code at "the same time". While a thread is running a loop, the "main program" which is another thread executes other instructions.
A ThreadStart
object contains a reference to a method that has to be run when the thread starts. If this function does not contain a loop, the thread will stop after a while. If the referenced method contains an infinite loop, the thread will be running as long as the application.
GUIRefresh.Start();
executes the "this.GUIRefreshThread()
" method, as specified in "new ThreadStart(this.GUIRefreshThread);
".
My "StartGUIRefreshThread
" method starts a thread which contains an infinite loop:
public void GUIRefreshThread()
{
int localDelay, totalDelay=1200, newDataPresentIconDelay=300;
while (true)
{
...
Thread.Sleep(localDelay);
}
}
This checks the Poller
status, and displays the appropriate output to the user.
The Poller
object handles the TCP connection, using a "raw" Socket
object, initialized with the "InizializzaTX()
" method if the current application is running as a client, or with "InizializzaRX
" if the application is running as a server (listener).
Server case:
public bool InizializzaRX()
{
if (this.IsListen)
this.FermaListening();
this.ListenSocket= new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
try
{
this.ListenSocket.Bind(LIPE);
this.ListenSocket.Listen(10);
this.IsListen=true;
return true;
}
...
This code creates a new socket (this.ListenSocket
) which is bound to the listening IP address and port (LIPE).
Now the socket is properly configured, but it's not "active". We have to start it to begin listening with the <Socket>.Accept()
method, which puts the socket in a "wait state" for client connections.
The propriety <Socket>.Blocking
determines whether the execution will stop waiting for client connections or not (true
=stop). I use blocking=true
. Note that if you use blocking=true
in the main thread, your application will hang until a connection is accepted.
private void PollingRX()
{
string s="Inizio";
while (s!=MSGTag.ConnectionStop+"\n")
{
if (this.UsedSocket==null&&this.ListenSocket!=null)
{
this.DebugRefresh("PollingRX: fermo in accept() su"+ this.LIPE.Port);
this.IsListen=true;
this.UsedSocket=this.ListenSocket.Accept();
this.DebugRefresh("PollingRX: Accept() eseguita");
this.IsConnectedAsRX=true;
this.PushString(MSGTag.UserName+this.LocalName);
this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
this.RTM.LocalIP=this.LIPE.ToString();
this.ControlClear();
}
s=this.CheckAndRetrive();
int n=0;
if (s!=null)
n=s.Length;
this.DebugRefresh("PollingRX: eseguito ("+n+" bytes)");
System.Threading.Thread.Sleep(500);
}
this.DebugRefresh("PollingRX: ricevuto EXIT");
this.KillRX();
}
When a client makes a connection, the Accept()
returns a new socket, connected to the client, which is used to transfer data, using my CheckAndRetrive()
(read from socket) and PushString()
(write to socket) methods. See below to read pertinent code.
This loop continues until a "stop string" is received from the client.
Client case:
public void InizializzaTX()
{
if ((!this.IsConnected))
{
try
{
this.UsedScocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
this.UsedScocket.Connect(this.RIPE);
this.IsConnectedAsTX=true;
this.PushString(MSGTag.UserName+this.LocalName);
this.ControlClear();
this.RTM.RemoteIP=this.UsedScocket.RemoteEndPoint.ToString();
this.RTM.LocalIP=this.UsedScocket.LocalEndPoint.ToString();
}
...
This is the dual of RX: a new socket is created, and a connection to a remote IP and port (RIPE) is created.
private void PollingTX()
{
string s="inizio";
while (s!=MSGTag.ConnectionStop+"\n")
{
if (this.UsedScocket!=null)
{
s=this.CheckAndRetrive();
}
int n=0;
if (s!=null)
n=s.Length;
this.DebugRefresh("PollingTX: eseguito("+n+" bytes)");
System.Threading.Thread.Sleep(500);
}
this.DebugRefresh("PollingTX: ricevuto EXIT");
this.KillTX();
}
Very similar to RX... once the socket is connected, communication is "symmetric".
Writing and Reading data
This is how I get a string from the socket (CheckAndRetrive()
uses this function to get data from the socket):
public static string GetStringFromSocket(Socket s)
{
Byte[] recBytes=new Byte[10001];
int i=0;
string st=null;
if (s.Available>0)
{
i=s.Receive(recBytes,0,s.Available,SocketFlags.None);
char[] ac=Encoding.UTF8.GetChars(recBytes,0,i);
foreach (char c in ac)
{
st+=c.ToString();
}
}
return st;
}
The socket needs to be a connected socket; you have to use a byte array to put in the received data. Then you have to encode it with a encoding mechanism, the same is used to decode the data. I use UTF8 Encoding.
This reads at most a 10001 bytes block from the socket. To read more bytes, a loop is required, something like:
while (s.Available>0){...}
This is how to put a string in a socket:
public static int PutStringInSocket(Socket s, string msg)
{
Byte[] sendBytes=Encoding.UTF8.GetBytes(Framing.IncapsulaMSG(msg));
if (s.Connected)
return s.Send(sendBytes);
else return 0;
}
Framing.IncapsulaMSG()
is simply a method that writes tags in a message to make it simple to interpret it, and returns a string.
Polling Threads
Inside Poller
are executed other threads, responsible for receiving/checking and sending data over the TCP connection. Loops inside PollingRX()
and PollingTX()
run in two different threads.
public bool StartPollingTX()
{
try
{
if (!this.IsRunningPollingTX)
{
ThreadStart TXPollingDelegate = new ThreadStart(this.PollingTX);
TXPolling= new Thread(TXPollingDelegate);
TXPolling.Name="TXPolling";
TXPolling.Start();
this.IsRunningPollingTX=true;
this.DebugRefresh("PollingTX: (Start) eseguito");
}
return true;
}
catch
{
return false;
}
}
This is an example that shows how the "Transmission" thread is started. (It runs "this.PollingTX
" described above.) The Settings tab is stored in a separate DLL, and so is the config utility. Settings are stored in an XML file.
I know there are many silly things, but this was intended only as an example.
I hope you enjoyed the article.