Wiele instancji serwera TCP/IP

Wiele instancji serwera TCP/IP
WileCoyote
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Poznań
  • Postów:20
1

Witajcie, mam problem z serwerem TCP/IP.

Chcę stworzyć symulator urządzenia które będzie na jakimś porcie. Jednak tych urządzeń ma być X. Sęk w tym, że utworzenie 4 serwerów to ułamek sekundy, ale już każdy kolejny otwiera się w ok. sekundę co przy dużej ilości symulacji trochę utrudnia sprawę. Aby lepiej to przedstawić zrobiłem mały program:

Kopiuj
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TestServer
{
    class Program
    {
        private static DateTime StartDate;
        private static int ServerCouneter = 20;
        private static List<Server> Servers;
        static void Main(string[] args)
        {
            Servers = new List<Server>();
            for (var i = 0; i < ServerCouneter; i++)
            {
                Servers.Add(new Server(i));
            }
            Console.WriteLine("Wciśnięcie dowolnego klawisza rozpocznie uruchamianie serwerów - " + ServerCouneter);
            Console.ReadKey();
            StartDate = DateTime.UtcNow;
            Task.Run(RunServers).Wait();
            for (var i = 0; i < Servers.Count; i++)
            {
                Servers[i].Stop();
            }
            Console.WriteLine("Koniec");
        } 
        private static async Task RunServers()
        {
            var res = Parallel.For(0, Servers.Count, (i) => {
                Task.Run(Servers[i].Start);
            });
            while (ServerCouneter > 0 || !res.IsCompleted)
            {
                await Task.Delay(1);
            }
        } 

        public static void StartServer(int port)
        {
            var timeDelta = DateTime.UtcNow - StartDate;
            Console.WriteLine(string.Format("Serwer na porcie {0} włączony w wczasie {1}", port, timeDelta));
            ServerCouneter--;
        }
    }

    internal class Server
    {
        public int Port { get; set; }
        public Server(int port)
        {
            Port = 10000 + port;
        } 
        private System.Net.Sockets.TcpListener Listener;
        public void Start()
        {
            try
            {
                Listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, Port);
                Listener.Start();
                Program.StartServer(Port);
                while (true)
                {
                    Listener.AcceptTcpClient();
                }
            }
            catch {
                ///obsługa błedu WSACancelBlockingCall
            }
        }
        public void Stop()
        {
            Listener.Stop();
            Listener = null;
        }
    }
}

Na początku jest tworzona instancja 20 serwerów na porcie od numeru 10000 i zwiększane co jeden Następnie, za taska i pętli są uruchamiane wszystkie serwery - kiedy zostaną uruchomione, następuje ich wyłącznie. Efektem tego są takie wpisy w konsoli:
Screen

I właśnie... czy da się jakoś to przyspieszyć? Tworzenie serwera przy użyciu soketa i bind'a daje ten sam rezultat. Uruchomienie jako admin też nie pomogło. Całość jest napisane jest w Net 5.0. Jeśli ktoś ma pomysł jak to zrobić to będę bardzo wdzięczny :)

mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
1

Słabo znam temat, ale ztcw ThreadPool ma jakiś limit jak szybko są tworzone nowe wątki. Myślę, że do twojego przypadku użycia - duża liczba długo żyjących wątków - bardziej nadaje się Thread niż Task.

Sunnyline2
  • Rejestracja:około 8 lat
  • Ostatnio:około 3 lata
  • Postów:58
0

Parallel ma metodę która przyjmuje ParallelOptions i tam możesz ustawić maksymalną liczbę współbieżnych zadań.
Może spróbuj wpisać tam jakaś większą liczbę i porównaj czasy.

mad_penguin
mad_penguin
jedyne co jest odpalane przez Parallel to Task.Run, a ono nie blokuje, więc to raczej nie pomoże
WileCoyote
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Poznań
  • Postów:20
0

próbowałem obu pomysłów - niestety tu i tu jest ten sam efekt. Pierwsze 4 serwery tworzą się w mniej niż sekundę, potem po sekundzie każdy.

Kod z ThreadPool:

Kopiuj
        private static async Task RunServers()
        {
            var res = Parallel.For(0, Servers.Count, (i) => {
                ThreadPool.QueueUserWorkItem(StartThread, Servers[i]);
            });
            while (ServerCouneter > 0 || !res.IsCompleted)
            {
                await Task.Delay(1);
            }
        }
        static void StartThread(object state)
        {
            if (state is Server server)
            {
                server.Start();
            }
        }

oraz kod z ParallelOptions. Domyślnie liczba zadań przyjmuje -1.

Kopiuj
private static async Task RunServers()
{
    var options = new ParallelOptions();
    options.MaxDegreeOfParallelism = Servers.Count;
    var res = Parallel.For(0, Servers.Count, options, (i) => {
        Task.Run(Servers[i].Start);
    });
    while (ServerCouneter > 0 || !res.IsCompleted)
    {
        await Task.Delay(1);
    }
}

Może ktoś ma jeszcze jakiś pomysł?

Wilktar
  • Rejestracja:ponad 5 lat
  • Ostatnio:4 dni
  • Postów:82
0

Użyj metod asynchronicznych.

Nie analizowałem dokładnie kodu ale wygląda na to że problem jest w:

Kopiuj
                while (true)
                {
                    Listener.AcceptTcpClient();
                }

po użyciu tego działa dużo szybciej:

Kopiuj
                 ....  
                 Listener.BeginAcceptTcpClient(callback, null);
                ...

        public void callback(IAsyncResult asyncResult)
        {
            Console.WriteLine(DateTime.Now);
            return;
        }

Chciałby nad poziomy człek, a tu ciągle niż,
Nie uciągnie pusty łeb ciężkiej d**y wzwyż.
mad_penguin
mad_penguin
  • Rejestracja:ponad 10 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Rzeszów
5

Samo bezpośrednie użycie ThreadPool nie da innego wyniku niż Task.Run, bo taski pod spodem też są wykonywane przez ThreadPool, a domyślna liczba wątków jest określona przez liczbę procesorów: https://docs.microsoft.com/en-us/dotnet/api/system.threading.threadpool.getminthreads?view=netcore-3.1#remarks

Można by zmienić ustawienia thread poola, ale w tym wypadku wciąż myślę, że ręczne tworzenie wątków jest prostszym rozwiązaniem:

Kopiuj
        static void Main(string[] args)
        {
            Servers = new List<Server>();
            for (var i = 0; i < ServerCouneter; i++)
            {
                Servers.Add(new Server(i));
            }
            Console.WriteLine("Wciśnięcie dowolnego klawisza rozpocznie uruchamianie serwerów - " + ServerCouneter);
            Console.ReadKey();
            StartDate = DateTime.UtcNow;
            RunServers();
            // głupie czekanie aż wszystkie serwery się odpalą
            Console.WriteLine("Wciśnięcie dowolnego klawisza rozpocznie zamykanie serwerów - " + ServerCouneter);
            Console.ReadKey();
            for (var i = 0; i < Servers.Count; i++)
            {
                Servers[i].Stop();
            }
            Console.WriteLine("Koniec");
        }
        private static void RunServers()
        {
            foreach (var server in Servers)
            {
                new Thread(server.Start).Start();
            }
        }

W powyższej wersji na moim kompie 240 serwerów startuje w 1,7s :)

edytowany 1x, ostatnio: mad_penguin
WileCoyote
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Poznań
  • Postów:20
0

mad_penguin - Działa! wielkie dzięki ,sporo ostatnio się nad tym głowiłem :D

mad_penguin
mad_penguin
w ramach podziękowań możesz dać łapkę w górę i oznaczyć post jako rozwiązanie ;)
SO
  • Rejestracja:ponad 10 lat
  • Ostatnio:12 miesięcy
1

Można też zamienić:

Kopiuj
while (true)
{
    Listener.AcceptTcpClient();
}

na:

Kopiuj
while (true)
{
   await  Listener.AcceptTcpClientAsync();
}

Wtedy nawet oryginalny kod @WileCoyote działa jak trzeba.
Ja nie wiem czy kiedykolwiek przez kilka ostatnich lat używałem new Thread() tak jak pokazywał @mad_penguin. Do wszystkiego wystarczały mi Taski.

WileCoyote
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Poznań
  • Postów:20
0

@some_ONE: taski dawały radę do 4 instacji serwera tcp/ip. (może to kwestia procesora - nie wiem)
Zmiana akceptacji klientów z synchronicznego na asynchroniczny tutaj nie wpływa na główny problem jakim jest tworzenie serwera za pomocą funkcji Listener.Start(). W kodzie programu możesz zobaczyć że wpis do konsoli jest generowany jeszcze przed rozpoczęciem akceptacji klientów. W każdym razie udało się problem rozwiązać i wygląda teraz to dobrze:
Schemat
Utworzenie 100 instancji zajmuje trochę ponad 2s co uważam za bardzo dobry wynik ;)

SO
  • Rejestracja:ponad 10 lat
  • Ostatnio:12 miesięcy
2

Lol, to odpal ten kod:

Kopiuj
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TestServer
{
    class Program
    {
        private static DateTime StartDate;
        private static int ServerCouneter = 1000;
        private static List<Server> Servers;
        static void Main(string[] args)
        {
            Servers = new List<Server>();
            for (var i = 0; i < ServerCouneter; i++)
            {
                Servers.Add(new Server(i));
            }
            Console.WriteLine("Wciśnięcie dowolnego klawisza rozpocznie uruchamianie serwerów - " + ServerCouneter);
            Console.ReadKey();
            StartDate = DateTime.UtcNow;
            Task.Run(RunServers).Wait();
            for (var i = 0; i < Servers.Count; i++)
            {
                Servers[i].Stop();
            }
            Console.WriteLine("Koniec");
        }
        private static async Task RunServers()
        {
            var res = Parallel.For(0, Servers.Count, (i) => {
                Task.Run(Servers[i].Start);
            });
            while (ServerCouneter > 0 || !res.IsCompleted)
            {
                await Task.Delay(1);
            }
        }

        public static void StartServer(int port)
        {
            var timeDelta = DateTime.UtcNow - StartDate;
            Console.WriteLine(string.Format("Serwer na porcie {0} włączony w wczasie {1}", port, timeDelta));
            ServerCouneter--;
        }
    }

    internal class Server
    {
        public int Port { get; set; }
        public Server(int port)
        {
            Port = 10000 + port;
        }
        private System.Net.Sockets.TcpListener Listener;
        public async Task Start()
        {
            try
            {
                Listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, Port);
                Listener.Start();
                Program.StartServer(Port);
                while (true)
                {
                    await Listener.AcceptTcpClientAsync();
                }
            }
            catch
            {
                ///obsługa błedu WSACancelBlockingCall
            }
        }
        public void Stop()
        {
            Listener.Stop();
            Listener = null;
        }
    }
}

Na nim właśnie odpaliłem 1000 instancji w 400ms

edytowany 1x, ostatnio: some_ONE
WileCoyote
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 2 lata
  • Lokalizacja:Poznań
  • Postów:20
0

Wow! nieźle, przyznaję, działa bardzo dobrze ;)

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)