S
S
Slonopotam442018-04-28 19:32:36
C++ / C#
Slonopotam44, 2018-04-28 19:32:36

How to establish data exchange between the client and the server?

There is communication between the server and the client through communicator classes.
Basic communicator

public abstract class TcpCommunicatorBase : ICommunicator
    {
        private readonly byte[] _bufer = new byte[1024];
        protected TcpClient _client;
        protected Encoding _encoding;
        private object _streamLock;
        private object _clientLock;

        /// <summary>
        /// Переменная необходимая для остановки прослушивания
        /// </summary>
        private bool _stopListen;

        protected int? expectedMessageLenght;

        protected string Message = string.Empty;

        protected TcpCommunicatorBase(TcpClient client, Encoding encoding)
        {
            _streamLock = new object();
            _clientLock = new object();
            lock (_clientLock)
            {
                _encoding = encoding;
                _client = client;
                MessageReaded = (s, a) => { };
                ConnectionBroken = (s, a) =>
                {
                    _client.Close();
                    EndListenForMessage();
                };
            }
        }

        /// <summary>
        /// Получение потока
        /// </summary>
        protected NetworkStream Stream
        {
            get
            {
                lock (_clientLock)
                {
                    return _client.GetStream();
                }
            }
        }

        /// <summary>
        /// Отправка сообщения
        /// </summary>
        /// <param name="text"></param>
        public virtual void SendMessage(string text)
        {
            var MessageLenght = _encoding.GetByteCount(text).ToString()+":";//В начало сообщения добавляется его длинна в байтах
            var enumerable = _encoding.GetBytes(MessageLenght+text);
            lock (_streamLock)
            {
              try
              {
                Stream.Write(enumerable, 0, enumerable.Length);
              }
              catch (Exception e)
              {
          Debug.Print(e.Message);
              }
            }
        }

        /// <summary>
        /// Комманда к началу ожидания сообщений
        /// </summary>
        public virtual void BeginListenForMessage()
        {
            _stopListen = false;
            lock (_streamLock)
            {
              try
              {
                Stream.BeginRead(_bufer, 0, _bufer.Length, AsyncCallback, null);
              }
              catch (Exception e)
              {
                Debug.Print(e.Message);
              }
            }
        }

        /// <summary>
        /// Комманда к окончанию ожидания сообщений
        /// </summary>
        public virtual void EndListenForMessage()
        {
            _stopListen = true;
        }
        /// <summary>
        /// Событие к прочтению сообщения
        /// </summary>
        public event EventHandler<MessageReadedEventArgs> MessageReaded;
        /// <summary>
        /// Событие к разрыву связи
        /// </summary>
        public event EventHandler ConnectionBroken;

        /// <summary>
        /// Подключение к IP
        /// </summary>
        /// <param name="adres"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public virtual bool Connect(string adres, int port)
        {
            lock (_clientLock)
            {
                _client?.Close();
                try
                {
                    _client = new TcpClient(adres, port);
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }


        /// <summary>
        /// Отключение коммуникатора
        /// </summary>
        public virtual void Disconect()
        {
            lock (_clientLock)
            {
                _client?.Close();
            }
        }

        /// <summary>
        /// Прасер для получения длинны сообщения
        /// </summary>
        protected Parser<int> MessageLenght =
            from Lenght in Parse.Decimal
            select int.Parse(Lenght);

        /// <summary>
        /// Парсер для получения тела сообщения
        /// </summary>
        protected Parser<string> MessageBody =
            from Lenght in Parse.Digit.Many()
            from spliter in Parse.Char(':').Once()
            from body in Parse.CharExcept('\0').Many().Text()
            select body;

        /// <summary>
        /// Проверка сообщения на корректность
        /// (соответствие фактической длинны сообщения заявленной) 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        protected virtual bool IsCorrect(string message)
        {
            int Lenght = MessageLenght.Parse(message);
            string Body = MessageBody.Parse(message);

            try
            {
                if (Lenght == _encoding.GetByteCount(Body))
                    return true;
            }
          catch
          {
            // ignored
          }

          return false;
        }

        ~TcpCommunicatorBase()
        {
            _client.Close();
        }

        /// <summary>
        /// Вызывается при получение сообщения
        /// </summary>
        /// <param name="ar"></param>
        protected virtual void AsyncCallback(IAsyncResult ar)
        {
            Message += _encoding.GetString(_bufer);
            while (Stream.DataAvailable)//В случае если сообщение больше длинны буфера, то считываем его несколько раз
            {
                for (int i = 0; i < _bufer.Length; i++)
                    _bufer[i] = 0;

                Stream.Read(_bufer, 0, _bufer.Length);
                Message += _encoding.GetString(_bufer);
            }

            if (IsCorrect(Message))
            {
                //Проверка сообщения на корректность

                if (!_stopListen)//Если прослушку не остановили
                {
                    MessageReaded(this, new MessageReadedEventArgs
                    {
                        Message = MessageBody.Parse(Message)
                    });
                    Message = String.Empty;
                    BeginListenForMessage();
                }

                Message = string.Empty;
                expectedMessageLenght = null;
            }
        }
    }

At some point, the server sends a message, but the client cannot catch it (AsyncCallback does not start), despite the fact that no one turned off listening.
What could be the problem?
Is the code thread-safe enough?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
A
Alexander Yudakov, 2018-04-29
@Slonopotam44

1. Remove all "locks" - they are of no use here.
2. Remove all "try {...} catch (Exception e) { Debug.Print(e.Message); }
There is no benefit from them either.
3. Remove IsCorrect. Read the length of the message, then read the required number of bytes. A what you call "Actual length" is a figment of your imagination
4. You need to read not until the DataAvailable, but until the required number of bytes is received.Synchronously or asynchronously, you will do it - no difference 5.
And now the actual answer to your question:
The message reader mechanism dies when the reader reads faster than the writer writes (or the network transmits). In this case, DataAvailable will return false; your algorithm, instead of waiting for the rest of the message, breaks reading mid-message, says IsCorrect() == false, and hangs.
6. It makes sense to simplify the message format: the first 4 bytes are the length of the body in bytes; then the body itself (e.g. in UTF-8 if you want text). Accordingly, we first read 4 bytes, and then as many more as indicated there.
PS
7. Encoding.GetString() should only be called on the entire message; if you do it on a part of the message - the result is unpredictable.
8. Of course, it is necessary to process network exceptions. However, Debug.Print is not the solution to the problem. It makes sense to close the socket. And then - either notify your client about this problem (who processes the received messages), or, in a good way, try to open another connection and try again to continue working so that the client does not notice anything :)

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question