Síťová komunikace pro více klientů – .NET – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama
Reklama

Síťová komunikace pro více klientů – .NET – Fórum – Programujte.comSíťová komunikace pro více klientů – .NET – Fórum – Programujte.com

 

Hledá se programátor! Plat 1 800 € + bonusy (firma Boxmol.com)
mimic
~ Anonymní uživatel
6 příspěvků
24. 7. 2011   #1
-
0
-

Dobrý den,

Už několik dní řesim problém pro TCP (nebo socket) server, který pojme více uživatelů. A pokud uživatel serveru pošle zprávu, tak server přesně bude vědět, o jakého uživatele jde. To samé v obráceném případě, aby server uměl přesně poslat zprávu konkrétnímu uživateli.

Dám příklad... Jako uživatel jsem se přihlásím na server, pod svím jménem a heslem, a od té doby co se přihlásím, tak připojení bude aktivní do té doby, dokud se neodhlásím nebo proste neukončím klient... po přihlášení jsou třeba nějaké funkce, které pro toho přihlášeného uživatele získávají informace ze serveru...

Když jsem to stále zkoušel, tak jsem se dostával do problému, že pokud se přhlásilo více lidi najednou, tak ostatním proste padal klient.. tim padem mi zamrznul server a choval se jako když je vypnutý.

Prohledam jsem plno webů, ale prostě nějak nepobírám jak by to mělo vypadat.. vim, že tam nejspíš na straně serveru bude nějaké pole připojených klientů, server bude neustále naslouchat k dalšímu připojení, poté nejspíš další vlákno, které bude zjištovat dostupnost každého klienta nebo nevim...

Můžete mi prosím někdo poradit nebo napsat ukázkový příklad jak by to mělo vypadat pro více klientů?

Sice jsem našel plno věcí i na ofic. stánce MS, ale stejně nějak nechápu jak je to s těma klientama..

Nahlásit jako SPAM
IP: 213.168.183.–
Reklama
Reklama
X30
Newbie
24. 7. 2011   #2
-
0
-
Nahlásit jako SPAM
IP: 81.200.55.–
mimic
~ Anonymní uživatel
6 příspěvků
24. 7. 2011   #3
-
0
-

#2 X3
A není to trochu blbí? Když třeba na serveru bude přihlášených 3000 lidí, tak to bude muset být vytvořeno 3000 vláken? To je celkem maso na CPU si myslim...

Krom toho budu teda počítat, že budu vytvářet vlákna, ale jak poznám, ke kterýmu klientu to danné vlákno patří? Např. na server přijde paket se zprávou "user[xyz]:connect", ke kterýmu se vytvoří nové vlákno a to bude znamenat, že vždy budu muset nějak porovnávat nebo posílat paket s hlavičkou "user[xyz]" aby to poznalo, na jakém vlákně konkrétní uživatel běží nebo jak?

Nahlásit jako SPAM
IP: 213.168.183.–
X30
Newbie
24. 7. 2011   #4
-
0
-

Když na server plánuješ přihlásit 3000 lidí, tak už to samotné bude masakr na internetovou linku...udržet aktivních 3000 spojení není jen tak, v této situaci bude procesor to poslední co bude mít problém.

Poznáš to tak, že při připojení uživatele otevřeš spojení do nového vlákna, komunikace ve vláknu bude vždy jen s tím konkrétním uživatelem.

Nahlásit jako SPAM
IP: 81.200.55.–
X30
Newbie
24. 7. 2011   #5
-
0
-

Podívej se na nějaké tutoriály jak vytvořit multi-user chat...

Nahlásit jako SPAM
IP: 81.200.55.–
mimic
~ Anonymní uživatel
6 příspěvků
24. 7. 2011   #6
-
0
-

Noo jsem koukal na několik stránek, ale stejně nějak nemůžu pochopit jak na každýho klienta vytvoři vlakno, který by bylo stále připojené k serveru a mohlo tak komunikovat server -> client a obráceně :/

using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ChatServer
{
    internal class SocketServer
    {
        private bool running = false;
        private int pendingAcc = 16;
        private int port;
        private IPAddress IP;
        private Socket socket;
        private Hashtable clients;
        private Thread mainThread;

        // nastavuje server - IP, port a maximalni velikost cekajici fronty
        public SocketServer(IPAddress serverIP, int serverPort, int pendingAccInQueue = 16)
        {
            IP = serverIP;
            port = serverPort;
            pendingAcc = pendingAccInQueue;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        // spusti server + vlakno, ktere zacne naslouchat
        public void start()
        {
            try
            {
                socket.Bind(new IPEndPoint(IP, port));
                socket.Listen(pendingAcc);
                running = true;

                mainThread = new Thread(listening);
                mainThread.Start();
            }
            catch (SocketException) { }
        }

        // zastavuje naslouchani (server)
        public void stop()
        {
            socket.Close();
            mainThread.Abort();
            running = false;
        }

        // nasloucha prichozim pripojeni a rozdeluje klienty do dalsich vlaken
        private void listening()
        {
            while (running)
            {
                Socket client = socket.Accept();
                clients.Add(client.RemoteEndPoint, client);
                // TODO : zde by meli byt nejspise spcecialni vlakno, pro kazdy klient
            }
        }
    }
}
Nahlásit jako SPAM
IP: 213.168.183.–
Lukasas0
Newbie
24. 7. 2011   #7
-
0
-

to je lehký, když máš

Socket client = socket.Accept();

vytvoříš nový vlákno (pod clients.add...) a tam pošleš tu proměnnou (socket (client)) a použiješ v tom vláknu cyklus třeba zase while (running)

a např. budeš čekat, dokud nepříjde zpráva. a poté jak se budou klienti připojovat, budou se vytvářet pro každého nová vlákna a server bude držet a komunikovat se všemi. Bude jen na tobě, co s nimi budeš dělat / co jim dovolíš  

Nahlásit jako SPAM
IP: 46.13.20.–
Kyry+2
Newbie
25. 7. 2011   #8
-
0
-

Máš naprostou pravdu. Samotné vytvoření vlákna trvá nějákou dobu a každé vlákno si v .NETU alokuje zhruba 1MB. Pokud máš přes 1000 vláken, systému zabere velkou část času CPU přepínání mezi vlákny a proces se stane nestabilní - přestanou fungovat timery apod. Stačí jedno řídící vlákno a třeba 10 execution threads. Jedno vlákno čeká na připojení clienta a každému vláknu přidělí podle vytížení. "Client" pak jednoduše zavolá nějáký event, který request zpracuje. Pokud by sis nevěděl rady, můžu ti napsat nějáký jednoduchý thread management pro TCP server...

Jinak do 500 clientů bych určitě tento problém neřešil.   

Nahlásit jako SPAM
IP: 77.87.240.–
Vsadil jsem se, že budu mít na fóru nejlepší reputaci, pokud jsem ti pomohl(opravdu),
klikni na bezvýznamné plusko :-)
[b]Whether you think you can, or can't you are right ![/b]
mimic
~ Anonymní uživatel
6 příspěvků
27. 7. 2011   #9
-
0
-

#8 Kyry
To Kyry: To bych byl fakt vděčnej, řešim to už dělší dobu a jedinej problém mám pro těch víc klientů. Koukal jsem na dost webů a videi na youtube, ale stejně to nějak nemůzu dát dohromady :(

Na druhou stranu ty vlákna, co vlákno to klient mi prijdě fakt šílený ikdyž logický, že to může být stabilní, ale mělo by to šílený nároky na PC pro hodně klientech.

Prostě to nějak nemůžu dát dohromady no, takovej jednoduchej tcp server pro více klientů a bylo to stabilní, furt se mi stává, že při otevření více klientů se ty pakety míchaj a vsechno obstaravaji vlastně 2 vlakna no

Jestli máš čas a nevadilo by ti to, tak bych ocenil nějakou pomoc z tyhle strany, ostatní věci si dokážu udělatr sám, ale tohle mě naprosto zastavilo v projektu :(

Nahlásit jako SPAM
IP: 213.168.183.–
Kyry
~ Anonymní uživatel
23 příspěvků
27. 7. 2011   #10
-
+1
-
Zajímavé

Tak jsem ti to napsal, není to žádný bussiness kód (nic jsi mi nedal   ), ale prasárna taky ne..
Chybí tam optimalizace pamětí (přes reference) ale CPU to skoro nežere. Zkoušel jsem a jedno vlákno utáhne 100 clientů v klidu.Kdybys něco... ehm.. přihodil, tak to klidně dodělám, ale takhle to pro mě nemá cenu :-). Tady máš kód:

/* MULTICLIENT TCP SERVER

 * -----------------------------------------------Copyright Kryštof 'Kyry' Hilar-----------------------------------------
                                                             (\_(\
                                                            (=' :') 
                                                            (,(")(")
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace WindowsFormsApplication21
{
    class Server
    {
        TcpListener server;
        public List<Client> clients = new List<Client>();//Seznam vsech pripojenych clientu
        private List<ClientHandler> handlers = new List<ClientHandler>();//"Cisnici" pro clienty

        //Konstruktor - prijima port na kterem nasloucha a pocet "cisniku" - handleru
        public Server(int port,int handlercount)
        {
            this.server = new TcpListener(port);//Vytvori server
            CreateHandlers(handlercount);//Vytvori handlery
            new Thread(Start).Start();
        }

        #region Events
        //Pripojil se client, priradime ho k handleruj
        private void OnClientConnect(TcpClient tcpclient)
        {
            Client client = (new Client(tcpclient, clients.Count + 1));
            clients.Add(client);
            PickFreeHandler().AddClient(client);//Vybereme nejmene zatizeny handler
        }
        //Client nam utekl
        private void OnClientDisconnect(Client client)
        {
            if (clients.Contains(client))
                clients.Remove(client);
        }
        //Client name neco poslal
        private void OnClientMessage(Client client, string message)
        {
            System.Windows.Forms.MessageBox.Show(message);
        }
        #endregion

        //Vytvori handlery podle predem nadefinovaneho poctu
        private void CreateHandlers(int count)
        {
            for (int i = 0; i < count; i++)
            {
                ClientHandler handler = new ClientHandler();
                handler.OnClientMessage += new ClientHandler.ClientMessageHandler(OnClientMessage);
                handler.OnClientDisconnect += new ClientHandler.ClientDisconnectHandler(OnClientDisconnect);
                handlers.Add(handler);
            }
        }
        
        //Spusti sever
       private void Start()
        {
            server.Start();
            while (true)
            {   //Ceka na prichoziho clienta
                OnClientConnect(server.AcceptTcpClient());
            }
        }

         //Najde nejvhodnesi handler (s nejmeni clienty)
        private ClientHandler PickFreeHandler()
        {
             int min = int.MaxValue;
             ClientHandler freeHandler = null;
                 foreach (ClientHandler h in handlers)
                 {
                     if (h.ClientsCount < min)
                     {
                         freeHandler = h;
                         min = h.ClientsCount;
                     }
                 }
                 return freeHandler;
        }


        private class ClientHandler
        {
            public delegate void ClientMessageHandler(Client c, string data);
            public event ClientMessageHandler OnClientMessage;

            public delegate void ClientDisconnectHandler(Client c);
            public event ClientDisconnectHandler OnClientDisconnect;
            private List<Client> clients = new List<Client>();         

            //Vrati pocet obsluhovanych clientu
            public int ClientsCount { get { return clients.Count; } }

            
            //Handler dostal na starost noveho clienta
            public void AddClient(Client client)
            {
                clients.Add(client);
                Handle(client);
            }

            struct MessageEventArgs
            {
                public Client client;
                public string data;
            }
            private void CallMessageEvent(object arg)
            {
                MessageEventArgs args = (MessageEventArgs)arg;
                OnClientMessage(args.client, args.data);
            }


            //Prisly data od clienta 
            private void AsyncCallback(IAsyncResult ar)
            {
                Client client = ar.AsyncState as Client;               
                byte[] data = client.buffer;
                string message = ASCIIEncoding.ASCII.GetString(data);
                if (message != null && message.Trim() != "")
                {
                    MessageEventArgs args = new MessageEventArgs();
                    args.client = client;
                    args.data = message;
                    new Thread(new ParameterizedThreadStart(CallMessageEvent)).Start(args);
                }
                Handle(client);//Pripravime handler na dalsi data od tohoto clienta
            }
            private void Handle(Client c)
            {
                if (c == null || !c.connected)//Client nam utekl, uz ho tedy nebudeme obsluhovat
                {
                    clients.Remove(c);
                    OnClientDisconnect(c);
                    return;
                }
                try
                {
                    c.buffer = new byte[4096];
                    c.clientStream.BeginRead(c.buffer, 0, c.buffer.Length, new AsyncCallback(AsyncCallback), c);//Precteme data od clienta
                }
                catch 
                {
                    clients.Remove(c);
                    OnClientDisconnect(c); 
                }
            }            
        }

        public class Client 
        {
            private int _id;
            public int id {get { return _id; }}
            public bool connected { get { return tcpclient.Connected; }}
            private TcpClient tcpclient;
            public byte[] buffer = new byte[4096];
            public Stream clientStream;
            public StreamReader clientStreamReader;
            public Client(TcpClient _client,int clientid)
            {
                this._id = id;
                this.tcpclient = _client;
                this.clientStream = _client.GetStream();
                this.clientStreamReader = new StreamReader(clientStream);
            }
        
        }

    }
}

new Server(88, 5);//(port, pocet_vlaken - staci i jedno :-))
Nahlásit jako SPAM
IP: 77.87.240.–
mimic
~ Anonymní uživatel
6 příspěvků
27. 7. 2011   #11
-
0
-

#10 Kyry
Hele dík moc, zkusim jak bude čas ;) Můžeš mi kdyžtak napsat na icq 20517399. Až to budu zkoušet a něco mi tam nešlo, tak abych nemusel čekat na odpověď tady třeba celej den :)

Nahlásit jako SPAM
IP: 213.168.183.–
mimic
~ Anonymní uživatel
6 příspěvků
28. 7. 2011   #12
-
0
-

Co si myslíte o této verzi? Dalo by se tam ještě něco vylepšit? Stává se mi, že když se klient odpojí, tak zůstave stále ve frontě :/ A něco by šlo možná elegantněji vyřešit..

Pokud by někdo měl čas, tak bych byl rád, jinak ta verze od Kyryho je celkem dobrá, ale jakmile jsem poslal zprávu na server, tak vlakna začla hned cyklit do konzole :/

Potřeboval bych vyřešit ten server, aby to bylo stabilní, rychlý a jednoduchý pro hodně klientů... pomalu se nejspíš blížim do konce :)

Kdyžtak se na to koukněte a řekněte, co by se mohlo opravit / změnit / vylepšit apod. kdyžtak ICQ 204-517-399, kdo by měl čas teda, bylo by to pro mě rychlejší :)

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Server
{
    //! Trida pro spusteni serveru
    internal class TCP
    {
        // spusti naslouchani na portu
        public static void StartOnPort(int port, int countThreads = 2)
        {
            // vytvorime frontu klientu
            ClientPool clientPool = new ClientPool();

            // stara se o klienty
            ClientService clientService = new ClientService(clientPool, countThreads);
            clientService.start(); // spustime

            // naslouchna na vsech dostupnych IP na portu XYZ
            TcpListener listener = new TcpListener(IPAddress.Any, port);

            try
            {
                listener.Start(); // zkusime spustit naslouchani na portu

                while (true)
                {
                    // nasloucha prichozimu pripojeni
                    TcpClient client = listener.AcceptTcpClient();

                    if (client != null)
                    {
                        Console.WriteLine("The client was connected!");

                        // spracuje prichozi pripojeni (prida klienta do fronty)
                        clientPool.addClient(new ClientHandler(client));
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }

    //! Trida pro tvorbu fronty klientu
    internal class ClientPool
    {
        // vytvoří synchronizovaný obal, kolem fronty klientu
        private Queue syncQueue = Queue.Synchronized(new Queue());

        // pridava klienty do fronty
        public void addClient(ClientHandler client)
        {
            syncQueue.Enqueue(client);
        }

        // vrati a odebere klienta z fronty
        public ClientHandler getAndremoveClient()
        {
            return (ClientHandler)(syncQueue.Dequeue());
        }

        // vrati velikost fronty (pocet klientu ve fronte)
        public int totalClients
        {
            get { return syncQueue.Count; }
        }

        // vrati objekt, ktery lze pouzit jako synchronizovane pripojeni do fronty
        public object getSyncRoot
        {
            get { return syncQueue.SyncRoot; }
        }
    }

    //! Trida pro spravu klientu pomoci vlaken
    internal class ClientService
    {
        private List<Thread> clientsThreads;
        private ClientPool clientPoolService;
        private int clientThreadSpeed;
        private bool serviceRunning = false;

        // vytvorime do fonty urcity pocet vlaken, ktere se budou starat o klienty
        // zaroven nastavuje rychlost vlaken (vychozi je 10x za sekundu)
        public ClientService(ClientPool clientPool, int countThreads, int threadSpeed = 100)
        {
            clientPoolService = clientPool;
            clientsThreads = new List<Thread>(countThreads);
            clientThreadSpeed = threadSpeed;
        }

        // spustime spravu klientu (vytvorime vlakna)
        public void start()
        {
            serviceRunning = true;

            // vytvori a spusti vsechna vlakna
            for (int i = 0; i < clientsThreads.Capacity; i++)
            {
                clientsThreads.Add(new Thread(new ThreadStart(threadProcess)));
                clientsThreads[i].Start();
            }
        }

        // proces, ktery bude kazde vlakno vykonavat
        private void threadProcess()
        {
            // pokud bude spustena sprava vlaken
            while (serviceRunning)
            {
                ClientHandler client = null;

                // ziska pres synchronizovane pripojeni ke klientu
                lock (clientPoolService.getSyncRoot)
                {
                    // pokud ve fronte budou nejaci klienti, tak vyjme posledniho
                    if (clientPoolService.totalClients > 0) client = clientPoolService.getAndremoveClient();
                }

                if (client != null)
                {
                    client.clientProcess(); // vyvola klienta

                    // pokud klient bude stale pripojen, tak ho opet prida do fonty
                    if (client.isAlive) clientPoolService.addClient(client);
                }
                Thread.Sleep(clientThreadSpeed);
            }
        }

        // zastavuje spustena vlakna
        public void Stop()
        {
            serviceRunning = false;

            for (int i = 0; i < clientsThreads.Capacity; i++)
            {
                // blokuje volani vlaken do doby, dokud se neukonci
                if (clientsThreads[i] != null && clientsThreads[i].IsAlive) clientsThreads[i].Join();
            }

            // ukonci pripojeni se vsemi klienty
            while (clientPoolService.totalClients > 0)
            {
                // ukoncuje klienty od posledniho pripojeneho
                ClientHandler client = clientPoolService.getAndremoveClient();
                client.close();
                Console.WriteLine("Connecting with the client was terminated!");
            }
        }
    }

    //! Trida pro drzeni kazdeho klienty (informace o nem)
    internal class ClientHandler
    {
        private TcpClient clientSocket;
        private NetworkStream clientStream;
        private bool clientState = false;
        private byte[] bufferOfBytes;
        private StringBuilder dataTemp = new StringBuilder();
        private string clientData = null;

        // pres konstruktor nastavuje kazdeho klienta
        public ClientHandler(TcpClient tcpClient)
        {
            tcpClient.ReceiveTimeout = 100; // v milisekundach
            clientSocket = tcpClient;
            clientStream = tcpClient.GetStream();
            bufferOfBytes = new byte[tcpClient.ReceiveBufferSize];
            clientState = true;
        }

        // cte data od klienta a uklada do stringu
        public void clientProcess()
        {
            try
            {
                // ziska velikost streamu
                int BytesRead = clientStream.Read(bufferOfBytes, 0, (int)bufferOfBytes.Length);

                if (BytesRead > 0)
                {
                    // uklada obdrzena data od klienta
                    dataTemp.Append(Encoding.ASCII.GetString(bufferOfBytes, 0, BytesRead));
                }
                else dataReceived(); // prijme prazdny paket
            }
            catch (IOException)
            {
                // pokud nastane chyba v IO, tak opet prijme prazdny paket
                dataReceived();
            }
            catch (SocketException)
            {
                // pokud nastane chyba v pripojeni klienta, tak ho odpoji
                clientStream.Close();
                clientSocket.Close();
                clientState = false;
                Console.WriteLine("The client has a problem with the connection and been disconnected!");
            }
        }

        // prijima data od klienta (pouze pokud neprisel prazdny paket)
        private void dataReceived()
        {
            if (dataTemp.Length > 0)
            {
                // pokud paket bude obsahovat zpravu "quit", tak ukonci pripojeni klienta
                bool stop = (String.Compare(dataTemp.ToString(), "quit", true) == 0);

                clientData = dataTemp.ToString(); // ulozi build do stringu
                dataTemp.Length = 0; // vyprazdni temp

                Console.WriteLine(clientData); // vypise zpravu klienta

                // vytvori zpravu pro klienta
                StringBuilder response = new StringBuilder();
                response.Append("Received at ");
                response.Append(DateTime.Now.ToString());
                response.Append("\r\n");
                response.Append(clientData);

                sendDataBack(response.ToString()); // odesla zpravu klientovi

                // pokud klient odesle paket se zpravou "quit", tak ukonci svoje pripojeni
                if (stop)
                {
                    clientStream.Close();
                    clientSocket.Close();
                    clientState = false;
                }
            }
        }

        // odesila data zpet ke klientu
        private void sendDataBack(string message)
        {
            byte[] sendBytes = Encoding.ASCII.GetBytes(message.ToString());
            clientStream.Write(sendBytes, 0, sendBytes.Length);
        }

        // uzavira klientske pripojeni
        public void close()
        {
            clientStream.Close();
            clientSocket.Close();
        }

        // zjistuje, zda je klient pripojen
        public bool isAlive
        {
            get { return clientState; }
        }
    }
}
Nahlásit jako SPAM
IP: 213.168.183.–
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 33 hostů

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý