1 Présentation
Cet article vous propose aujourd’hui de créer une nouvelle classe de transfert de fichiers sur un réseau Tcp, à l’aide des classes présentes dans le Framework 2.0.
Pour illustrer notre exemple, vous trouverez deux applications consoles simplistes, en plus de notre assembly, permettant de tester celle-ci afin de télécharger un simple fichier.
Le pré-requis nécessaire est d’avoir déjà manipulé les technologies liées à la plateforme .NET.
Une connaissance des délégués est nécessaire pour appréhender les appels de procédures asynchrones.
Aucune connaissance particulière sur le Framework .NET 2.0 n’est obligatoire, puisque nous détaillerons chacune d’elles au cours de cet article.
Quelques notions de transfert réseaux faciliteront votre compréhension de l’ensemble.
Notre assembly BewisePeerToPeer sera composée de deux classes :
- BewisePeerToPeer.Server
- BewisePeerToPeer.Client
Cet article a été réalisé sur les versions suivantes :
§ Framework .NET version 2.0.50727
§ Visual Studio 2005 version 8.0.50727.42 (RTM.050727-4200)
2 BewisePeerToPeer.Server
Notre Classe Serveur assurera deux rôles principaux :
· Assurer l’autorisation d’un client à se connecter, lorsque celui-ci en fera la demande. Nous partirons du principe dans notre exemple que le client est toujours autorisé.
· Envoyer via un flux réseau, en l’occurrence, le fichier demandé par notre client.
Le principal objet utilisé dans cette assembly est la classe TcpListener.
TcpListener fournit des méthodes simples qui écoutent et acceptent (ou refusent) des connexions entrantes sur un port et une adresse Tcp donnée.
TcpListener peut travailler en mode asynchrone, et nous utiliserons bien entendu cette méthode, plus souple à l’utilisation.
2.1 TcpListener
Avant d’aborder les flux réseaux, nous allons voir comment gérer le modèle asynchrone de notre TcpListener.
J’ai pris le soin au préalable de protéger mon listener en créant une propriété, comme suit :
// Listener de type Tcp
private TcpListener listenerTcp;
/// <summary>
/// Accesseur du Listener de type Tcp
/// </summary>
public TcpListener ListenerTcp
{
get{return listenerTcp;}
set{listenerTcp = value;}
}
Une fois notre listener démarré [méthode Start()] il est nécessaire de récupérer les flux clients.
Ceci est réalisé grâce à la méthode AcceptTcpClient().
Bien entendu, il existe la version asynchrone de cette méthode qui n’est autre que .BeginAcceptTcpClient()
2.1.1 Initialisation
public void Start(Int32 port, String iPAddress)
{
try
{
IPAddress localAddr = IPAddress.Parse(iPAddress);
ListenerTcp = new TcpListener(localAddr, port);
// Début d'écoute des requetes clients.
ListenerTcp.Start();
this.StartListenToTcpClient();
}
catch (SocketException e)
{
throw e;
}
}
2.1.2 Lancement asynchrone
/// <summary>
/// Débute l'écoute d'une connexion Tcp Client
/// </summary>
public void StartListenToTcpClient()
{
// Pointeur asynchrone
IAsyncResult iar = null;
//Attente du prochain message
iar = ListenerTcp.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), this);
}
/// <summary>
/// Asynchrone d'accepation de connexion
/// A la fin de la récupération, je relance une autre écoute
/// </summary>
public void AcceptTcpClientCallback(IAsyncResult ar)
{
try
{
…
}
}
2.1.3 Réception du flux client
Maintenant que notre Listener est capable de récupérer un flux client, il est nécessaire de traiter la demande et de renvoyer une réponse (un flux en guise de réponse).
Le transfert de flux s’effectue via l’objet NetworkStream.
La classe NetworkStream fournit des méthodes de transfert (réception et envoi) de données au travers d’un flux réseau.
Dans notre exemple, nous allons récupérer le flux client, qui en entête, nous donnera l’ordre à exécuter.
Les ordres client seront simples :
· Demande d’autorisation de connexion
· Demande de transfert d’un fichier
En réponse, et via notre classe NetworkStream, nous enverrons le flux de retour.
Mais, tout d’abord, la réception du flux client :
// Récupération du flux du poste client
NetworkStream stream = client.GetStream();
// Lecture du flux entrant
Int32 i = stream.Read(bytes, 0, bytes.Length);
// Décodage
data = System.Text.Encoding.UTF8.GetString(bytes, 0, i);
// Découpage
tabArgs = data.Split(new char[] { '|' });
// Analyse de l'ordre à éxécuter
// demande d'authentification
if (data.StartsWith("Request", StringComparison.CurrentCultureIgnoreCase))
{
this.CheckForAutorisations(tabArgs, stream, server);
}
else
{
this.SendFile(tabArgs, stream, server);
}
// Fermeture de la connexion courante
client.Close();
2.1.4 Envoi du flux de réponse
Une fois notre flux reçu et la demande analysée (autorisation ou demande de téléchargement), il ne reste plus qu’à utiliser notre flux réseau déjà ouvert par le client, pour renvoyer notre réponse.
Je vous laisse regarder les sources de l’article pour avoir un aperçu de la réponse, lors de l’authentification ; nous allons ici détailler le processus d’envoi du fichier.
Le principe reste relativement simple : une fois récupérés les arguments de l’entête du flux client (qui contient le fichier à télécharger), il suffit d’ouvrir un flux fichier en lecture, avec l’objet FileStream, et de le lire à l’aide d’un BinaryReader.
J’utilise la méthode .Read() de mon Reader binaire pour remplir un tableau de Byte, que j’envois en retour sur mon flux NetworkStream.
//Nom du fichier à récupérer
FilePath = tabArgs[1];
// Vérification de l'existence du fichier
if (!System.IO.File.Exists(FilePath))
{
throw new System.IO.FileNotFoundException("Bewise Peer to Peer : Fichier non trouvé sur le serveur", FilePath);
}
// Récupération des informations relatives au fichier
System.IO.FileInfo fileInfo = new System.IO.FileInfo(FilePath);
// Ouverture d'un flux vers le fichier souhaité.
fs = new FileStream(fileInfo.FullName, FileMode.Open);
// Création d'un Reader Binaire sur ce flux
br = new BinaryReader(fs, Encoding.UTF8);
//Transformation en tableau de Byte
Byte[] bb = new Byte[1024];
while (br.Read(bb, 0, bb.Length) != 0)
{
// Envoi du fichier en retour
networkStream.Write(bb, 0, bb.Length);
}
3 BewisePeerToPeer.Client
Notre Classe Client assumera deux rôles principaux :
· Demander l’autorisation de connexion au serveur.
· Envoyer le nom du fichier à télécharger et le récupérer.
Ici nous utiliserons principalement la classe TcpClient, qui permet de se connecter à un server, pour peu que celui-ci soit démarré.
Il suffit alors de faire une demande et d’accepter en retour le flux renvoyé par le serveur.
Il faut bien sûr écrire ce flux sur un espace disque accessible, et dont vous disposez des droits d’accés.
3.1 TcpClient
Une fois connecté au serveur, le TcpClient nous renvoie, via la méthode GetStream(), le flux en cours sur le port et à l’adresse spécifiée.
Pour ne pas occuper le thread principal, nous ferons appel à un délégué asynchrone pour gérer le transfert du fichier, qui peut être volumineux, et ce afin d’éviter notamment les phénomènes tel que le « blocage » de la fenêtre d’exécution.
3.2 Autorisation
Elle se fait par un simple envoi de données texte au format binaire. Nous aurions pu bien sur utiliser d’autre format de demande comme le remoting par exemple.
Suivant la réponse du serveur, notre client est considéré comme « connecté », ou non.
// Creation d'un TcpClient
TcpClient client = new TcpClient(iPAddress, port);
// Ouvrir le flux
NetworkStream networkStream = client.GetStream();
// Construction d'un message de demande de connexion
string request = string.Format("Request|{0}", Client.GetHostAddress());
// Récupération du tableau de Byte correspondant
Byte[] requestByte = Encoding.UTF8.GetBytes(request);
// Ecriture dans le flux réseau
networkStream.Write(requestByte, 0, requestByte.Length);
// --------Réponse du serveur-------------------------------
// Récupération du flux de retour.
Byte[] bytes = new Byte[256];
Int32 i = networkStream.Read(bytes, 0, bytes.Length);
// Décodage
String data = System.Text.Encoding.UTF8.GetString(bytes, 0, i);
String[] tabArgs = data.Split(new char[] { '|' });
isConnected = Convert.ToBoolean(tabArgs[1]);
3.3 Réception
Tout d’abord, il nous faut créer un délégué asynchrone pour démarrer le téléchargement.
Le sujet de l’article n’étant pas accès sur ce type de délégué, voici succinctement un aperçu du code :
3.3.1 Délégué asynchrone
public delegate void AsynchroneDownloadDelegate(BinaryWriter w, FileStream fs, NetworkStream ns);
private AsynchroneDownloadDelegate threadStart;
/// Download d'un fichier
/// </summary>
public void DownloadFile(string serverFullFilePath, string localFullFilePath)
{
try
{
…
// Démarrage dun processus asynchrone
threadStart = new AsynchroneDownloadDelegate(AsynchroneBeginDownload);
AsyncCallback asyncCallback = new AsyncCallback(AsynchroneEndDownload);
threadStart.BeginInvoke(w, fs, networkStream, asyncCallback, threadStart);
}
}
/// <summary>
/// Asynchrone
/// </summary>
private void AsynchroneBeginDownload(BinaryWriter w, FileStream fs, NetworkStream ns)
{
…
}
/// <summary>
/// Fin du Download
/// </summary>
private void AsynchroneEndDownload(IAsyncResult ar)
{
AsynchroneDownloadDelegate threadStart = (AsynchroneDownloadDelegate)ar.AsyncState;
threadStart.EndInvoke(ar);
}
3.3.2 Téléchargement du fichier
Le téléchargement du fichier se fait tout simplement, une requète de demande avec le nom du fichier en entrée, et la réception du fichier comme réponse.
Il suffit alors d’ouvrir un flux fichier (en mode écriture !) et réceptionner le flux réseau donné par notre TcpClient
// Creation d'un TcpClient
TcpClient client = new TcpClient(this.ipAddress, this.port);
// Ouvrir le flux
NetworkStream networkStream = client.GetStream();
// Construction d'un message de demande de téléchargement
string request = string.Format("Download|{0}", serverFullFilePath.ToString());
Byte[] b = Encoding.UTF8.GetBytes(request);
// Envoi du message
networkStream.Write(b, 0, b.Length);
// ----Réponse---------------------------------------------------------
// Ouverture d'un flux vers un fichier client.
FileStream fs = new FileStream(localFullFilePath, FileMode.OpenOrCreate);
// Création d'un Writer Binaire sur ce flux
BinaryWriter w = new BinaryWriter(fs);
// Démarrage dun processus asynchrone
threadStart = new AsynchroneDownloadDelegate(AsynchroneBeginDownload);
AsyncCallback asyncCallback = new AsyncCallback(AsynchroneEndDownload);
threadStart.BeginInvoke(w, fs, networkStream, asyncCallback, threadStart);
4 Conclusion
Nous venons de créer facilement, une simple assembly de transfert de fichier, grâce aux outils et fonctionnalités fournies par le Framework .net 2.0
Vous trouverez le code complet et fonctionnel de ce petit outil dans les sources de l’article.
Cet article a pour vocation d’appréhender un coté qui peut être considéré quelque fois comme obscur, les transferts réseaux, mais qui s’avère au final très simple, grâce notamment à la puissance des outils et du langage .NET.
Il vous deviendra alors aisé de développer vos propres outils de transferts.