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

[C#] Organisatie van Tcp-verbindingen

Pagina: 1
Acties:

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

diondokter

Dum spiro, spero

Topicstarter
Beste tweakers,

ik ben al een poosje bezig om een FPS game te maken in unity. Het is een multiplayer fps (zonder plugins, dus met eigen server gebruikmakend van TcpListeners e.d.) waar ik natuurlijk probeer uniek te zijn. Nou ben ik nog niet bij dat stadium, maar ik heb al wel een paar ideeën.

Ik ben in dit project als nog een redelijke noob gestapt. Zo had ik nog weinig benul wat een static variable nou deed of wat een struct precies was.

Gelukkig heb ik veel geleerd sinds het begin van het project. Daarom dacht ik dat het handig was om mijn oudste code eens wat beter te maken. Dit was ook de aller belangrijkste code van de game en server, namelijk de tcp networking code.

De oude code was gebaseerd om deze filmpjes op youtube:
YouTube: C# Tcp tutorial pt 1 - Server
YouTube: C# Tcp tutorial pt 2 - Client

spoiler:
Pas op: vage tekst hier beneden...

offtopic:
Hoe werkte mijn systeem?
De server en client waren (zo goed als) gelijk aan elkaar qua werking.
Beiden zaten ze op een poort te luisteren. Zodra er een connectie gevraagd werd, werd hij geaccepteerd. Het bericht werd uitgelezen en in een centrale list gezet. Hierna werd de connectie geclosed.

Om de centrale berichten list te doorlopen had ik een loop gemaakt. Deze checkte of er nieuwe berichten waren en voerde ze uit op de volgende manier: De server maakte verschil tussen servercommands en lobbycommands. Deze zaten in verschillende classes. Zodra het bericht naar de juiste class was gestuurd (let op: er zijn meerdere lobbies) voerde de class het bericht uit. Ik kan het wel verder uitleggen, maar het zal er niet duidelijker van worden.

Wat was er mis met de oude implementatie?
  • Om een bericht te versturen naar een client/server moest er elke keen een nieuwe connectie gemaakt worden. Zodra het bericht gestuurd was, werd de connectie weer gesloten. Dit is niet optimaal voor een tcp-verbinding.
  • Elk bericht was een naar bytes geconverteerde string. Om bijv. aan de server te zeggen welke map geselecteerd is stuurde je een bericht als deze: "/Lobby:2 Action:SetMap Value:5". Dit zou in de derde lobby de zesde map selecteren. Dit zorde ervoor dat als ik iets van de server/client nodig had, dat ik eerst moest opzoeken hoe de string voor de functie die ik wilde gebruiken eruit moest zien. Dit was erg onhandig. Ook moest je alle variabelen die wilde verzenden
  • Het was niet mogelijk om antwoord te geven aan een request; Als je wilde weten van de server welke map geselecteerd dan stuurde je een request. De server "antwoorde" daarop met een setmap bericht, wat alle erg ongeorganiseerd maakte.
  • Het was onveilig. Het maakte niet uit wie je was, maar als je een bericht stuurde, dan werd hij uitgevoerd.
Ik heb nu een nieuw systeem gemaakt dat een paar dingen van het oude systeem oplost. Maar het blijft nog steeds onveilig en ik kan nog steeds geen direct antwoord geven. De strings zijn vervangen door enums e.d. en connecties blijven behouden.


Als je het offtopic gedeelte hebt gelezen en begrepen: _/-\o_
Zo niet dan is hier de conclusie: Ik ben nog steeds niet tevreden met mijn huidige implementatie van mijn tcp-systeem.

Heeft iemand ervaring met iets soortgelijks? Ik zoek niet naar een concrete oplossing, maar meer een duw in de goede richting. Tot nu toe zijn de oplossingen die ik bedacht heb het telkens net niet.

Ik hoop dat ik niet te vaag ben... Als er vragen zijn, dan beantwoord ik ze natuurlijk graag.

  • rikkert278
  • Registratie: Februari 2010
  • Laatst online: 13-06-2024
Wat betreft berichten, kijk eens naar Google's Protocol Buffers. https://code.google.com/p/protobuf/ Verder kun je de implementatie hier http://www.codeproject.co...-Chat-Application-Using-C waarschijnlijk goed verder uit bouwen.

Heb ooit een volledige TCP client/server systeem geschreven met Protobuf. Werkte geweldig simpel en was goed uit te breiden. :)

Qua SSL en dergelijke kun je eens naar MSDN: SslStream Class (System.Net.Security) kijken. ALs je daar op Googled zou je zat implemetaties moeten vinden.

[ Voor 24% gewijzigd door rikkert278 op 25-05-2014 17:18 ]


  • xzaz
  • Registratie: Augustus 2005
  • Laatst online: 20-11 17:07
FPS game en TCP, dat lijkt mij geen goede combinatie.

Schiet tussen de palen en je scoort!


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

diondokter

Dum spiro, spero

Topicstarter
xzaz schreef op zondag 25 mei 2014 @ 20:16:
FPS game en TCP, dat lijkt mij geen goede combinatie.
Nee, daarom heb ik ook een aparte udp verbinding XD

Maar toch om wat meer duidelijkheden te scheppen: Ik zoek een manier om het netwerken te structureren... Het opzetten van sockets enzo, dat kan ik zeker wel.

Ik zal zo ook wel wat relevante code posten van mijn huidige structuur. (Zit nu niet achter mijn pc)

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Als je netwerk communicatie voor een multiplayer FPS wilt regelen; check Quake III Arena eens. Het is weliswaar plain C (niet eens C++), maar er zitten een paar goede concepten in die je over zou kunnen nemen. Het is ook een best wel rude wakeup call; er komt enorm veel kijken bij het netjes en performant afhandelen van dit spul.

[ Voor 23% gewijzigd door R4gnax op 26-05-2014 00:04 ]


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

diondokter

Dum spiro, spero

Topicstarter
R4gnax schreef op maandag 26 mei 2014 @ 00:02:
check Quake III Arena eens.

er komt enorm veel kijken bij het netjes en performant afhandelen van dit spul.
Klopt, dat had ik ook al door. Heb gekeken naar de code, maar ik heb geen idee waar te beginnen... |:(
Maar het is inderdaad super veel code.

Ik had ook wat van mijn code beloofd. Ik hoop dat dit werkt:
http://pastebin.com/NFR9a0n1 //ServerCommandsClass (snippet)
http://pastebin.com/YByggpmB //LobbyClass (snippet)
http://pastebin.com/HjXZTmPj //Main Loop
http://pastebin.com/D5aX37Kc //Tcp-networking

Ik moet hierbij opmerken dat PlayerClass derived is van NetworkClient. En sorry voor de weinige comments... Ik probeer het steeds meer te doen.

Ik zat ook te denken om eens met events te gaan werken. Heeft dit eigenlijk ook nadelen?

Die SslStream is ook erg interessant. Daar moet ik nog even meer naar kijken.

[ Voor 4% gewijzigd door diondokter op 26-05-2014 11:34 ]


  • epic007
  • Registratie: Februari 2004
  • Laatst online: 17-11 15:31
In principe kan je ook structs heen en weer sturen via tcp/ip. Ik heb zoiets gemaakt om met een PLC te communiceren.
Ik gebruik zelf onderstaande Message Struct:
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
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public enum MessageType
{
  None = 0,
  Ping = 1,
  Connect = 10,
  Disconnect = 11,
  AddClient = 12,
  ...
}

public struct Message
{
    const ushort MAGIC = 0xf5fa;

    public Message(MessageType type, ushort destination, uint status = 0, ushort size = 0)
    {
        this.magic = MAGIC;
        this.type = (ushort)type;
        this.destination = destination;
        this.status = status;
        this.size = size;
    }

    public bool Valid
    {
        get { return magic == MAGIC; }
    }

    public MessageType Type
    {
        get { return (MessageType)type; }
    }

    ushort magic;
    ushort type; // MessageType
    public ushort destination;
    public ushort size; // > 0 indien meer data volgt voor dit message
    public uint status;
}

De StructLayout zorgt er voor dat de struct sequentieel in het geheugen staat.

Deze converteer ik naar byte [] via een StructConverter:
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
    public static class StructConverter
    {
        public static byte[] ToArray<T>(T data)
        {
            int size = Marshal.SizeOf(data);
            byte[] arr = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);

            Marshal.StructureToPtr(data, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);
            Marshal.FreeHGlobal(ptr);

            return arr;
        }

        public static T ToType<T>(byte[] data)
        {
            int sz = Marshal.SizeOf(typeof(T));
            IntPtr ptr = Marshal.AllocHGlobal(sz);
            Marshal.Copy(data, 0, ptr, sz);
            T result = (T)Marshal.PtrToStructure(ptr, typeof(T));
            Marshal.FreeHGlobal(ptr);
            return result;
        }
    }



Verder is wat je hier doet niet erg efficient:
C#:
1
2
3
4
5
6
7
8
9
10
11
while (!Stop)
{
  if (Listener.Pending())
  {
    AddNewClient();
  }
  else
  {
    Thread.Sleep(10);
  }
}

Je kijkt hier elke 10ms of er een nieuwe client aanwezig is. De functie Listener.AcceptTcpClient() is blocking, dus wacht zelf op nieuwe clients. Bij een niewe Client wordt meestal een thread opgestart die deze client verder afhandeld, de server kan dan via AcceptTcpClient weer op een volgende client wachten.
Gezien je nog niet wist wat een struct was zou ik zeker nog niet aan threads beginnen (alhoewel je er niet onderuit komt als je aan een multiplayer fps gaat beginnen).

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

diondokter

Dum spiro, spero

Topicstarter
Je kijkt hier elke 10ms of er een nieuwe client aanwezig is. De functie Listener.AcceptTcpClient() is blocking, dus wacht zelf op nieuwe clients. Bij een niewe Client wordt meestal een thread opgestart die deze client verder afhandeld, de server kan dan via AcceptTcpClient weer op een volgende client wachten.
Gezien je nog niet wist wat een struct was zou ik zeker nog niet aan threads beginnen (alhoewel je er niet onderuit komt als je aan een multiplayer fps gaat beginnen).
Dat hij blocking was, was ik ff vergeten ja XD.
En geloof me, ik was al veel eerder met threading bezig dan uberhaubt iets anders.

Ik ben dit project zo'n 6 maanden geleden gestart. Ik doe het gewoon lekker langzaam met veel experimenteren. Ik heb in die 6 maanden super veel geleerd. Ik weet nu ook wel wat structs zijn en doen en waarom hij niet null kan zijn (wat soms ongelofelijk irritant kan zijn). Daarom wilde ik dus met mijn nieuwe kennis mijn oudste code opschonen.
In principe kan je ook structs heen en weer sturen via tcp/ip.
Ik was al op het idee gekomen om events te gebruiken. Ik zou hierbij de eventargs ook kunnen verzenden.

Ik heb nu al wel weer wat nieuwe ideeën gekregen over hoe ik dingen kan aanpakken, maar 1 ding ben ik nog niet over uit.
Ik zou graag zoiets willen doen:
C#:
1
2
3
4
5
SignalClass Response = Client.Send(*/ A random request like getmap \*);

//of misschien makkelijker

int CurrentMap = Client.Send(*/ A request like getmap \*);


Ik heb een idee hoe ik dit zou moeten aanpakken:
Misschien moet bij de request een nummer worden meegegeven, die met de response wordt meegeleverd. Zodra er een bericht is ontvangen met die nummer dat hij dan pas returned met een signalclass/int?
Veel te vaag XD...

Mijn idee: Wanneer er een request wordt verzonden, wordt er een herkenningsteken/nummer meegestuurd. De onvanger stuurt bij zijn response datzelfde getal mee. De ontvanger van de response kan dan weer kijken of er ergens op dat getal gewacht word. Zo ja, dan krijgt de wachtende functie de response waarna hij kan returnen.

Zou dit handig / op de lange termijn werkbaar zijn?
Heeft iemand hier een idee/tip voor?

Daarbij bedank ik jullie allemaal. Jullie hebben me al flink geholpen :Y

[ Voor 9% gewijzigd door diondokter op 26-05-2014 17:35 ]


  • DizzyVacation
  • Registratie: November 2006
  • Niet online
diondokter schreef op maandag 26 mei 2014 @ 11:31:
[...]


Klopt, dat had ik ook al door. Heb gekeken naar de code, maar ik heb geen idee waar te beginnen... |:(
Maar het is inderdaad super veel code.
...
Ik had toevallig deze nog in mijn bookmarks staan:
Quake Engine code review : Network
Quake 3 Source Code Review: Network Model

Misschien zijn de andere pagina's ook wel interessant voor je.

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

diondokter

Dum spiro, spero

Topicstarter
Ooh! Zal ik morgen eens goed doorkijken! Bedankt😆

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 09:57
diondokter schreef op maandag 26 mei 2014 @ 17:29:
[...]
Mijn idee: Wanneer er een request wordt verzonden, wordt er een herkenningsteken/nummer meegestuurd. De onvanger stuurt bij zijn response datzelfde getal mee. De ontvanger van de response kan dan weer kijken of er ergens op dat getal gewacht word. Zo ja, dan krijgt de wachtende functie de response waarna hij kan returnen.

Zou dit handig / op de lange termijn werkbaar zijn?
Wat je beschrijft is eigenlijk een transaction id, en wordt vrij veel toegepast.

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.


  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
IngmarBlonk schreef op dinsdag 27 mei 2014 @ 22:55:
[...]


Ik had toevallig deze nog in mijn bookmarks staan:
Quake Engine code review : Network
Quake 3 Source Code Review: Network Model

Misschien zijn de andere pagina's ook wel interessant voor je.
Ik was al op zoek gegaan waar ik die source code review van Quake 3 ook al weer gebookmarked had, maar je bent me al voor geweest. :)
Pagina: 1