NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Diskuze: Návrh Async TCP serveru s využitím async/await

V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Erik Šťastný:15.11.2017 8:45

Mám následující kód pro server využívající TcpListener

Třída pro server:

class Server
{
    private TcpListener server;

    public Server(string hostname, int port = 25000)
    {
        server = new TcpListener(IPAddress.Parse(hostname), port);
    }

    public async void ServerStart()
    {
        server.Start();
        while (true)
        {
            TcpClient client = await GetClient();
            ClientLowAPI clientReq = new ClientLowAPI(client);
        }
    }

    private async Task<TcpClient> GetClient()
    {
        TcpClient client = await server.AcceptTcpClientAsync();
        Console.WriteLine("The async connection was created for: " + ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());
        return client;
    }
}

Třída pro jednotlivé klienty:

class ClientLowAPI
    {
        private TcpClient client;
        private NetworkStream stream;
        private string ip;

        public ClientLowAPI(TcpClient clientConnected)
        {
            client = clientConnected;
            stream = client.GetStream();
            ip = string.Copy(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());

            Listening();
        }

        private async void Listening()
        {
            while (true)
            {
                try
                {
                    int length = await GetHeader();
                    byte[] message = await GetData(length);
                    byte[] asnwerMessage = ExecuteMessage(message);
                    await SendMessage(asnwerMessage);
                }
                catch (System.IO.IOException)
                {
                    stream.Close();
                    client.Close();
                    Console.WriteLine("The async connection was closed for: " + ip + " (Username: " + HighAPI.GetUsername() + ")");
                    return;
                }
            }
        }

        private async Task<int> GetHeader()
        {
            byte[] buffer = new byte[4];

            int bytesRead = 0;
            while (bytesRead < 4)
            {
                bytesRead += await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
                if (bytesRead == 0)
                {
                    throw new System.IO.IOException();
                }
            }

            return FourBytesToInt(buffer);
        }

        private async Task<byte[]> GetData(int length)
        {
            byte[] buffer = new byte[length];

            int bytesRead = 0;
            while (bytesRead < length)
            {
                bytesRead += await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
                if (bytesRead == 0)
                {
                    throw new System.IO.IOException();
                }
            }

            return buffer;
        }

        private byte[] ExecuteMessage(byte[] binaryData)
        {
                //work with message
                // return answer
        }

        public async Task SendMessage(byte[] message)
        {
            byte[] buffer = new byte[message.Length + 4];
            byte[] length = IntToFourBytes(message.Length);

            length.CopyTo(buffer, 0);
            message.CopyTo(buffer, 4);

            await stream.WriteAsync(buffer, 0, buffer.Length);
        }

        private int FourBytesToInt(byte[] array)
        {
            int res = 0;
            res += array[0] * 256 * 256 * 256;
            res += array[1] * 256 * 256;
            res += array[2] * 256;
            res += array[3];
            return res;
        }

        private byte[] IntToFourBytes(int intValue)
        {
            byte[] array = new byte[4];
            array[0] = (byte)(intValue >> 24);
            array[1] = (byte)(intValue >> 16);
            array[2] = (byte)(intValue >> 8);
            array[3] = (byte)intValue;
            return array;
        }
    }

Hlavní otázka bohužel není vůbec konkrétní, ale zní jak kód zlepšit nebo změnit?

Pro Neaktivní uživatel
Víš já už nevím co si o tom myslet,jsem z toho už totální jelen, protože mě ten kód funguje přesně jak po něm potřebuju a někdo jiný mi pořád říká, že to nedává smysl a nefunguje to.
Zkoušel jsem si to odkrokovat, Pustí se main, vytvořím Server, dojde na await, vrátí se do mainu a až se vrátí hodnota z await metody tak ta už skončí v while (true) cyklu.

Editováno 15.11.2017 8:46
 
Odpovědět
15.11.2017 8:45
Avatar
Odpovídá na Erik Šťastný
Neaktivní uživatel:15.11.2017 18:35

Tak začneme třeba serverem.
Metoda ServerStart je sice async, ale nevrací Task. Takže nemůžeme ji zavolat s await.
Když teda spustím takový server, tak program hned skončí s výpisem "konec";

static async Task Main(string[] args)
{
    var server = new Server("127.0.0.1");
    server.ServerStart();
    Console.WriteLine("konec");
}

Chápeš proč? Jestli mi řekneš, že tobě to nedělá, tak končím :D

Editováno 15.11.2017 18:35
Nahoru Odpovědět
15.11.2017 18:35
Neaktivní uživatelský účet
Avatar
Odpovídá na Neaktivní uživatel
Neaktivní uživatel:15.11.2017 20:14

Neni mozny, ze ma async Main a awaituje volani ServerStart() ?
Ja jsem kvuli tomu nainstaloval mono na linux a sice mam podporu jen C# 7, takze async Main nevyzkousim, ale potvrzuju to co rikas. Program se ukonci.
Asi by potreboval poradne procist teorii async/await.

Nahoru Odpovědět
15.11.2017 20:14
Neaktivní uživatelský účet
Avatar
Odpovídá na Neaktivní uživatel
Neaktivní uživatel:15.11.2017 20:43

Já spíš tipuju, že má wpf aplikaci a server spouští v události na klik tlačítka. Takže si toho ani nevšimne.
EDIT: await nemuzes pouzit na metodu async void

Editováno 15.11.2017 20:45
Nahoru Odpovědět
15.11.2017 20:43
Neaktivní uživatelský účet
Avatar
Odpovídá na Neaktivní uživatel
Erik Šťastný:15.11.2017 21:17

Asi se celou dobu nechápeme co vlastně chci, vždyť tohle je ale přesně to co potřebuju, co by měl vracet nějaký asynchroní start serveru? já ho potřebuji jen odstartovat. pak jen ve smyčce připojovat klienty vevnitř.

Já mám pocit, že celou dobu základní teorii async/await chápu. Jen se nechápeme co očekávám od konkrétní implementace.

Jinak je to jen console app.

Obsah mainu je prakticky nepodstatný, obsluhuju v něm psaním commandů aplikační databázi.

Pro informaci Main je prakticky takovýdle:

private static void Main(string[] args)
{
        Server server = new Server("192.168.1.101");
        server.ServerStart();

        for (string command = Console.ReadLine(); command != "EXIT"; command = Console.ReadLine())
        {
                // všemožné commandy
        }
}
Editováno 15.11.2017 21:17
 
Nahoru Odpovědět
15.11.2017 21:17
Avatar
Odpovídá na Erik Šťastný
Neaktivní uživatel:15.11.2017 21:48

Nojo, jenze tobe pri zivote ten server drzi ta smycka. To je vono :D.

Nahoru Odpovědět
15.11.2017 21:48
Neaktivní uživatelský účet
Avatar
Odpovídá na Erik Šťastný
Neaktivní uživatel:15.11.2017 22:04

Tak já teda nevím, co vlastně chceš. Async/await nechceš řešit, nebudu to už řešit. Tvoje použití bude fungovat, ale je to dost nestandardní.
Tak třeba další tip: rozdělit server a psaní příkazů do 2 vláken (server do nového vlákna, příkazy nechat v původním).
Pak se běžně dělá, že server naslouchá v odděleném vlákně a klienti se obsluhují v dalším vlákně. Případně se použije jedno vlákno na jednoho klienta. Tam už záleží na povaze aplikace.
Takže to už máme 3 vlákna: příkazy, server (accept), server (klienti)

Nahoru Odpovědět
15.11.2017 22:04
Neaktivní uživatelský účet
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.

Zobrazeno 7 zpráv z 7.