Internetová komunikace - problém – .NET – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Internetová komunikace - problém – .NET – Fórum – Programujte.comInternetová komunikace - problém – .NET – Fórum – Programujte.com

 

Kubas1290
Stálý člen
21. 7. 2021   #1
-
0
-

ahoj,

mám problém a nevím si s ním rady :(

Snažím se udělat lobby do, které se budou připojovat hráči, ale narazil jsem na problém. V lobby mám list připojených hráčů a dvě tlačítka (odejít, hrát). 

Na aktualizaci listu hráčů jsem spustil samostatné vlákno, které v určitém časovém intervalu (1,3 sekundy) se ptá serveru na obsah listu (jestli se někdo nový nepřipojit, nebo naopak někdo neodpojil), problém je ovšem v tom, že tyto dvě vlákna věží nesynchronně a když hráč klikne na tláčitko Odejít (může to být i tlačítko Hrát), tak je zde určitá nemalá šance, že se bude klient snažit poslat serveru info o hráčově odchodu z lobby, ve chvíli, kdy se druhé vlákno ptá serveru na obsah listu.. a to mi potom hodí chybu: WSACancelBlockingCall

Nevěděl by někdo prosím jak tento problém vyřešit?

Díky moc :-)

Nahlásit jako SPAM
IP: 62.141.28.–
gna
~ Anonymní uživatel
1882 příspěvků
21. 7. 2021   #2
-
0
-

Tomu se říká synchronizace a je potřeba ji použít u všeho na co "saháš" z více vláken.

Základem je zámek (mutex), který jde zamknout jen jednou a další pokus o zamčení se "zasekne", dokud ho původní "držitel" neodemkne. A pak to může být ještě složitější, ale s tímhle si vystačíš hodně dlouho.

V .NETu je na to třída Mutex:

private Mutex socketLock = new Mutex();
...
public void Metoda()
{
    socketLock.WaitOne()
    // tady je zamčeno
    // ...
    socketLock.ReleaseMutex()
    // tady už ne
}

A nebo klíčové slovo lock, které pro tebe asi bude vhodnější a pracuje s libovolným objektem: 

private object socketLock = new object();
...
public void Metoda()
{
    lock (socketLock)
    {
        // tady je zamčeno
        // ...
    }
    // tady už ne
}

Takže kolem všeho, co potřebuješ synchronizovat, přidáš zamykání. (Respektive ideálně implementaci komunikace budeš mít někde centralizovanou a jen jedna metoda bude potřebovat zámek.)

A logicky z toho vyplývá, že by bylo dobré ten zámek držet jen chvilinku, což při práci se sockety úplně nejde, ale budiž.

Nahlásit jako SPAM
IP: 213.211.51.–
Kubas1290
Stálý člen
21. 7. 2021   #3
-
0
-

#2 gna
moc díky, ten první způsob s tím zámkem vypadá, že mi funguje (u druhého mi to i přes to hodilo nějakou chybu).

Zkontroloval bys mi prosím ještě tu classku, jestli jsem v ní ten zámek implementoval správně?

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using Newtonsoft.Json;
using System.Threading;

namespace Monopoly
{
    public static class Communication
    {
        private static TcpClient client = null;
        private static NetworkStream networkStream = null;
        private static byte[] buffer;
        private const string IP = "192.168.1.103";
        private const int PORT = 2050;
        private static Mutex socketLock = new Mutex();

        public static string GetCode(CODES code, params object[] ob)
        {
            socketLock.WaitOne();
                string send = JsonConvert.SerializeObject(code, Formatting.Indented);
            if (ob != null)
            {
                for (int i = 0; i < ob.Length; i++)
                    send += ";" + JsonConvert.SerializeObject(ob[i], Formatting.Indented);
            }
            int bytesRead = 0;
            buffer = new byte[6068];
            client = new TcpClient(IP, PORT);
            networkStream = client.GetStream();
            networkStream.Write(Encoding.UTF8.GetBytes(send));
            networkStream.Flush();

            networkStream.Read(buffer, 0, buffer.Length);
            networkStream.Close();
            client.Close();
            foreach (byte b in buffer)
            {
                if (b != 0)
                {
                    bytesRead++;
                }
            }
            socketLock.ReleaseMutex();
            return Encoding.UTF8.GetString(buffer, 0, bytesRead);
        }
    }
}
Nahlásit jako SPAM
IP: 62.141.28.–
gna
~ Anonymní uživatel
1882 příspěvků
21. 7. 2021   #4
-
0
-

Ještě je potřeba zajistit, aby se třeba při vyjímce ten zámek odemknul. To ta varianta s lock dělá automaticky (nepoužívám C# aktivně, tak nevím, co jsem v tom příkladu napsal špatně). Jinak to musíš udělat sám (try-finally)

Pak ještě bych zamykal jen to co je opravdu potřeba. Tj. tu serializaci dal mimo zamčenou sekci.

A pak hlavně, když pokaždé otevíráš nové spojení, tak není potřeba nic zamykat. Tu synchronizaci bys potřeboval, pokud máš jeden "přetrvávající" socket a může ti na něj sahat více vláken. Tj. něco jako toto: 

void Connect() {
    socketLock.WaitOne();
    try {
        if (networkStream != null)
            throw new AlreadyConnected();

        client = ...;
        networkStream = ...;
    } finally {
        socketLock.ReleaseMutex();
    }
}

void Disconnect() {
    socketLock.WaitOne();
    try {
        if (networkStream == null)
            throw new NotConnected();

        networkStream.Close();
        networkStream = null;
        client.Close();
        client = null;
    } finally {
        socketLock.ReleaseMutex();
    }
}

void Communicate() {
    SerializeRequest();

    socketLock.WaitOne();
    try {
        if (networkStream == null)
            throw new NotConnected();

        networkStream.Write();
        networkStream.Read();
    } finally {
        socketLock.ReleaseMutex();
    }

    UnserializeResponse();
}
Nahlásit jako SPAM
IP: 213.211.51.–
Kubas1290
Stálý člen
21. 7. 2021   #5
-
0
-

#4 gna
jako říkal jsem si, že je to zajímavý, že kdž to mámm oddělený, tak jsem moc nechápal co způsobuje tu chybu, ale na internetu jsem se dočetl, že ten typ chyby WSACancelBlockingCall znamená, že se druhé vlákno snaží něco poslat přes TCPClienta, když je zrovna využíván jiným vláknem a od té doby co jsem to tam dal, tak to tolik nezlobí a více méně to funguje už plynule. 

Tak nevím co tento typ vyjímky ještě mohlo vyvolávat :(

Nahlásit jako SPAM
IP: 62.141.28.–
gna
~ Anonymní uživatel
1882 příspěvků
21. 7. 2021   #6
-
0
-

No, máš tam proměnné jako atributy třídy, takže mohlo dojít k souběhu a to jsi vyřešil tim mutexem.

Nevím, jak máš zbytek kódu, ale tenhle fragment vypadá, že ty proměnné nikde jinde nepotřebuješ, takže by taky šlo je mít jako lokální proměnné v té metodě a různá vlákna by se o ně neprala.

Nahlásit jako SPAM
IP: 213.211.51.–
Kubas1290
Stálý člen
21. 7. 2021   #7
-
0
-

#6 gna
jo tím to asi bude moc díky :-) 

Takže teda statická třída a statický atributy jsou jen jedny v celý aplikaci včetně ikdyž jsou spuštěny s jiného vlákna? Tudíž bych měl tu statickou třídu a všechny její atributy změnit na instanční? (nebo jak se nazývají ty třídy co nejsou statické)

Nahlásit jako SPAM
IP: 62.141.28.–
gna
~ Anonymní uživatel
1882 příspěvků
21. 7. 2021   #8
-
0
-

Statická proměnná existuje jenom jedna jediná, pořád. Instanční je samostatná pro každou instanci dané třídy. Takže třeba můžeš mít třídu Osoba a každá instance bude mít vlastní jméno a můžeš vytvořit Honzíka a Pepíka.

Ale když budeš z více vláken manipulovat s jednou osobou, tak pořád může dojít k souběhu.

Pokud nepotřebuješ ty proměnné (hodnoty) nějak "držet", tak jsem měl na mysli vyloženě lokální proměnné v metodě. Ty se vytvářejí při každém vyvolání metody (takže se vlákna nemají o co prát) a existují jen po dobu provádění té metody (s tím, že .NET pohlídá, když tu hodnotu někam předáš, tak se neztratí, ale smaže se, až když se opravu nikde nepoužívá)

void Metoda()
{
    TcpClient client = ...;
    NetworkStream networkStream = ...;
    ...
}
Nahlásit jako SPAM
IP: 213.211.51.–
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 19 hostů

Podobná vlákna

Com komunikace v C — založil Bedla

Komunikace — založil Zelenáč

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý