Witam
Zrobiłem dwie aplikacje, serwerową i kliencką.

Aplikacja serwerowa działa w taki sposób że po kliknięciu "start server" tworzy tcplistener'a na adresie lokalnym i porcie 7 a następnie tworzy i uruchamia dwa nowe wątki z których pierwszy jest wieczną pętlą (z każdą iteracją wątek ten jest blokowany na 270ms) w której sprawdza się czy są jakieś połączenia oczekujące (pending) a następnie się je akceptuje dodając takiego klienta do listy. W tym wątku także cała lista klientów jest poolingowana, to znaczy że do każdego z nich wysyłane są krótkie wiadomości "null" aby sprawdzić czy któryś z nich się nie rozłączył, jeśli to się potwierdzi to wówczas taki klient usuwany jest z listy.
W drugim wątku także jest wieczna pętla i z każdą jej iteracją wątek jest usypiany na 100 ms. W pętli tej jest odbieranie informacji ze streamów wszystkich klientów z listy i jeśli coś jest do odebrania to do takich klientów wysyłane jest echo.

Aplikacja kliencka działa tak że po połączeniu uruchamiany jest jeden dodatkowy wątek do odbioru danych. Znowu wieczna pętla z oczekiwaniem 100ms, dane odbiorcze są prezentowane w okienku. Mamy dodatkowo przycisk send który pozwala na wysłanie czegoś tam do serwera, celem tego żeby spodziewać się echa od niego.

Aplikacje jeden do jeden pracują wyśmienicie. Mam problem ponieważ w przypadku jak podłączam do serwera dwóch klientów to wówczas wiadomości nie dochodzą. Tzn. dochodzą ale nie od razu, albo też należy naprzemiennie coś wysyłać raz od jednego raz od drugiego klienta.
Jaka może być tego przyczyna?

Kod serwera:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace Echo_server
{
    public partial class Form1 : Form
    {
        private int ClientsCountConnected;
        private TcpListener server;
        private Thread pool, receive;
        private List<TcpClient> clients;
        private List<NetworkStream> streams;
        private List<int> indexes;

        public Form1()
        {
            streams = new List<NetworkStream>();
            clients = new List<TcpClient>();

            ClientsCountConnected = 0;
            InitializeComponent();
        }

        private void DebugPrint(string s)
        {
            /* dodaj tekst, znak nowej linii i potem przeklej to co w schowku */
            DebugText.AppendText(s + "\n");

            /* idź na koniec tekstu */
            DebugText.ScrollToCaret();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            /* ustawienie domyślnych wartości pól wyboru portu oraz licznika klientów połączonych */
            Port.Text = "7";
            ClientsCount.Text = "Clients count: " + ClientsCountConnected.ToString();
        }

        private void Start_Click(object sender, EventArgs e)
        {
            /* próba odpalenia serwera na wpisanym porcie */
            try
            {
                /* przechwycenie wartości numeru portu z założeniem domyślnej wartości 7 */
                int PortNumber;
                try
                {
                    PortNumber = Convert.ToInt32(Port.Text);
                }
                catch (Exception)
                {
                    PortNumber = 7;
                }

                /* wykreowanie poprawnej wartości w polu port */
                Port.Text = PortNumber.ToString();

                /* odpalamy serwer na adresie własnym z możliwością nasłuchu na dowolnym interfejsie sieciowym i wybranym porcie */
                server = new TcpListener(IPAddress.Parse("0.0.0.0"), PortNumber);
                server.Start();

                /* jeśli wszystko jest w porządku i serwer wystartował */
                DebugPrint("Server is working on port " + Port.Text);
                DebugPrint("Waiting for new client");

                /* wygaszenie przycisku start i aktywacja przycisku stop */
                Start.Enabled = false;
                Stop.Enabled = true;

                /* utworzenie dwóch nowych wątków - jeden do sprawdzania stanu i akceptacji połączeń od klientów i wprowadzania ich do listy */
                /* drugi zaś odpowiada za odbieranie i wysyłanie komunikatów do klientów połączonych (obecnych na liście) */
                pool = new Thread(PoolLoop);
                pool.Start();

                receive = new Thread(ReceiveLoop);
                receive.Start();
            }
            catch (Exception)
            {
                /* jeśli serwer nie umie wystartować na tym porcie */
                DebugPrint("Can not start the server");
            }
        }

        private void Stop_Click(object sender, EventArgs e)
        {
            try
            {
                /* próba zatrzymania serwera */
                server.Stop();

                /* próba wyłączenia wątków do poolingu i przetwarzania komunikatów */
                pool.Abort();
                receive.Abort();

                DebugPrint("Server stopped");

                /* wygaszenie przycisku stop i ponowna aktywacja przycisku start */
                Start.Enabled = true;
                Stop.Enabled = false;
            }
            catch (Exception)
            {
                /* jeśli serwer nie może być zatrzymany z jakiś powodów */
                DebugPrint("Can not stop the server or close a socket");
            }
        }

        /* metoda jest pomocna przy konieczności dostępu do obiektu tekstowego z poziomu innego wątku */
        public void PrintDebugText(string text)
        {
            /* sprawdzenie czy sterowanie jest w innym wątku niż główny */
            if (this.DebugText.InvokeRequired)
            {
                /* jeśli tak to umieszczenie w kolejce głównego wątku wywołania metody */
                this.DebugText.BeginInvoke(new MethodInvoker(delegate() { PrintDebugText(text); }));
            }
            else
            {
                /* jeśli nie (tzn. sterowanie w wątku głównym) to wówczas wywołujemy metodę normalnie */
                this.DebugPrint(text);
            }
        }

        /* metoda jest pomocna przy konieczności dostępu do obiektu tekstowego z poziomu innego wątku */
        public void ChangeLabelText(string text)
        {
            /* sprawdzenie czy sterowanie jest w innym wątku niż główny */
            if (this.ClientsCount.InvokeRequired)
            {
                /* jeśli tak to umieszczenie w kolejce głównego wątku wywołania metody */
                this.ClientsCount.BeginInvoke(new MethodInvoker(delegate () { ChangeLabelText(text); }));
            }
            else
            {
                /* jeśli nie (tzn. sterowanie w wątku głównym) to wówczas wywołujemy metodę normalnie */
                this.ClientsCount.Text = text;
            }
        }

        /* nieskończona pętla w osobnym wątku tworzonym bezpośrednio po uruchomieniu serwera (odbieranie od połączonych klientów) */
        private void ReceiveLoop()
        {
            try
            {
                while(true)
                {
                    /* zamrożenie wątku na 10 ms */
                    Thread.Sleep(100);

                    /* odczyt z bufora odbiorczego wszystkich danych dla wszystkich klientów */
                    foreach (NetworkStream stream in streams)
                    {
                        /* deklaracja tablicy na dane odbiorcze */
                        Byte[] data = new Byte[256];

                        /* utworzenie stringa złożonego z odebranych od klienta danych */
                        String responseData = String.Empty;

                        /* odczytaj wszystko */
                        Int32 bytes;
                        try
                        {
                            bytes = stream.Read(data, 0, data.Length);
                        }
                        catch (Exception)
                        {
                            bytes = 0;
                        }

                        /* jeśli były jakieś dane do odczytania to rozkoduj je na postać ASCII */
                        if (bytes > 0)
                        {
                            responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);

                            /* wyświetl je w okienku */
                            PrintDebugText("Receive from client: " + responseData);

                            /* i próbuj odesłać echo do tegoż klienta */
                            Byte[] echo = System.Text.Encoding.ASCII.GetBytes(responseData);

                            try
                            {
                                stream.Write(echo, 0, echo.Length);
                                PrintDebugText("Send echo to client: " + responseData);
                            }
                            catch (Exception)
                            {

                            }
                        }

                    }
                }
            }
            catch (Exception)
            {

            }
        }

        /* nieskończona pętla w osobnym wątku tworzonym bezpośrednio po uruchomieniu serwera (pooling klientów i przyjmowanie połączeń) */
        private void PoolLoop()
        {
            try
            {
                while (true)
                {
                    /* zamrożenie wątku na 100 ms */
                    Thread.Sleep(271);

                    /* czy są jakieś oczekujące przychodzące połączenia */
                    if (server.Pending())
                    {
                        TcpClient temp = server.AcceptTcpClient();

                        /* zaakceptowanie połączenia od klienta TCP i dodanie go do listy */
                        clients.Add(temp);

                        /* dodanie ścieżki wymiany danych do listy z nowo dołączonym klientem */
                        streams.Add(temp.GetStream());

                        /* zwiększenie o jeden ilości połączonych klientów */
                        ClientsCountConnected++;

                        ChangeLabelText("Clients count: " + ClientsCountConnected.ToString());
                    }

                    int i = 0;
                    indexes = new List<int>();

                    foreach (NetworkStream x in streams)
                    {
                        try
                        {
                            x.WriteByte(0);
                        }
                        catch (Exception)
                        {
                            ClientsCountConnected--;
                            ChangeLabelText("Clients count: " + ClientsCountConnected.ToString());
                            indexes.Add(i);
                        }

                        /* zwiększ bieżący index o jeden */
                        i++;
                    }

                    /* usunięcie wszystkich zarejestrowanych połączeń które wygasły */
                    foreach (int k in indexes)
                    {
                        streams.RemoveAt(k);
                        clients.RemoveAt(k);
                    }
                }
            }
            catch (Exception)
            {

            }
        }
    }
}

Kod klienta:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace Echo_client
{
    public partial class Form1 : Form
    {
        TcpClient client;
        NetworkStream stream;
        Thread receive;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            /* ustaw domyślne wartości widoczności przycisków */
            Connect.Enabled = true;
            Disconnect.Enabled = false;
            Send.Enabled = false;

            /* ustaw domyślne wartości w polach adres i port */
            Port.Text = "7";
            Address.Text = "localhost";
        }

        private void DebugPrint(string s)
        {
            /* dodaj tekst i znak nowej linii */
            DebugText.AppendText(s + "\n");
            
            /* idź na koniec tekstu */
            DebugText.ScrollToCaret();
        }

        private void Connect_Click(object sender, EventArgs e)
        {
            /* próba wyciągnięcia docelowego adresu IP przy zastosowaniu serwera DNS */
            try
            {
                IPAddress[] addresses = Dns.GetHostAddresses(Address.Text);
                int port = Int32.Parse(Port.Text);

                /* próba połączenia do serwera */
                try
                {
                    client = new TcpClient(addresses[addresses.Length - 1].ToString(), port);
                    stream = client.GetStream();

                    PrintDebugText("Connection established");

                    /* wygaś przycisk connect a uaktywnij disconnect i send */
                    Connect.Enabled = false;
                    Disconnect.Enabled = true;
                    Send.Enabled = true;

                    /* utwórz nowy wątek realizujący odbieranie */
                    receive = new Thread(ReceiveLoop);
                    receive.Start();
                }
                catch (Exception)
                {
                    PrintDebugText("Connection refused");
                }
            }
            catch (Exception)
            {
                PrintDebugText("DNS can not resolve this address or port");
            }
        }

        private void ReceiveLoop()
        {
            /* klauzula chroni wieczną pętlę odbierającą dane przed wyjątkami */
            try
            {
                while (true)
                {
                    /* zamrożenie wątku na 100 ms */
                    Thread.Sleep(100);

                    /* deklaracja bufora na dane odbiorcze */
                    Byte[] data = new Byte[256];

                    /* komunikat odbiorczy w formie znaków ASCII */
                    String responseData = String.Empty;

                    /* odczytaj wszystko */
                    Int32 bytes = stream.Read(data, 0, data.Length);
                    if (bytes > 0)
                    {
                        /* poskładaj stringa ze wszystkich odebranych danych */
                        responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
                    }

                    /* jeśli string nie jest pusty to wyświetl go kończąc znakiem nowej linii */
                    if ((responseData != String.Empty) && (responseData != "\0"))
                    {
                        PrintDebugText("Receive from server: " + responseData);
                    }
                }
            }
            catch (Exception)
            {
                /* jeśli z jakichś powodów wystąpi wyjątek w nieskończonej pętli to uznaje się że połączenie zostało zerwane */
                /* należy zatem ustawić flagę żądania zatrzymania wątku odbiorczego i wykonania procedury zamykania połączenia */
                Disconnect_Click(null, null);
            }
        }

        private void Disconnect_Click(object sender, EventArgs e)
        {
            /* próba zamknięcia połączenia z serwerem */
            try
            {
                receive.Abort();
                client.Close();
                
                PrintDebugText("Client disconnected");
                
                /* ustaw domyślne wartości widoczności przycisków */
                Connect.Enabled = true;
                Disconnect.Enabled = false;
                Send.Enabled = false;
            }
            catch (Exception)
            {
                
            }
        }

        /* metoda jest pomocna przy konieczności dostępu do obiektu tekstowego z poziomu innego wątku */
        public void PrintDebugText(string text)
        {
            /* sprawdzenie czy sterowanie jest w innym wątku niż główny */
            if (this.DebugText.InvokeRequired)
            {
                /* jeśli tak to umieszczenie w kolejce głównego wątku wywołania metody */
                this.DebugText.BeginInvoke(new MethodInvoker(delegate () { PrintDebugText(text); }));
            }
            else
            {
                /* jeśli nie (tzn. sterowanie w wątku głównym) to wówczas wywołujemy metodę normalnie */
                this.DebugPrint(text);
            }
        }

        private void Send_Click(object sender, EventArgs e)
        {
            /* jeśli jest coś do wysłania */
            if (MessageText.Text != String.Empty)
            {
                /* utworzenie tablicy bajtów powstałych na skutek kodowania tekstu kodem ASCII */
                Byte[] data = System.Text.Encoding.ASCII.GetBytes(MessageText.Text);
                
                /* próba wysłania */
                try
                {
                    stream.Write(data, 0, data.Length);
                    PrintDebugText("Client send to server: " + MessageText.Text.ToString());
                }
                catch (Exception)
                {
                    /* jeśli wysyłanie się nie powiodło to uznaje się że przyczyną tego jest zerwane połączenie */
                    /* na skutek czego należy zamknąć połączenie wyłączając również przy tym wątek odbiorczy */
                    Disconnect_Click(null, null);
                }
            }
        }
    }
}

Proszę o pomoc.