Diskuze: Asynchronní síťová komunikace v c#
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Zobrazeno 5 zpráv z 5.
//= Settings::TRACKING_CODE_B ?> //= Settings::TRACKING_CODE ?>
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Což takhle síťovou komunikaci jako takovou provádět v samostatném vlákně?
Fakt, že posíláš celý objekt serializovaný vypovídá o chybném návrhu protokolu. Většinou se data mapují do/z datagramu a většinou si v případě takové potřeby dopíšeš do hlavičky velikost paketu v bytech. Pak víš, že určitě nemá smysl paket číst dokud se nenačetlo pole s hodnotou a pokud se načetlo, tak má smysl paket číst jen pokud je skutečně tak velký jako je v poli uvedeno. U síťových aplikací je dobré (možná nutnost) obsluhovat komunikaci v odděleném vlákně a třeba pomocí událostí exportovat data do aplikace, tím se ti vyřeší zasekávání. Nenapsal jsi, jestli stavíš nad UDP nebo TCP (nebo něčem jiném?), pak by se dala postavit konstruktivnější rada.
Aha a já jsem si myslel, jak je to elegantní, když budu každý příkaz
posílat v serializovaném objektu a nebudu muset parsovat řetězec... navíc
bych chtěl časem posílat i krátké hlasové zprávy. Chtěl bych tedy
poprosit o trochu detailnější radu. Pracuju s TCP, pro server každého tcp
klienta jsem udělal oddělené vlákno. Příkazy ukládám do instance níže
uvedené třídy, kterou pak serializuji a ukládám do NetworkStreamu. Příkaz
je v řetězci, argumenty v kolekci typu Object. Čtení probíhá tak, že v
odděleném vlákně ve smyčce zkouším deserializovat objekt rovnou z
NetworkStreamu a jednotlivé argumenty pak chci přetypovat podle toho, který
příkaz přišel. Je to podle návodu z netu, ale nechápu, jak BinaryFormatter
pozná, že už objekt dorazil celý. Zkoušel jsem takto posílat textové
zprávy a šlo to.
;
using System;
using System.Collections.Generic;
namespace Ludo
{
public enum MessageType
{
Test,
Info,
GameAction,
PlayerAction,
Error
}
[Serializable]
public class NetMessage
{
#region fields
private List<Object> _args;
#endregion
#region constructors
public NetMessage(string command) : this(MessageType.Info, command, null, null)
{
}
public NetMessage(MessageType type, string command, List<object> args) : this(type, command, null, null)
{
//Copy the list
_args = new List<object>(args);
}
public NetMessage(MessageType type, string command, object arg1, object arg2 = null)
{
Type = type;
Command = command;
if (string.IsNullOrEmpty(Command) || string.IsNullOrEmpty(arg2))
throw new ArgumentException(nameof(this));
_args = new List<object>();
_args.Add(arg1);
if (arg2 != null)
_args.Add(arg2);
}
#endregion
#region properties
public List<object> Args { get => _args; }
public string Command { get; private set; }
public MessageType Type { get; private set; }
#endregion
}
}
Právě výhoda řešení "udělej si formát zpráv/komunikační protokol sám" spočívá v tom, že vždy víš, kolik bajtů chceš přečíst. Obvyklý návrh je takový, že zpráva se skládá z hlavičky pevné délky (např. vždy 32 bajtů) a nepovinného těla, jehož délka je uvedena v hlavičce.
Přečtení jedné zprávy pak znamená přečtení hlavičky, získání délky těla a přečtení těla. Nevýhoda samozřejmě spočívá v tom, že si musíš ta přijatá data naparsovat, na druhou stranu budeš asi efektivnější co se týče množství přenesených dat.
Obě řešení by se zřejmě dala zkombinovat následovně:
Odesílání v kroku 2) a 3) lze případně spojit do jedné operace.
Při přijímání přečteš hlavičku, přečteš serializovaná data a následně je deserializuješ. Přičemž vždy víš, kolik bajtů potřebuješ přečíst.
Netvrdím, že je to nějaké super řešení, ale nebudeš muset parsovat přijatá data.
Co se týče zamezení zasekávání při čekání na zprávy, můžeš jej řešit buď komunikací v samostatném vlákně, nebo asynchronními operacemi. U samostatného vlákna si ale sám musíš zajistit předávání zpráv do vlákna hlavního (pokud to má ty zprávy nakonec zpracovávat) včetně potřebné synchronizace.
U asynchronních operací nic takového obvykle dělat nemusíš, ale jejich sémantika může být poněkud "tajemnější". Nevím, jak je to v C#, tam třeba měli rozum a odstínili vás od pár ošklivostí... ale dělat asynchronní operace třeba přímo přes WIndows API není úplně triviální záležitost; je třeba pečlivě číst dokumentaci, aby to fungovalo ve všech případech.
Zobrazeno 5 zpráv z 5.