IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
Lukáš Vámoš:1.5.2018 15:09

Ahoj,
dělám síťovou hru pro čtyři hráče. A rád bych, aby mezi sebou server a klienti posílali příkazy ve formě serializovaných objektů. Mohli byste mi poradit, jak zařídit, aby aplikace nezamrzávala, když se posílá/přijímá objekt přes NetworkStream? Tuším, že bych měl využít asynchronní metodu NetworkStream­.EndRead, ale nevím jak poznat, kdy se přenesl právě jeden celý objekt, abych ho mohl deserializovat. Velikost objektu je proměnlivá, jeho členy budu přetypovávat až podle toho, který příkaz přijde.

 
Odpovědět
1.5.2018 15:09
Avatar
VitekST
Člen
Avatar
Odpovídá na Lukáš Vámoš
VitekST:6.5.2018 10:04

Což takhle síťovou komunikaci jako takovou provádět v samostatném vlákně?

 
Nahoru Odpovědět
6.5.2018 10:04
Avatar
Odpovídá na Lukáš Vámoš
Michal Žůrek - misaz:6.5.2018 10:36

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.

 
Nahoru Odpovědět
6.5.2018 10:36
Avatar
Lukáš Vámoš:6.5.2018 12:36

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
    }
}
 
Nahoru Odpovědět
6.5.2018 12:36
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Lukáš Vámoš
Martin Dráb:6.5.2018 14:39

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ě:

  1. serializuješ objekt, řekněme do paměti,
  2. pošleš hlavičku, kam napíšeš velikost serializovaných dat,
  3. pošleš vlastní serializovaná data.

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.

Nahoru Odpovědět
6.5.2018 14:39
2 + 2 = 5 for extremely large values of 2
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 5 zpráv z 5.