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í.

Špiónská aplikace v C# - Server, dokončení - 3. díl

= Článek pokračuje přesně tam, kde minulý skončil =

Metodě předáme SslStream, z kterého budeme číst. Metoda nám vrátí MemoryStream obsahující data, co klient poslal a jako out parametr vrátíme ještě hlavičku. Metoda je myslím celkem jasná. Vytvoříme BinaryWritery a Readery, pak přečteme long, abychom věděli kolik bajtů má zpráva a pak dva bajty jako hlavičku. Pokud klient poslal hlavičku, že se chce odpojit, vyhodíme výjimku a za chvíli si povíme, jak ji zpracujeme. Pokud je velikost zprávy 0, nemá cenu dále pokračovat, tak vrátíme prázdný MemoryStream. Jinak bajt po bajtu přečteme zprávu a data dáme do MemoryStreamu, abychom se vyhnuli OutOfMemoryEx­ceptionům. Na závěr přičteme k počítadlu počet bajtů, které jsme přečetli, resetujeme Stream a vrátíme 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;
}

Metodu si přetížíme, abychom si usnadnili práci v další metodě.

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

Nyní máme už vytvořený konstruktor, odesílací vlákno a základní věci, dále musíme udělat naslouchací vlákno. Naslouchací vlákno nebude fyzicky zpracovávat data od klientů, jen se s ním spojí, pošle mu certifikát s veřejným klíčem, a když ověření proběhne v pořádku, vytvoří pro něj nové vlákno. Nejdříve tedy začneme naslouchat a pak budeme prostě spát, dokud se něco nepřipojí. Až se někdo připojí, musíme ještě připojení zašifrovat. Pošleme tedy délku certifikátu a pak vlastní certifikát, co jsme si vygenerovali dříve. MUSÍ SE JEDNAT O CERTIFIKÁT OBSAHUJÍCÍ POUZE VEŘEJNÝ KLÍČ! Jinak by celé šifrování ztratilo smysl… Když klient má certifikát a můžeme tedy komunikovat šifrovaně, nezbývá nic jiného, než zprovoznit SslStream za pomoci NetworkStreamu z našeho TcpClienta. SslStream ovšem zavře náš původní NetworkStream, a proto jsme si na začátku vytvářeli strukturu Connection – potřebujeme vždy k TcpClientovi jeho SslStream. Pak se autentifikujeme svým privátním certifikátem a vytvoříme pro klienta nové vlákno, které již nyní bude obsluhovat jeho požadavky. Inicializace spojení je tímto hotova.

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);
        }
    }
}

Jako poslední musíme vytvořit vlastní klientské vlákno. Rozdělím jej na dvě části. V první části ověříme klienta ještě „naším“ heslem a dostaneme od něj jméno. Na začátku této metody máme tedy funkční spojení, ovšem server bude klientovi odpovídat, jen když klient ještě pošle heslo. Jméno se zase hodí, když budeme programovat s touto knihovnou např. chatovací aplikaci. Zase si definujeme pár konstant – pokud klient poslal správné nebo špatné heslo.

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

Dokud tedy připojení funguje, bude se provádět tato metoda stále dokola. Pokud v kolekci připojených klientů ten náš není, znamená to, že ještě heslo neposlal. Heslo a jméno jsou první dvě věci, co musí každý klient poslat, takže použijeme přetížení metody Read a přečteme heslo. Pokud heslo souhlasí, přečteme ještě jméno a přidáme si ho do kolekce. Na závěr ještě informujeme klienta o výsledku a spustíme event, že se klient přihlá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á část je jednoduchá, spustí se, pokud klient už je v kolekci (a tím pádem se už přihlásil). Přečte od něj zprávu (až nějakou pošle) a pak ji prostě předá eventu. Pokud nám čtecí funkce vyhodila výjimku, cyklus ukončíme. To samé nastane, pokud nám klient pošle špatné heslo, či se někde jinde vyskytne výjimka, z čehož vyplývá, že dokud vše funguje jak má, cyklus běží. Když se cyklus ukončí, něco se pokazilo a klient se musí odpojit. Odstraníme ho tedy z kolekce a spustíme pří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 vlastně „všechno“. Možná to na první pohled vypadá složitě, ale teď máme na použití krásně jednoduchou třídu, ke které zvenčí jen přiřadíme metody k eventům a voláme Send(). V příštím díle si naprogramujeme klienta.

Server si můžete stáhnout v archivu pod článkem.


 

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 1309x (16.81 kB)

 

Všechny články v sekci
Špiónská aplikace v C# .NET
Článek pro vás napsal jiri.sada
Avatar
Uživatelské hodnocení:
1 hlasů
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