TCP Client Server programming using TCP with C# .NET2
[20 mn of reading - published 6/3/2006 6:29:38 PM - Target : Confirmé]
|
   
|
Author
2. Coding the client
2.1. Architecture
For our project, we will create the following classes:
- 1 Windows Form
- 1 static connection class (since we only need 1 connection for the client)
The project consists in writing a chat client and server (non standard).
Development Steps
- Create the static connection class
- Send the different events to the user interface
- Create the Connect method which will launch the thread that will connect.
- Create a function that will loop within the thread while the connection is alive.
- Create the methods that will send and receive data
- Code the events that will be sent to the client
- Then, create the methods called by the client to send commands.
There are links to download the full project source code at the end of this article.
2.2. Connection to a server
This code is for the first part of the project (The connection, the event creation and the Thread launch execution)
Connection code and global structure (cleaned):
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;
namespace ChatClient
{
//Event content class
public class ConnectEventArgs:EventArgs
{
string message;
public ConnectEventArgs(string message)
{
this.message = message;
}
}
public static class Connexion
{
//Global Variable declaration
private const int MAX_PACKET_SIZE = 80;
private const char TERMINATION_STRING = '\0';
//Our Local Thread
private static Thread thread;
//Delegates & Events public delegate void ConnectionEstablishedEventArgs(ConnectEventArgs e);
public delegate void ConnectFailedEventHandler(ConnectEventArgs e);
public static event ConnectFailedEventHandler ConnectFailed;
public static event ConnectionEstablishedEventArgs
ConnectionEstablished;
//Methods to throw events
private static void OnConnectionEstablished(string message)
{
ConnectionEstablished(new ConnectEventArgs(message));
}
private static void OnConnectFailed(string message)
{
ConnectFailed(new ConnectEventArgs(message));
}
//Method used to connect public static void Connect()
{
//In case we had a previous connection.
if (thread != null )
if (thread.IsAlive)
{
try
{
if (thread != null)
{
thread.Join(400);//Sync. Threads
}
}
catch (Exception e)
{
//Exception if thread failed to unload in 400ms
Debug.WriteLine("Thread était lancé: "e.Message);
}
}
//Launch Thread
thread = new Thread(new ThreadStart(ManageConnection));
}
//Method executed by the new thread
public static void ManageConnection()
{
TcpClient client;
NetworkStream ns;
try
{
client = new TcpClient("localhost", 22);
}
catch (Exception ex)
{
Debug.WriteLine("Error : "+ex.Message);
OnConnectFailed("Connection failed: "+ex.Message);
return;//Exit thread.
}
//Connection Established.
OnConnectionEstablished("Connection Established");
ns = client.GetStream();
while(client.Connected)
{
//...
}
} |
At this time, the TCP connection remains open. Inside the loop structure we can use the Thread.Sleep(50) method to limit processor use. It will not interfere with data reception.
Once events are declared, you can use them by using methods like OnConnectionEstablished().
2.3. Receiving and sending data
2.3.1. Receiving data method
The SocketRead(NetworkStream ns) method on the bottom returns a string of what has been received by the TcpClient.GetStream() object.
Please note that these methods are common to our client and server and could be reused in every socket project.
///
/// Read NetWorkStream to retrieve data
///
///
public static string SocketRead(NetworkStream ns)
{
int iterator = 0;
int srb = 0;
int numBytesRead = 0;
string buffer;
buffer = String.Empty;
while (ns.CanRead)
{
try
{
byte[] b = new byte[MAX_PACKET_SIZE];
srb = ns.Read(b, 0, MAX_PACKET_SIZE);
buffer += Encoding.Default.GetString(
b, 0, srb).Substring(0, srb);
numBytesRead += srb;
if (srb == -1 || numBytesRead >= 64 * 1024) break;
//Quit loop when all understandable data have been received
//TERMINATION_STRING: end of message character
//This method is used to prevent packet fragmentation.
//We receive all the data arriving at the same time.
//You will have to separate received messages later
if (buffer.Length > 1)
if (buffer[buffer.Length - 1] == TERMINATION_STRING)
break;
if (iterator > 10000)
throw (new Exception("Connection Problem: Receive"));
}
catch (Exception ex)
{
throw (ex);
}
}
return buffer;
} |
Messages received are commonly fragmented and could be difficult to reassemble for the messages to be understood. Especially if we don't delimit them perfectly.
Here, we use a termination string (a char) to delimit messages. Check that at no moment you have used the termination string inside a message, otherwise you will have no possibilities to reassemble the message correctly.
Fragmented messages are received all together to make sure we reach at last the end of a message (Otherwise we will still have to wait for data).
We can encode messages in different ways in order to remove some termination string existence from messages. I used base64 supported by the framework.
To encode a message:
System.Convert.ToBase64String(Encoding.UTF8.GetBytes(str))+CST_PARAM_TERMINATION;
To decode the message: Convert.FromBase64String().
2.3.2. Sending data
The next code we'll present: the _send() method, allows to send data converted in byte[] to a NetworkStream.
Returns false when data have not been sent, plus the ConnectionFailed event (not necessary).
Returns true when data have been sent.
///
/// Sending data
///
/// NetWork Stream
/// message to be sent, byte[]
/// true if success / Exception then
public static bool _send(byte[] values,NetworkStream ns)
{
//Use:
//send(Encoding.ASCII.GetBytes("fsdofbsqgsfg"+TERMINATION_STRING));
int nextBuf, totalSent = 0;
if (ns.CanWrite)
{
for (int indx = 0; indx < (values.GetLength(0)); indx += MAX_PACKET_SIZE)
{
try
{ //Writing data ns.Write(values, indx, nextBuf = indx + MAX_PACKET_SIZE < values.GetLength(0) ? MAX_PACKET_SIZE : values.GetLength(0) - indx);
totalSent += nextBuf;
}
catch (Exception ex)
{
OnConnectFailed(ex.Message);
return false;
}
};
}
else
{
OnConnectFailed("Cannot write");
return false;
}
return true;
} |
2.4. Stop the Thread and Socket
Forcing a Thread to stop is not advised (Abort method) because the Thread location in the method is unknown. But you should use this method if the thread cannot exit by itself.
You have to create a method that will make this Thread stop (shared variable set to a different value) and that will execute a Thread.Join(int_time_in_ms).
Eventually, don't forget to close both communication objects tcpClient.Close() and ns.Close() (for the NetworkStream of our examples).
|