Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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í.

Špiónske aplikácie v C # - Server, dokončenie - 3. diel

= Článok pokračuje presne tam, kde minulý skončil =

Metóde odovzdáme SslStream, z ktorého budeme čítať. Metóda nám vráti MemoryStream obsahujúce dáta, čo klient poslal a ako out parameter vrátime ešte hlavičku. Metóda je myslím celkom jasná. Vytvoríme BinaryWritery a Readery, potom prečítame long, aby sme vedeli koľko bajtov má správa a potom dva bajty ako hlavičku. Ak klient poslal hlavičku, že sa chce odpojiť, vyhodíme výnimku a za chvíľu si povieme, ako ju spracujeme. Ak je veľkosť správy 0, nemá cenu ďalej pokračovať, tak vrátime prázdny MemoryStream. Inak bajt po bajtu prečítame správu a dáta dáme do MemoryStreamu, aby sme sa vyhli OutOfMemoryEx­ceptionům. Na záver pripočítame k počítadlu počet bajtov, ktoré sme prečítali, resetujeme Stream a vrátime ho.

private MemoryStream Read(Stream securedStream, out byte[] header)
{
    MemoryStream result = new MemoryStream();
    BinaryReader networkReader = new BinaryReader(securedStream);
    BinaryWriter memoryWriter = new BinaryWriter(result);
    long bufferSize = networkReader.ReadInt64();
    header = new byte[2];
    header[0] = networkReader.ReadByte();
    header[1] = networkReader.ReadByte();

    if (header[0] == OwnerID.SERVER_INTERNAL && header[1] == TypeID.BYE)
        throw new Exception("Client said: \"bye!\"");

    if (bufferSize == 0)
        return new MemoryStream();

    while (bufferSize != 0)
    {
        memoryWriter.Write(networkReader.ReadByte());
        bufferSize--;
    }

    uncompressedBPS += result.Length;

    result.Position = 0;
    return result;
}

Metódu si preťažíme, aby sme si uľahčili prácu v ďalšej metóde.

private string ReadString(Stream securedStream)
{
    byte[] header = new byte[2];
    return GetString(Read(securedStream, out header).ToArray());
}

Teraz máme už vytvorený konštruktor, odosielacie vlákno a základné veci, ďalej musíme urobiť načúvacie vlákno. Načúvacie vlákno nebude fyzicky spracovávať dáta od klientov, len sa s ním spojí, pošle mu certifikát s verejným kľúčom, a keď overenie prebehne v poriadku, vytvorí pre neho nové vlákno. Najskôr teda začneme počúvať a potom budeme jednoducho spať, kým sa niečo nepripojí. Až sa niekto pripojí, musíme ešte pripojenie zašifrovať. Pošleme teda dĺžku certifikátu a potom vlastný certifikát, čo sme si vygenerovali skôr. Musí ísť o CERTIFIKÁT OBSAHUJÚCE LEN VEREJNÝ KĽÚČ! Inak by celé šifrovanie stratilo zmysel ... Keď klient má certifikát a môžeme teda komunikovať šifrovane, nezostáva nič iné, než sprevádzkovať SslStream za pomoci NetworkStreamu z nášho TcpClienta. SslStream však zavrie náš pôvodný NetworkStream, a preto sme si na začiatku vytvárali štruktúru Connection - potrebujeme vždy k TcpClientovi jeho SslStream. Potom sa autentifikuje svojím privátnym certifikátom a vytvoríme pre klienta nové vlákno, ktoré už teraz bude obsluhovať jeho požiadavky. Inicializácia spojenia je týmto hotová.

private void ListenThread()
{
    try
    {
        this.tcpListener.Start();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    while (true)
    {
        try
        {
            TcpClient connection = tcpListener.AcceptTcpClient();

            using (FileStream cert = File.OpenRead(@"cert-public.pem"))
            {
                BinaryWriter writer = new BinaryWriter(connection.GetStream());
                writer.Write(cert.Length);
                cert.Position = 0;
                cert.CopyTo(connection.GetStream());
            }

            SslStream sslStream = new SslStream(connection.GetStream());
            sslStream.AuthenticateAsServer(new X509Certificate2(@"cert.pfx", password), false, SslProtocols.Default, false);

            Thread t = new Thread(ClientThread);
            t.IsBackground = true;
            t.Start(new Connection(sslStream, connection));
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

Ako posledný musíme vytvoriť vlastné klientské vlákno. Rozdelím ho na dve časti. V prvej časti overíme klienta ešte "naším" heslom a dostaneme od neho meno. Na začiatku tejto metódy máme teda funkčné spojenie, avšak server bude klientovi zodpovedať, len keď klient ešte pošle heslo. Meno sa zase hodí, keď budeme programovať s touto knižnicou napr. Chatovacie aplikáciu. Zase si definujeme pár konštánt - ak klient poslal správne alebo zlé heslo.

public static readonly byte AUTH_OK = 255;
public static readonly byte AUTH_ERR = 254;

Kým teda pripojenie funguje, bude sa vykonávať táto metóda stále dokola. Ak v kolekcii pripojených klientov ten náš nie, znamená to, že ešte heslo neposlal. Heslo a meno sú prvé dve veci, čo musí každý klient poslať, takže použijeme preťaženie metódy Read a prečítame heslo. Ak heslo súhlasí, prečítame ešte meno a pridáme si ho do kolekcie. Na záver ešte informujeme klienta o výsledku a spustíme event, že sa klient prihlásil.

private void ClientThread(object data)
{
    Connection connection = (Connection)data;
    string name = "";
    while (connection.client.Connected)
    {
        if (!connections.Values.Contains(connection))
        {
            try
            {
                if (password != "")
                {
                    if (ReadString(connection.securedStream) == password)
                    {
                        name = ReadString(connection.securedStream);
                        connections.Add(name, connection);

                        Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_OK });
                    }
                    else
                    {
                        Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_ERR });
                        break;
                    }
                }
                else
                {
                    name = ReadString(connection.securedStream);
                    connections.Add(name, connection);

                    Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_OK });
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            if (ClientLoggedIn != null)
                ClientLoggedIn(name, connection);

Druhá časť je jednoduchá, spustí sa, ak klient už je v kolekcii (a tým pádom sa už prihlásil). Prečíta od neho správu (až nejakú pošle) a potom ju jednoducho odovzdá eventu. Ak nám čítacie funkcie vyhodila výnimku, cyklus ukončíme. To isté nastane, ak nám klient pošle zlé heslo, či sa niekde inde vyskytne výnimka, z čoho vyplýva, že kým všetko funguje ako má, cyklus beží. Keď sa cyklus ukončí, niečo sa pokazilo a klient sa musí odpojiť. Odstránime ho teda z kolekcie a spustíme príslušný event.

        }
        else
        {
            try
            {
                byte[] header = new byte[2];

                MemoryStream stream = Read(connection.securedStream, out header);

                if (MessageRecieved != null)
                    MessageRecieved(header, stream, name, connection);

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                connections.Remove(name);
                break;
            }
        }
    }

    if (ClientLeft != null)
        ClientLeft(name, connection);
}

No a to je vlastne "všetko". Možno to na prvý pohľad vyzerá zložito, ale teraz máme na použitie krásne jednoduchú triedu, ku ktorej zvonka len priradíme metódy k eventom a voláme Send (). V budúcom dieli si naprogramujeme klienta.

Server si môžete stiahnuť v archíve pod článkom.


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 1308x (16.81 kB)

 

Všetky články v sekcii
C # - Pre pokročilých
Článok pre vás napísal jiri.sada
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje programování v C#, stříhání a úpravou videí a efektů do nich, trollení svých kamarádů drobnými viry a obecně všemu okolo počítačů a elektroniky s Androidem
Aktivity