HobbyProggy Ответов: 1

Проблемы с производительностью серверного / клиентского приложения


Привет, чтобы улучшить мое текущее программное обеспечение, я хотел переключить datalogic на серверное приложение, которое имеет более быстрое соединение с базой данных и может кэшировать данные.

До сих пор я писал работающее серверное приложение и клиент, который подключается к этому серверу.
В настоящее время я испытываю проблему производительности на стороне сервера, приводящую к высокой загрузке процессора, и я хотел бы спросить, Может ли кто-нибудь найти эту проблему.

Сервер и клиент должны взаимодействовать асинхронно, так что несколько клиентов могут работать с сервером, и сервер позже создаст очередь, если операции достигнут более высоких уровней.

Ниже вы найдете код, он немного запутан, так как сейчас это всего лишь прототип.
Я уже прочитал несколько статей о CP и переплетениях на этом, и я думаю, что проблема может возникнуть через цикл назад к началу accept.

Почему? -> Потому что, поскольку accept выполняется асинхронно, я ожидаю, что 4 потока застрянут на endAccept, хотя это всего лишь предположение, но кажется, что это по крайней мере один.
Кроме того, если я останавливаю сервер (закрываю прослушивание и все такое), я всегда получаю исключение в серверной части, где находится endAccept.

Буду рад любому совету или намеку на проблему.
Спасибо в афвансе

Что я уже пробовал:

Я написал сервер примерно так:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Security.Cryptography.X509Certificates;

namespace ImsServer.ImsSockets
{
    public class ServerSocket
    {
        private TcpListener _listeningSocket;
        private readonly IPEndPoint _localEndPoint;
        private ServerMain _serverMainUi;
        List<TcpClient> _connectedClients = new List<TcpClient>(500);
        private byte[] _bufferForMessages = new byte[256];

        public ServerSocket(ServerMain InUi)
        {
            //Get Settings from Config File
            _localEndPoint = new IPEndPoint(IPAddress.Any, 12000);
            _serverMainUi = InUi;

            StartListening();
        }

        internal void StartListening()
        {
            try
            {
                _listeningSocket = new TcpListener(_localEndPoint);
                _listeningSocket.Start(100);

                StartAccept();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }         
        }

        internal void StartAccept()
        {
            //Create an AsyncCallbackForAccept and go to ProcessAccept on completion
            AsyncCallback _asyncCallbackForAccept = ProcessAccept;

            //Begin the Accept
            _listeningSocket.BeginAcceptTcpClient(_asyncCallbackForAccept, _listeningSocket);
        }

        private async void ProcessAccept(IAsyncResult InAcceptAsyncResult)
        {
            //Get the Listener from the AsyncResult
            TcpListener _asyncListener = (TcpListener) InAcceptAsyncResult.AsyncState;

            //Accept the client connection
            TcpClient _clientToAccept = _asyncListener.EndAcceptTcpClient(InAcceptAsyncResult);

            await _serverMainUi.SetServerInformationText(Environment.NewLine + "Client : " + _clientToAccept.Client.LocalEndPoint + " Connected");

            _connectedClients.Add(_clientToAccept);

            //SslStream _clientStream = new SslStream(_clientToAccept.GetStream(), false);
            NetworkStream _clientStream = _clientToAccept.GetStream();

            //_clientStream.AuthenticateAsServer(new X509Certificate(), false, true);
            _clientStream.ReadTimeout = 5000;
            _clientStream.WriteTimeout = 5000;

            int _clientIndex = _connectedClients.FindIndex(Cc => Cc == _clientToAccept);

            byte[] _serverMessageToClient = Encoding.UTF8.GetBytes("You are connected to IMS-Server : Client No " + _clientIndex);
            _clientStream.Write(_serverMessageToClient, 0, _serverMessageToClient.Length);

            await _serverMainUi.SetServerInformationText(Environment.NewLine + "Connected Clients : " + _connectedClients.Count);

            _clientStream.Flush();

            //Loop back to accept other requests
            StartAccept();

            while(!_clientStream.DataAvailable) { }

            _clientStream.BeginRead(_bufferForMessages, 0, _bufferForMessages.Length, ReceiveClientMessageCallback, _clientToAccept);
        }

        private async void ReceiveClientMessageCallback(IAsyncResult InAsyncResult)
        {
            //TcpClient _client = _connectedClients.Find(Cc => Cc == (TcpClient)InAsyncResult.AsyncState);
            TcpClient _client = (TcpClient)InAsyncResult.AsyncState;

            if (_client.Connected)
            {
                NetworkStream _clientStream = _client.GetStream();
                byte[] _clientMessage = _bufferForMessages;
                int _noOfBytes = _clientStream.EndRead(InAsyncResult);
                string _clientDecodedMessage = "";

                _clientDecodedMessage = String.Concat(_clientDecodedMessage, Encoding.UTF8.GetString(_clientMessage, 0, _noOfBytes));

                await _serverMainUi.SetServerInformationText(Environment.NewLine + "Client : " + _connectedClients.FindIndex(Cc => Cc == _client) + " wants to " + _clientDecodedMessage);

                if (_clientDecodedMessage == "Disconnect")
                {
                    //We accept the disconnect wish and send a bye bye
                    byte[] _disconnectMessage = Encoding.UTF8.GetBytes("Bye");
                    _clientStream.Write(_disconnectMessage, 0, _disconnectMessage.Length);

                    _connectedClients.Remove(_client);
                }
            }
            else
            {
                _client.Close();
            }
        }

        private void AcceptRequestCompleted(object Sender, SocketAsyncEventArgs InArgs)
        {
            //ProcessAccept(InArgs);
        }

        public bool ServerSocketStop()
        {
            bool _connectionsPending = _listeningSocket.Pending();

            while(_connectionsPending) { }
            _listeningSocket.Stop();
            return true;
        }
    }
}


Клиент делает это:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ServerMiniTester
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private TcpClient _client;
        private byte[] _serverResponse = new byte[256];

        public MainWindow()
        {
            InitializeComponent();
        }

        private void BtConnectToServerClick(object sender, RoutedEventArgs e)
        {
            _client = new TcpClient();

            try
            {
                _client.Connect(IPAddress.Parse("10.176.2.8"),12000);

                while(!_client.Connected) { }

                //SslStream _clientStream = new SslStream(_client.GetStream(), false, ValidateServerCertificate, null);
                NetworkStream _clientStream = _client.GetStream();

                //while(!_clientStream.CanRead) { }

                //_clientStream.AuthenticateAsClient("10.176.2.8");
                AsyncCallback _receiveServerMessage = ReceiveServerMessageCallback;

                _serverResponse = new byte[256];
                _clientStream.BeginRead(_serverResponse, 0, _serverResponse.Length, _receiveServerMessage, _client);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
            }
        }

        private void BtDisconnectServerClick(object Sender, RoutedEventArgs E)
        {           
            try
            {
                NetworkStream _clientStream = _client.GetStream();

                byte[] _disconnectMessage = Encoding.UTF8.GetBytes("Disconnect");
                _clientStream.Write(_disconnectMessage, 0, _disconnectMessage.Length);

                while(!_clientStream.DataAvailable) { Thread.Sleep(50);}

                AsyncCallback _receiveServerMessage = ReceiveServerMessageCallback;

                _serverResponse = new byte[256];
                _clientStream.BeginRead(_serverResponse, 0, _serverResponse.Length, _receiveServerMessage, _client);
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
            }
        }

        private async void ReceiveServerMessageCallback(IAsyncResult InAsyncResult)
        {
            NetworkStream _clientStream = ((TcpClient)InAsyncResult.AsyncState).GetStream();
            byte[] _serverMessage = _serverResponse;
            int _noOfBytes = _clientStream.EndRead(InAsyncResult);

            string _serverDecodedMessage = "";

            _serverDecodedMessage = String.Concat(_serverDecodedMessage, Encoding.UTF8.GetString(_serverMessage, 0, _noOfBytes));

            if (_serverDecodedMessage == "Bye")
            {
                await SetClientInformationText(Environment.NewLine + "Server response : " + _serverDecodedMessage +
                                               Environment.NewLine + "*****Server accepted Disconnect*****");

                _client.Close();
                _client.Dispose();

                await SetClientInformationText(Environment.NewLine + "*****Disconnected from Server*****");
            }
            else
            {
                await SetClientInformationText(Environment.NewLine + "Server response : " + _serverDecodedMessage);
            }
        }

        public async Task SetClientInformationText(string InText)
        {
            await LbServerResponse.Dispatcher.InvokeAsync(() => LbServerResponse.Text += InText);
        }

        private bool ValidateServerCertificate(Object Sender, X509Certificate InCertificate, X509Chain InChain, SslPolicyErrors InSslPolicyErrors)
        {
            return true;
        }
    }
}

RickZeeland

Я всегда борюсь с асинхронным кодом, до такой степени, что я отказался от его использования. Я использую старомодный статический класс TcpReceiver, который использует потоки для каждого клиентского сообщения, и он отлично работает !

HobbyProggy

Поскольку я еще не дошел до этого момента, я попробую асинхронный код. Но если мне нужно будет вернуться к старомодному (ТМ) рабочему коду, я позвоню вам, хорошо?

RickZeeland

Хорошо со мной, было бы лучше, если бы вы получили асинхронный код, работающий, это должно иметь преимущества, особенно когда у вас много клиентов, по-старому это означало бы Новый Поток для каждого клиента !

1 Ответов

Рейтинг:
5

Rob Philpott

У вас там есть интересное сочетание различных типов асинхронного кода! И я вижу неосторожное использование коллекционной поперечной нити и т. д.

Но это:

while(!_clientStream.DataAvailable) { }

похоже на ... очень плотная петля, которая, скорее всего, заставит ваш вентилятор процессора попытаться взлететь.

По крайней мере, шлепни по нитке.Sleep(0); там, но очевидно, что это далеко не оптимально!


HobbyProggy

Да, я новичок в этом деле, пустое время на самом деле просто для тестирования. Но если бы это было проблемой моей загрузки процессора, я бы изменил ее на сумму, которая имеет смысл.

HobbyProggy

Ого, это точно сработало! Процессор сейчас охлаждается, даже с подключенными 25 клиентами.

Большое спасибо!

Rob Philpott

Приятно! :)

HobbyProggy

Кстати, имеет ли смысл спать 100 в зависимости от времени обработки?

Rob Philpott

Не уверен, что понял вопрос. Sleep остановит выполнение текущего потока и больше не будет планировать его до тех пор, пока не пройдет определенное количество времени. Между тем, ваш поток, который сейчас находится в коме, все еще использует свои ресурсы - пространство стека и тому подобное. Это, вероятно, не имеет большого значения в обычном приложении, но когда вы перемещаете серверную часть в службу, которая может обрабатывать много параллельных подключений, вам нужно быть осторожным, вы можете получить много спящих потоков, которые ничего не делают, но потребляют ресурсы. Он не масштабируется.

Итак, вы хотите избежать любого вида спящего сервера. Вы могли бы использовать задачу.Вместо этого задержка (если в асинхронном методе), что гораздо лучше, но общая предпосылка ожидания на стороне сервера немного тревожит. Вместо этого стремитесь реагировать, когда что - то происходит-получаемые данные и т. д. с помощью асинхронных методов.



HobbyProggy

Я понимаю, так что заставить сервер спать-не самая лучшая идея. Но как я могу гарантировать, что общение
Клиент -сервер (мне нужны данные) -
Сервер -клиент (я дам вам данные) -
Клиент -> сервер (ОК) -
Сервер -> клиент (буфер будет) -
Клиент -> сервер (ОК) -
Сервер -клиент (вот данные)
будет ли все гладко, если они не испортятся?
Будет ли это просто работать, если я удалю сон и буду ждать данных? Или это все засорит?

Rob Philpott

Ты действительно не хочешь спать. Несколько советов/предложений:

1. Не путайте код listen/accept с вещами, которые обрабатывают соединение. Запустите задачу, которая прослушивает, ожидает принятия и запускает TcpClient для новой задачи, а затем повторяет ее до тех пор, пока не будет отменена. Создание этой асинхронности означает, что единственный раз, когда поток находится в игре, - это когда он фактически принимает и создает задачу для обработки соединения.

2. Используйте методы ReadAsync/WriteAsync в вашем сетевом потоке для части разговора. То же самое относится и к потоку, только в игре, когда данные получены или готовятся к отправке - никогда не блокируются.

3. Будьте осторожны с Read(). Он возвращает количество прочитанных байтов, которое может быть не всем, что вы ожидаете, это зависит от того, как данные пакуются в дейтаграммы. Обычно считывание происходит в цикле, пока вы не получите все данные, которые ожидаете.

4. Разделите ваши проблемы, в основном описанные в (1), но бизнес прослушивания соединений и передачи их обработчикам соединений-это в значительной степени то, как работает каждая служба на основе TCP, поэтому постарайтесь сохранить это как можно более общим. Возможно, вы сможете повторно использовать его в другом приложении.

Это не самая простая вещь для программирования TCP, и очень трудно получить удар!