Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C#] Timing probleem: Kan dit?

Pagina: 1
Acties:

  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
Beste tweakers,

ik zit hier met een vreemd probleem. Ik heb er al wel een "ducttape fix" voor, maar een echte oplossing is wel gewenst.

De Situatie:
Ik ben een multiplayer fps game in unity aan het maken. Laatst heb ik het netwerk (zowel tcp als udp) systeem dat ik had compleet opnieuw geschreven. Dit is allemaal goed gegaan, en ik denk dat het een prima systeem is geworden.

Het Systeem:
Mijn networking is gebaseerd op classes die ik rondstuur. Voor elk soort bericht heb ik een class, die derived kan zijn van de "GenericMessage" class, de "RequestMessage" class (die weer derived is van GenericMessage) en de "ResponseMessage" class (ook derived van GenerikMessage). Wanneer ik zo'n message wil versturen haal ik hem door een BinaryFormatter heen. Nu ontstaat er een probleem met de formatters van twee verschillende assemblies, maar dat was blijkbaar op te lossen met een eigen binder.

Om een voorbeeld te geven:
De client wil een nieuwe lobby aanmaken =>
  1. De client stuurt een CreateLobbyRequest met daarin een voorstel voor een naam (met misschien later nog een wachtwoord mogelijkheid).
  2. De server bepaald of dit kan en stuurt een CreateLobbyResponse met daarin het nieuwe lobby nummer als de request geaccepteerd is. (op elke request moet een response worden gestuurd)
  3. De server stuurt aan elke connected client een CreateLobbyMessage met daarin alles wat de client moet weten over de nieuwe lobby.
Het Probleem:
Bij sommige berichten die de client ontvangt geeft de BinaryFormatter een exception dat hij de ontvangen byte[] niet snapt. Door de ontvangen byte[]'s te loggen op de console, kwam ik erachter dat er bij de foute berichten de eerste vier bytes ontbraken.

De Ducttape:
Toen ik met breakingpoints in mijn server ging debuggen, ging het probleem weg. Ook door de byte[]'s ook op de server te loggen ging het probleem weg. Ik vind dit super vreemd. Samen met een vriend van me bedachten we dat het wellicht met timing te maken kon hebben. En wat blijkt? Door een simpele Thread.Sleep(10) (a.k.a. de ducttape) te plaatsen werkt alles ook.

Mijn Vragen:
  1. Hoe kan het dat als ik meerdere tcp-berichten achter elkaar verstuur, dat er dan bytes verloren gaan?
  2. Hoezo heeft timing daar effect op?
  3. En weet iemand een manier om dit op te lossen?
Als er nog onduidelijkheden zijn of iemand wil wat code zien, dan wil ik graag opheldering geven.

Bij voorbaat al bedankt!

Btw: een vorig topic van mij ook over mijn game:\[C#] Organisatie van Tcp-verbindingen
het is misschien niet zo relevant meer, maar misschien wel handig om erbij te hebben :9

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 09:57
Ik vermoed dat het hier om de TCP variant gaat? Die vier bytes zitten denk ik aan het einde in de buffer van de vorige Read( .. ) call. Je hebt, net als velen voor je, waarschijnlijk kennis gemaakt met Nagle.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Otherside1982
  • Registratie: Februari 2009
  • Laatst online: 09:44
Ik vermoed hetzelfde als farlane. Je berichten worden waarschijnlijk in stukjes gelezen. In dat opzicht is dit wel een interessant artikel: TCP/IP Protocol Design: Message Framing

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Inderdaad, TCP/IP zal zeker geen Timing probleem hebben, hooguit jouw code die de communicatie afhandeld. Het voornaamste is dat je er niet vanuit kunt gaan dat één Send aan de ene kant ook daadwerkelijk door één Receive gelezen wordt. Je moet een TCP/IP socket dan ook gewoon als doorlopende stream van data behandelen.

[ Voor 4% gewijzigd door Woy op 23-06-2014 11:24 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Mavamaarten
  • Registratie: September 2009
  • Laatst online: 21-11 22:10

Mavamaarten

Omdat het kan!

Als je data over TCP stuurt, moet je er sowieso vanuit gaan dat de data niet per sé als 1 geheel aankomt. Als je elk stuk data dat aankomt gaat beschouwen als een GenericMessage, dan ga je ongetwijfeld halve messages ontvangen, die je formatter natuurlijk niet begrijpt.

De oplossing hiervoor is, zoals Otherside al zei, message framing. Elke brok data dat je stuurt, pak je in in een vaste structuur. Meestal is dit ongeveer zo: [ length | data ]. Telkens je dus een message stuurt, stuur je ook mee hoe lang de data is die je stuurt. Zo lees je geen halve messages, en als je niet alles ontvangen hebt, dan wacht je tot je genoeg data binnen hebt.

Ik heb een tijdje geleden ook eens liggen knoeien met sockets, en dit heeft mij geholpen om message framing te begrijpen en te implementeren: http://blogs.msdn.com/b/j...ample-for-tcp-socket.aspx
Het is een heel simpele vorm van message framing, maar het werkte goed genoeg voor mijn doeleinden.

Android developer & dürüm-liefhebber


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Vorige week hadden we hier overigens een soortgelijk topic over. Wel in Java, maar het komt op hetzelfde neer: [Java]Sockets - Chat en File transfer

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Phyxion
  • Registratie: April 2004
  • Niet online

Phyxion

_/-\o_

Overigens zou ik sowieso de binary formatter ditchen en voor iets gaan wat veel meer input vreet zoals Json.net, bijkomend voordeel is dat de performance een stuk beter is (4-5x sneller).

'You like a gay cowboy and you look like a gay terrorist.' - James May


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Je moet sowieso natuurlijk al goed nadenken over wat voor protocol je praat. Een binair protocol kan natuurlijk een stuk compacter serializen dan JSON. Al is het een en ander ook erg afhankelijk van wat voor berichten je precies over stuurt.

Ik weet niet precies wat de performance van de BinaryFormatter is, maar er zijn natuurlijk nog een hoop andere mogelijkheden.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
Ah, bedankt voor alle Input zo ver!

Ik zal sowieso vanmiddag de NoDelay optie uitproberen in de sockets die ik gebruik.
"Als je data over TCP stuurt, moet je er sowieso vanuit gaan dat de data niet per sé als 1 geheel aankomt."
Ik gebruik hiervoor ook al een prefix van een int. Ik had inderdaad al wel door dat tcp een stream is.
"Overigens zou ik sowieso de binary formatter ditchen en voor iets gaan wat veel meer input vreet zoals Json.net, bijkomend voordeel is dat de performance een stuk beter is (4-5x sneller)."
Als de performance slecht is, dan kan ik dit nog altijd overwegen. Ik moet de snelheid dan ook nog testen.


Maar mijn vraag nu is, is waarom, als ik zo snel als het kan berichten in de stream pomp (wel gewoon synchroon), dan de eerste 4 bytes verloren gaan?
Die vier bytes zitten denk ik aan het einde in de buffer van de vorige Read( .. ) call.
Dat zou best wel zo kunnen zijn, maar ik twijfel er wel aan, aangezien ik in de stream maar zoveel lees als de prefix int aangeeft.

Het vreemde is dus dat als ik de boel vertraag dat het dan wel goed gaat.


Hier is mijn code dat over send en receive gaat:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
        public void Send(GenericMessage Message)
        {
            try
            {
                Message.SendTime = DateTime.UtcNow;
                List<byte> Bytes = ClassToBytes(Message).ToList();
                Bytes.InsertRange(0, (BitConverter.GetBytes((int)Bytes.Count + sizeof(int))));
                Stream.Write(Bytes.ToArray(), 0, Bytes.Count);

                Thread.Sleep(10); //With this, everything is allright... Why?
            }
            catch (IOException e)
            {
                Main_Form.MainForm.ConsoleWriter(e);
            }
        }

        public GenericMessage Receive()
        {
            if (Stream.DataAvailable)
            {
                byte[] Buffer = new byte[sizeof(int)];
                Stream.Read(Buffer, 0, sizeof(int));
                int MessageLenght = BitConverter.ToInt32(Buffer, 0);
                byte[] Message = new byte[MessageLenght];
                Stream.Read(Message, 0, MessageLenght);

                GenericMessage ReceivedMessage = BytesToClass(Message);
                ReceivedMessage.ReceiveTime = DateTime.UtcNow;
                Ping = (int)(DateTime.UtcNow - ReceivedMessage.SendTime).Milliseconds * 2; //Times two because a normal ping indicates a round-trip time and mine is base on a single trip

                return ReceivedMessage;
            }
            else
            {
                return null;
            }
        }

        public static byte[] ClassToBytes(GenericMessage Data)
        {
            BinaryFormatter Formatter = new BinaryFormatter();
            Formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
            using (MemoryStream Stream = new MemoryStream())
            {
                Formatter.Serialize(Stream, Data);
                return Stream.ToArray();
            }
        }

        public static GenericMessage BytesToClass(byte[] Data)
        {
            BinaryFormatter Formatter = new BinaryFormatter();
            Formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
            Formatter.Binder = new FormatterBinder();
            using (MemoryStream Stream = new MemoryStream(Data))
            {
                return (GenericMessage)Formatter.Deserialize(Stream);
            }
        }


Ik heb voor het overzicht de request en response specifieke componenten er uit gelaten.

  • alienfruit
  • Registratie: Maart 2003
  • Laatst online: 10:18

alienfruit

the alien you never expected

Je hebt altijd nog BJSON (http://bjson.org) ;)

  • Caballeros
  • Registratie: November 2008
  • Niet online
bij het schrijven tel je je lengte indicator bij de lengte van het pakketje op, bij het lezen trek je deze lengte er niet af.
Je probeert dus altijd 4 bytes extra te lezen.
Als ze niet in je stream staan is er niks aan de hand.
Als ze er wel staan zijn dat de 4 bytes die uit het volgende pakketje weg zijn.

Je volgende probleem zou wel eens kunnen worden dat je pakketjes in gedeeltes binnenkomen, dan gaat jouw functie ook niet werken.

  • Teunis
  • Registratie: December 2001
  • Laatst online: 14-11 21:13
Misschien eerst eens Wireshark installeren zodat je weet of packet(ten) wel goed verzonden worden
zodat je in iedergeval op het verzenden of indien nodig op ontvang functie kan focussen

Please nerf Rock, Paper is fine. Sincerely yours, Scissor.
GW2:Teunis.6427


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Sowieso is je read functie erg foutgevoelig, want je doet Stream.Read zonder te controleren hoeveel bytes er daadwerkelijk gelezen zijn. Dat in combinatie met hetgeen Caballeros zegt zorgt ervoor dat het met de sleep wel goed werkt. Die zorgt er in jouw test waarschijnlijk voor dat het volgende bericht al helemaal klaar staat, waardoor de read ook daadwerkelijk jouw volledige bericht leest.

Maar het zou best kunnen dat de Read maar een half berichtje leest. Je zult dus ook de return waarde van de read moeten controleren, en eventueel nog 1 of meerdere extra reads doen.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 09:57
diondokter schreef op maandag 23 juni 2014 @ 12:51:
Dat zou best wel zo kunnen zijn, maar ik twijfel er wel aan, aangezien ik in de stream maar zoveel lees als de prefix int aangeeft.
Hier is mijn code dat over send en receive gaat:
[code=C#]
Je gaat er in je code onterecht vanuit dat de eerste vier bytes van de 1e Read de lengte bevatten, wat dus niet zo hoeft te zijn.

Als je je receive zo maakt dat hij alle ontvangen data achter elkaar in een buffer plakt en vanuit daar je frame gaat parsen ( met een timeout constructie natuurlijk want je wilt niet een incompleet bericht eindeloos in je buffer hebben ) gaat het denk ik beter.

Overigens (volgens mij is dit vaker gezegd) als je een low latency frame georienteerd protocol wilt dan wordt vaak UDP gebruikt, met name in games.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        public void Send(GenericMessage Message) 
        { 
            try 
            { 
                Message.SendTime = DateTime.UtcNow; 
                List<byte> Bytes = ClassToBytes(Message).ToList(); 
                Bytes.InsertRange(0, (BitConverter.GetBytes((int)Bytes.Count + sizeof(int)))); 
                Stream.Write(Bytes.ToArray(), 0, Bytes.Count); 

                Thread.Sleep(10); //With this, everything is allright... Why? 
            } 
            catch (IOException e) 
            { 
                Main_Form.MainForm.ConsoleWriter(e); 
            } 
        } 


Nou, ik weet wel waar die 4 bytes gebleven zijn.... Je length message (de int) is 'off by 4'.

Extra tip, je kan ook gewoon 2x stream.Write aanroepen (1x met het resultaat van de BitConverter, en 1x met je message). Scheelt qua performance waarschijnlijk wel iets omdat je niet van byte[] -> List<byte> -> byte[] hoeft te converten.

Misschien dat een stream.Flush() na het schrijven van de message ook helpt.

  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
Ik moet inderdaad werken aan de Receive method. Ik dacht dat Stream.Read() blocking zou zijn.

Die vier bytes die zijn prima, zolang ik niet te snel naar de stream schrijf. Dat is het punt. Als ik meerdere messages achter elkaar naar de stream schrijf, dan gaat het mis met die bytes. Ik moet weten waardoor dat komt want verder gaat alles goed.

  • Mavamaarten
  • Registratie: September 2009
  • Laatst online: 21-11 22:10

Mavamaarten

Omdat het kan!

Ik zou trouwens gewoon die binaryformatter houden en niet voor JSON gaan.

Met de binaryformatter krijg je rechtstreeks binaire data.
Met JSON ga je eerst je Message omzetten naar een string, en die string zet je dan om naar binair via encoding (UTF8 ofzo). Da's een tussenstap die onnodig en dus inefficient is.

[ Voor 42% gewijzigd door Mavamaarten op 23-06-2014 15:16 ]

Android developer & dürüm-liefhebber


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 09:57
HMS schreef op maandag 23 juni 2014 @ 14:25:
Extra tip, je kan ook gewoon 2x stream.Write aanroepen (1x met het resultaat van de BitConverter, en 1x met je message).
Dat is geen goed idee
diondokter schreef op maandag 23 juni 2014 @ 15:09:
Ik moet weten waardoor dat komt want verder gaat alles goed.
Nee, zoals meerdere mensen al hebben aangegeven is er iets fundamenteels fout in met name je receive gedeelte. Die delay aan de write kant is een vieze hack die ( in jouw situatie ) de brakheid camoufleert (in dit geval)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
farlane schreef op maandag 23 juni 2014 @ 15:29:
[...]

Dat is geen goed idee


[...]

Nee, zoals meerdere mensen al hebben aangegeven is er iets fundamenteels fout in met name je receive gedeelte. Die delay aan de write kant is een vieze hack die ( in jouw situatie ) de brakheid camoufleert (in dit geval)
Maar waarom missen dan de eerste 4 bytes, en niet bijvoorbeeld de laatste paar?

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
diondokter schreef op maandag 23 juni 2014 @ 15:09:
Ik moet inderdaad werken aan de Receive method. Ik dacht dat Stream.Read() blocking zou zijn.
Read is ook blocking, maar leest niet perse het aantal bytes dat jij vraagt.
Die vier bytes die zijn prima, zolang ik niet te snel naar de stream schrijf. Dat is het punt. Als ik meerdere messages achter elkaar naar de stream schrijf, dan gaat het mis met die bytes. Ik moet weten waardoor dat komt want verder gaat alles goed.
Kijk dan nog eens goed, want daar zit wel degelijk een probleem. Bij het schrijven stuur je de (sizeof(int) + sizeof(data) ), bij het lezen lees je eerst sizeof(int) en dan lees je de waarde van die gelezen int ( die is sizeof(int) + sizeof(data) ). Uiteindelijk lees je dus sizeof(int) + sizeof(int) + sizeof(data), echter doordat de read uiteindelijk eerder returnt zal je die laatste 4 bytes niet lezen als er maar genoeg delay tussen de pakketjes zit.

Oftewel, bij het versturen is de length inclusief de header, en bij het receiven exclusief. Dat moet je gelijk trekken.

[ Voor 34% gewijzigd door Woy op 23-06-2014 15:42 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
Bij het schrijven stuur je de (sizeof(int) + sizeof(data) ), bij het lezen lees je eerst sizeof(int) en dan lees je de waarde van die gelezen int ( die is sizeof(int) + sizeof(data) ). Uiteindelijk lees je dus sizeof(int) + sizeof(int) + sizeof(data), echter doordat de read uiteindelijk eerder returnt zal je die laatste 4 bytes niet lezen als er maar genoeg delay tussen de pakketjes zit.
Oh wow... dat is een grove fout... Dat het überhaupt heeft gewerkt!?
Ik zal het aanpassen en kijken of dat de oplossing is. Dank! _/-\o_

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
diondokter schreef op maandag 23 juni 2014 @ 15:41:
[...]


Oh wow... dat is een grove fout... Dat het überhaupt heeft gewerkt!?
Ik zal het aanpassen en kijken of dat de oplossing is. Dank! _/-\o_
Omdat je bij de vorige read 4 bytes te veel gelezen hebt. Door de sleeps leest de read waarschijnlijk alleen 4 bytes minder dan dat jij vraagt, en dus komt het dan bij het volgende bericht weer goed.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Phyxion
  • Registratie: April 2004
  • Niet online

Phyxion

_/-\o_

Woy schreef op maandag 23 juni 2014 @ 12:13:
Je moet sowieso natuurlijk al goed nadenken over wat voor protocol je praat. Een binair protocol kan natuurlijk een stuk compacter serializen dan JSON. Al is het een en ander ook erg afhankelijk van wat voor berichten je precies over stuurt.

Ik weet niet precies wat de performance van de BinaryFormatter is, maar er zijn natuurlijk nog een hoop andere mogelijkheden.
Gelukkig heeft Json.net ook ondersteuning voor BSON, zie ook:
Afbeeldingslocatie: http://james.newtonking.com/images/JSONPerformanceWithBinaryData_100D1/image.png
Mavamaarten schreef op maandag 23 juni 2014 @ 15:14:
Ik zou trouwens gewoon die binaryformatter houden en niet voor JSON gaan.

Met de binaryformatter krijg je rechtstreeks binaire data.
Met JSON ga je eerst je Message omzetten naar een string, en die string zet je dan om naar binair via encoding (UTF8 ofzo). Da's een tussenstap die onnodig en dus inefficient is.
Json.net is ongeveer 4-5x sneller dan een binaryformatter... Daarnaast vreet Json.NET veel meer data en kan je eenvoudiger velden toevoegen of weghalen zonder exceptions.

'You like a gay cowboy and you look like a gay terrorist.' - James May


  • diondokter
  • Registratie: Augustus 2011
  • Laatst online: 20-11 12:14

diondokter

Dum spiro, spero

Topicstarter
Geweldig, probleem opgelost! Dank jullie wel, jullie zijn geweldig!
Phyxion schreef op maandag 23 juni 2014 @ 15:42:
[...]

Gelukkig heeft Json.net ook ondersteuning voor BSON, zie ook:
[afbeelding]


[...]

Json.net is ongeveer 4-5x sneller dan een binaryformatter... Daarnaast vreet Json.NET veel meer data en kan je eenvoudiger velden toevoegen of weghalen zonder exceptions.
Ok cool, ik zal er zeker naar kijken als de binaryformatter te langzaam blijkt :9

Nogmaals, bedankt iedereen!
Dit is hoe de game er tot nu toe uit ziet (Alles wat een redelijk 3d model bevat is niet van mij XD *tank met steen textures* *ahem*):

[ Voor 3% gewijzigd door diondokter op 23-06-2014 15:49 ]


  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

Ah, ik ging er (verkeerd) van uit dat de NetworkStream buffered zou zijn. Dan is het inderdaad geen goed idee. Maar iets efficienters om de size voor de message aan te krijgen lijkt me wel wenselijk.

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Je kunt een NetworkStream natuurlijk altijd in een BufferedStream "wrappen" mocht je dat willen.

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

Dat zou wel mijn voorkeur hebben, buffering + expliciete flush.
Pagina: 1