Introduction
Bonjour, dans cette troisième partie, nous allons voir comment traité les packets que nous avons générés en partie 2.
Concepte
Avant de nous lancer en code, voyons d'abord le concept appliqué ici. Nous allons dabord crée une première classe abstract qui nous servira de base pour recevoir les packets. Nous avons dans cette classe un event et une méthode publique:
-AddBytes
-Event PacketReceived
Cette classe ne tiens aucunement en compte la provenance des packets, elle est versatile et pourra etre utiliser autant sur le serveur que sur le client, tant que nous avons une bonne dérivation pour chacun. Voici en gros le fonctionnement:
-Quand des données sont arrivées, ils sont ajoutés au buffer via la méthode AddBytes.
-A chaque nouvel arrivé de données, la classe vérifie si un packet est pret (a l'aide des 4 premier bytes de longueur de chaque packet).
-Ensuite elle génère une classe que j'ai nommé AbstractPacket qui contient longueur, id de packet et données.
-Elle apelle ensuite une méthode abstraite qui devra etre overrided par la classe qui dérivera pour généré la bonne classe relié au packet.
Ajout de données au buffer
Pour mon buffer j'ai choisi Queue<byte>. J'aime bien travailler avec un queue pour recevoir les données ca nous permet de tout garder bien ordonné. C'est possible par contre d'avoir certain problèmes de performances sur un serveur très loader, donc il faut prendre cela en considération. Vu que nous n'avons pas l'intention d'avoir des milliers d'utilisateurs connectés en permanence, cela fera très bien l'affaire.
Il est important de syncronise l'accès a cette queue pour éviter une panoplie de problèmes qui pourrait survenir. J'ai donc fait un object qui aura comme unique but la syncronisation de cette collection.
private Queue<byte> DataQueue = new Queue<byte>();
private readonly object DataQueueLock = new object();
Pour ajouter les données il suffit d'apeller la méthode AddBytes:
public void AddBytes(byte[] data)
{
lock (DataQueueLock)
{
foreach (byte b in data)
DataQueue.Enqueue(b);
}
GenerateAbstractPackets();
}
Nous verrons GenerateAbstractPackets un peu plus tard. Portez une attention particulière a la ligne lock(DataQueueLock), c'est ce qui nous permet de syncroniser l'accès a la collection. Chaque opération sur la queue devra etre protéger par un lock.
Génération d'un packet abstrait
Nous avons premièrement besoin de deux propriétés très utile. Nous avons ici NextPacketLength, qui lis les 4 premier bytes sur la queue et nous informe de la longueur du prochain packet:
int len = ((int)DataQueue.ElementAt(3) << 24)
+ ((int)DataQueue.ElementAt(2) << 16)
+ ((int)DataQueue.ElementAt(1) << 8)
+ ((int)DataQueue.ElementAt(0));
return len;
Ensuite il y a la propriété PacketReady, celle-ci nous dit si nous avons asser de bytes pour généré un packet (ou bien si nous devons attendre l'ajout de nouvelles données). Elle vérifie premièrement si notre buffer contient un minimum de 4 bytes, le minimum requis pour lire la longueur. Ensuite elle vérifie que nous avons asser de données a l'aide de la variable NextPacketLength. La formule mathématique appliqué ici est: (LongueurBuffer + 4) >= NextPacketLength. Voir ici nous avons également un lock, pour éviter que le buffer soit modifier pendant notre vérification.
lock (DataQueueLock)
{
if (DataQueue.Count < 4)
return false;
//read len
int len = NextPacketLength;
if ((DataQueue.Count + 4) >= len)
return true;
return false;
}
Finalement, nous avons seulement a généré un packet abstrait tant et aussi longtemps que la variable PacketReady nous retourne true. Pour faire cela, nous devons enlever le bon nombre de bytes, lire la longueur, l'identifiant et les données puis apeller la méthode abstraite qui sera en charge de généré le bon type de packet.
lock (DataQueueLock)
{
while (PacketReady)
{
int len = this.NextPacketLength;
byte[] data = new byte[len + 4];
for (int i = 0; i < len + 4; i++)
{
data = DataQueue.Dequeue();
}
using (BinaryReader reader = new BinaryReader(new MemoryStream(data)))
{
int packetLen = reader.ReadInt32();
uint packetId = reader.ReadUInt32();
byte[] packetData = reader.ReadBytes(len - 4);
AbstractPacket absPacket = new AbstractPacket(packetLen, packetId, packetData);
GeneratePacket(absPacket);
}
}
}
La dérivation pour le client et le serveur
C'est presque terminer. Il nous reste seulement a voir comment dériver la classe pour la rendre utile et compatible avec les classes de packets que nous avons créé en partie 2. Il faut seulement faire attention car la classe qui est sur le client s'apelle ServerPacketReceiver (car le client recoit les packets du serveur), et vice versa pour le serveur.
Tout ce que nous avons a faire est de faire un override de la méthode GeneratePacket, caster le PacketType a notre enum serveur ou client et puis faire un switch sur ce packet type pour généré la bonne classe a partir de notre packet abstrait. Ne pas oublier non plus de levé l'évenement PacketReceived.
ServerPacketReceiver (sur le client)
protected override void GeneratePacket(AbstractPacket abstractPacket)
{
ServerPacketType packetType = (ServerPacketType)abstractPacket.PacketTypeId;
switch (packetType)
{
case ServerPacketType.BroadcastMessage:
throw new NotImplementedException();
break;
case ServerPacketType.LoginResponse:
LoginResponse loginResponse = new LoginResponse(abstractPacket.Data);
base.onPacketReceived(this, new PacketReceivedEventArgs(loginResponse));
break;
case ServerPacketType.PrivateMessage:
throw new NotImplementedException();
break;
case ServerPacketType.UserList:
throw new NotImplementedException();
break;
case ServerPacketType.UserLogin:
throw new NotImplementedException();
break;
case ServerPacketType.UserLogoff:
throw new NotImplementedException();
break;
default:
throw new ArgumentException(string.Format("The server packet with id {0} does not exist", abstractPacket.PacketTypeId));
}
}
ClientPacketReceiver (sur le serveur)
protected override void GeneratePacket(AbstractPacket abstractPacket)
{
ClientPacketType packetType = (ClientPacketType)abstractPacket.PacketTypeId;
switch (packetType)
{
case ClientPacketType.BroadcastMessageRequest:
throw new NotImplementedException();
break;
case ClientPacketType.LoginRequest:
LoginRequest loginRequest = new LoginRequest(abstractPacket.Data);
base.onPacketReceived(this, new PacketReceivedEventArgs(loginRequest));
break;
case ClientPacketType.PrivateMessageRequest:
throw new NotImplementedException();
break;
default:
throw new ArgumentException(string.Format("The client packet with Id {0} does not exist", abstractPacket.PacketTypeId));
}
}
Conclusion
Voila qui décrit comment faire un buffer de packets. Nous allons bientot voir comment mélanger les sockets avec cela. Ce qui est interessant avec ces classes est que nous pouvons les utiliser meme sans sockets. Voici un petit exemple d'utilisation fort simple:
static void Main(string[] args)
{
var loginRequest = new MikeDotNet.Examples.Chat.SharedLib.Packets.Client.LoginRequest("user", "pass");
var loginResponse = new MikeDotNet.Examples.Chat.SharedLib.Packets.Server.LoginResponse(MikeDotNet.Examples.Chat.SharedLib.Packets.Server.LoginResponseStatus.Success);
MikeDotNet.Examples.Chat.Client.Network.ServerPacketReceiver serverPacketReceiver = new MikeDotNet.Examples.Chat.Client.Network.ServerPacketReceiver();
serverPacketReceiver.PacketReceived += new EventHandler<MikeDotNet.Examples.Chat.SharedLib.Packets.PacketReceivedEventArgs>(serverPacketReceiver_PacketReceived);
MikeDotNet.Examples.Chat.Server.Network.ClientPacketReceiver clientPacketReceiver = new MikeDotNet.Examples.Chat.Server.Network.ClientPacketReceiver();
clientPacketReceiver.PacketReceived += new EventHandler<MikeDotNet.Examples.Chat.SharedLib.Packets.PacketReceivedEventArgs>(clientPacketReceiver_PacketReceived);
clientPacketReceiver.AddBytes(loginRequest.GetPacket());
serverPacketReceiver.AddBytes(loginResponse.GetPacket());
Console.Read();
}
static void clientPacketReceiver_PacketReceived(object sender, MikeDotNet.Examples.Chat.SharedLib.Packets.PacketReceivedEventArgs e)
{
Console.WriteLine("Server received packet from client: " + e.Packet.ToString());
}
static void serverPacketReceiver_PacketReceived(object sender, MikeDotNet.Examples.Chat.SharedLib.Packets.PacketReceivedEventArgs e)
{
Console.WriteLine("Client received packet from server: " + e.Packet.ToString());
}