Wiele instancji serwera TCP/IP

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:

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 :)

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.

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.

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:

        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.

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ł?

0

Użyj metod asynchronicznych.

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

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

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

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

        public void callback(IAsyncResult asyncResult)
        {
            Console.WriteLine(DateTime.Now);
            return;
        }
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:

        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 :)

0

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

1

Można też zamienić:

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

na:

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.

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

2

Lol, to odpal ten kod:

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

0

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

1 użytkowników online, w tym zalogowanych: 0, gości: 1