Introduction
Bonjour, voici la troisième partie de mon tutoriel sur les sockets. Nous allons voir ici comment utiliser les méthodes asynchrones de la classe socket. Nous allons voir les méthodes suivances:
-BeginConnect / EndConnect
-BeginSend / EndSend
-BeginReceive / EndReceive
Nous allons reproduire la classe CustomSocket de la partie 2 en remplacant tout ce qui était synchrone par les méthodes mentionnés ci-haut.
Les dérivations de la classe EventArgs pour nos events
Premièrement, l'approche utilisé ici sera un peu différent. Nous allons utiliser des événements pour notifier les trois activités possible sur le socket: La connection, l'envoi et la réception. Nous devons donc définir une classe qui pourra encapsuler les informations nécessaire. Je n'ai inclus ici que le stricte minimum, dans un logiciel pratique, il serait utile d'avoir plus d'informations dans ces événements.
-Pour la connection, une bool Success qui indique si la connection a réussie ou pas.
-Pour la réception de données, un array de byte qui contient les données recues.
-Pour l'envoir de données, un int qui définit le nombre de bytes envoyés au serveur.
Voici les classes:
SocketConnectedEventArgs
Cliquez pour révéler
Cliquez pour masquer
public class SocketConnectedEventArgs : EventArgs
{
public bool Success { get; set; }
public SocketConnectedEventArgs(bool success)
{
this.Success = success;
}
}
SocketDataReceivedEventArgs
Cliquez pour révéler
Cliquez pour masquer
public class SocketDataReceivedEventArgs : EventArgs
{
public byte[] Data { get; set; }
public SocketDataReceivedEventArgs(byte[] data)
{
this.Data = data;
}
}
SocketDataSentEventArgs
Cliquez pour révéler
Cliquez pour masquer
public class SocketDataSentEventArgs : EventArgs
{
public int BytesSent { get; set; }
public SocketDataSentEventArgs(int bytesSent)
{
this.BytesSent = bytesSent;
}
}
La classe CustomSocket en mode asynchrone
Comme expliqué plus haut, nous utilisons des events, il faut donc les déclarés. Encore une fois il est intéressant d'utiliser des méthodes protected pour levé un event.
Cliquez pour révéler
Cliquez pour masquer
public event EventHandler<SocketConnectedEventArgs> Connected;
public event EventHandler<SocketDataReceivedEventArgs> DataReceived;
public event EventHandler<SocketDataSentEventArgs> DataSent;
protected void onConnected(object sender, SocketConnectedEventArgs e)
{
if (Connected != null)
{
Connected(sender, e);
}
}
protected void onDataSent(object sender, SocketDataSentEventArgs e)
{
if (DataSent != null)
{
DataSent(sender, e);
}
}
protected void onDataReceived(object sender, SocketDataReceivedEventArgs e)
{
if (DataReceived != null)
{
DataReceived(sender, e);
}
}
Nous déclarons notre IPEndpoint comme toujours, et qui sera pris comme argument de notre constructeur de classe
Cliquez pour révéler
Cliquez pour masquer
public IPEndPoint EndPoint { get; set; }
public CustomSocket(IPEndPoint endPoint)
{
this.EndPoint = endPoint;
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
N'oublions pas de déclarer nos deux variables privés, une pour le socket puis une deuxième qui nous servira de buffer pour recevoir les données.
Cliquez pour révéler
Cliquez pour masquer
private Socket socket;
private byte[] receiveBuffer = new byte[1024];
La classe contient deux méthodes public:
-Connect
-Send
Chacune de ses méthodes retournent avant d'avoir complété leur opérations: Nous devons utiliser les events pour savoir le résultat.
La facon de procéder est similaire pour toutes les méthodes BeginXXX et EndXXX. Il faut déclarer un object AsyncCallback qui pointe vers une méthode définie qui sera responsable de complété l'opération. Ces méthodes doivent accepter un seul argument de type IAsyncResult. L'appel a la méthode BeginXXX ce fait dans un premier lieu, puis l'appel a EndXXX ce fait dans la méthode du callback.
Connect
Voici un exemple concret avec la méthode Connect:
Cliquez pour révéler
Cliquez pour masquer
public void Connect()
{
AsyncCallback callBack = new AsyncCallback(this.ConnectCallback);
this.socket.BeginConnect(this.EndPoint, callBack, this.socket);
}
Vous remarquerez ici que le callback est définit a une méthode nommé ConnectCallback. Il est important de noté que ici je passe notre object socket comme argument dans l'appel à BeginConnect. Dans notre cas précis il ne serait pas nécessaire car nous avons accès a ce socket a l'intérieur de la classe. J'ai tout de meme choisi de l'expliquer de cette manière car en asynchrone nous n'avons pas nécessairement de moyen de savoir de quel socket viens l'opération (nous pourrions faire une classe qui gère plusieur connection sockets en simultanés). Cette facon nous permet d'accéder a la variable Socket a partir de notre object IAsyncResult.
Voici donc la méthode ConnectCallback:
Cliquez pour révéler
Cliquez pour masquer
private void ConnectCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
try
{
s.EndConnect(result);
}
catch (SocketException)
{
onConnected(this, new SocketConnectedEventArgs(false));
return;
}
onConnected(this, new SocketConnectedEventArgs(true));
ReceiveData();
}
Comme vous pouvez le constater, le socket que nous avons passer en argument a BeginConnect ce retrouve dans la variable AsyncState de notre object IASyncResult. Nous pourrions également faire une classe custom qui contient le socket mais aussi d'autre informations relative a la connection qu'il couvre.
Le principe d'exception s'applique aussi en asynchrone, a la différence près que c'est la fonction EndXXX qui génere les exceptions. Il faut donc apeller la méthode Socket.EndConnect, et si une exception est généré, la connection n'a pas réussi.
Une fois la connection réussie, nous pouvons levé l'event Connected. L'appel a ReceiveData (une méthode que nous allons voir plus loin) a ce point est important pour que nous commencons a recevoir les données.
Send
Je ne passerai pas beaucoup de temps a expliquer cet méthode: elle est trop semblable a ce que nous avons déja vu.
Cliquez pour révéler
Cliquez pour masquer
public void Send(byte[] data)
{
AsyncCallback callBack = new AsyncCallback(this.SendCallback);
socket.BeginSend(data, 0, data.Length, SocketFlags.None, callBack, this.socket);
}
private void SendCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
int bytesSent = 0;
try
{
bytesSent = s.EndSend(result);
}
catch (SocketException)
{
}
onDataSent(this, new SocketDataSentEventArgs(bytesSent));
}
Nous pouvons cependant noté que si il y a une exception généré par EndSend, bytesSent sera a zero. Nous pourrons aisi voir que l'envoi a échoué a partir de l'event DataSent.
Receive
Vous vous rapellerez probablement que nous avons fait appel a la fonction ReceiveData après une connection réussie. Cette méthode s'exécutera en boucle avec son callback jusqu'à ce que la connection soit fermée.
Voici un schéma qui décrit ce qui arrive (image a ajouter):
ReceiveData -> BeginReceive -> ReceiveDataCallback -> (erreur=retour OU succès=ReceiveData)
ReceiveData
Cliquez pour révéler
Cliquez pour masquer
private void ReceiveData()
{
AsyncCallback callBack = new AsyncCallback(this.ReceiveDataCallback);
this.socket.BeginReceive(this.receiveBuffer, 0, this.receiveBuffer.Length, SocketFlags.None, callBack, this.socket);
}
ReceiveDataCallback
Cliquez pour révéler
Cliquez pour masquer
private void ReceiveDataCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
int bytesReceived = 0;
byte[] data = null;
try
{
bytesReceived = s.EndReceive(result);
}
catch
{
}
if (bytesReceived > 0)
{
data = new byte[bytesReceived];
Array.Copy(this.receiveBuffer, 0, data, 0, bytesReceived);
onDataReceived(this, new SocketDataReceivedEventArgs(data));
ReceiveData();
}
else
{
data = new byte[0];
onDataReceived(this, new SocketDataReceivedEventArgs(data));
}
}
Vous pouvez voir ici que si la réception de données n'est pas réussie (bytesReceived == 0) alors nous apellons l'event DataReceived avec un array vide puis nous quittons la méthode. Par contre si des données sont recues, nous copions les données recues dans un nouvel array, nous apellons l'event DataReceived, et nous apellons a nouveau la méthode ReceiveData (ce qui fait notre boucle).
L'utilisation de la classe CustomSocket
Je vais faire un example d'utilisation tres simple. Voici le déroulement:
-Instanciation de la classe CustomSocket
-Attachement des events a nos propres event handlers
-DataSent: Console.WriteLine du nombre de bytes envoyés une fois l'opération terminé.
-DataReceived: Console.WriteLine du nombre de bytes recues.
-Connected: Si la connection réussie, nous envoyons un packet.
Cliquez pour révéler
Cliquez pour masquer
class Program
{
static byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static void Main(string[] args)
{
CustomSocket cSocket = new CustomSocket(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3405));
cSocket.Connected += new EventHandler<SocketConnectedEventArgs>(cSocket_Connected);
cSocket.DataReceived += new EventHandler<SocketDataReceivedEventArgs>(cSocket_DataReceived);
cSocket.DataSent += new EventHandler<SocketDataSentEventArgs>(cSocket_DataSent);
cSocket.Connect();
Console.WriteLine("Fin");
Console.ReadKey();
}
static void cSocket_DataSent(object sender, SocketDataSentEventArgs e)
{
Console.WriteLine("Data sent: " + e.BytesSent);
}
static void cSocket_DataReceived(object sender, SocketDataReceivedEventArgs e)
{
Console.WriteLine("Data received: " + e.Data.Count());
}
static void cSocket_Connected(object sender, SocketConnectedEventArgs e)
{
Console.WriteLine("Connected: " + e.Success);
if (!e.Success)
return;
Console.WriteLine("Sending data");
CustomSocket cSocket = (CustomSocket)sender;
cSocket.Send(data);
}
}
Conclusion
Merci d'avoir lu mon tutoriel. Je ne suis pas certain encore quel sera mon prochain sujet alors j'attend vos suggestions! Veuillez cependant noter que je tiens a resté dans la technologie en générale .NET, et rien qui a directement rapport avec un bot pour le jeu D..
Voici la classe CustomSocket complete:
Cliquez pour révéler
Cliquez pour masquer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace MikeDotNet.Examples.SocketTutorialPart3
{
public class CustomSocket
{
public event EventHandler<SocketConnectedEventArgs> Connected;
public event EventHandler<SocketDataReceivedEventArgs> DataReceived;
public event EventHandler<SocketDataSentEventArgs> DataSent;
public IPEndPoint EndPoint { get; set; }
private Socket socket;
private byte[] receiveBuffer = new byte[1024];
public CustomSocket(IPEndPoint endPoint)
{
this.EndPoint = endPoint;
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public void Connect()
{
AsyncCallback callBack = new AsyncCallback(this.ConnectCallback);
this.socket.BeginConnect(this.EndPoint, callBack, this.socket);
}
private void ConnectCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
try
{
s.EndConnect(result);
}
catch (SocketException)
{
onConnected(this, new SocketConnectedEventArgs(false));
return;
}
onConnected(this, new SocketConnectedEventArgs(true));
ReceiveData();
}
public void Send(byte[] data)
{
AsyncCallback callBack = new AsyncCallback(this.SendCallback);
socket.BeginSend(data, 0, data.Length, SocketFlags.None, callBack, this.socket);
}
private void SendCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
int bytesSent = 0;
try
{
bytesSent = s.EndSend(result);
}
catch (SocketException)
{
}
onDataSent(this, new SocketDataSentEventArgs(bytesSent));
}
private void ReceiveData()
{
AsyncCallback callBack = new AsyncCallback(this.ReceiveDataCallback);
this.socket.BeginReceive(this.receiveBuffer, 0, this.receiveBuffer.Length, SocketFlags.None, callBack, this.socket);
}
private void ReceiveDataCallback(IAsyncResult result)
{
Socket s = (Socket)result.AsyncState;
int bytesReceived = 0;
byte[] data = null;
try
{
bytesReceived = s.EndReceive(result);
}
catch
{
}
if (bytesReceived > 0)
{
data = new byte[bytesReceived];
Array.Copy(this.receiveBuffer, 0, data, 0, bytesReceived);
onDataReceived(this, new SocketDataReceivedEventArgs(data));
ReceiveData();
}
else
{
data = new byte[0];
onDataReceived(this, new SocketDataReceivedEventArgs(data));
}
}
protected void onConnected(object sender, SocketConnectedEventArgs e)
{
if (Connected != null)
{
Connected(sender, e);
}
}
protected void onDataSent(object sender, SocketDataSentEventArgs e)
{
if (DataSent != null)
{
DataSent(sender, e);
}
}
protected void onDataReceived(object sender, SocketDataReceivedEventArgs e)
{
if (DataReceived != null)
{
DataReceived(sender, e);
}
}
}
}