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

C# .NET Pro pokročilé Š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ženo 172x (16.81 kB)

 

  Aktivity (2)

Článek pro vás napsal jiri.sada
Avatar
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

Jak se ti líbí článek?
Celkem (1 hlasů) :
4444 4


 


Miniatura
Všechny články v sekci
C# - Pro pokročilé

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!