C# TcpClient читает несколько сообщений через постоянное соединение

Я пытаюсь создать TCP-сервер и клиент, который будет иметь постоянное соединение, чтобы сервер и клиент в любой момент времени могли уведомлять друг друга об определенных «событиях» (так что нажмите вместо опроса).

У меня почти все работает, клиенты могут подключаться, соединение остается открытым, а клиент и сервер могут как писать, так и читать из потока tcp.

Проблема с чтением, я определил границу сообщения, сначала отправив 8 байтов, которые содержат длину сообщения. Как только я его получил, сообщения длины x читаются и возникает событие.

Все это работает нормально, но как только сообщение будет прочитано, я хочу, чтобы «ожидание потока.ReadAsync» ждало новых входящих данных, но оно продолжает зацикливаться (и возвращает 0 данных) вместо ожидания, вызывающего 100% загрузку процессора.

Есть ли способ сказать «Сброс» потоку, чтобы он снова начал ждать, как это было изначально.

Это код для моего tcpclient (используется как для отправки, так и для получения), вы можете перейти к методу RunListener, я не думаю, что остальное имеет значение.

    public class SSLTcpClient : IDisposable {
        /**
         * Public fields
         */
        public SslStream SslStream { get; private set; }

        /**
         * Events
         */
        public ConnectionHandler connected;
        public ConnectionHandler disconnected;
        public DataTransfer dataReceived;

        /**
         * Constructors
         */
        public SSLTcpClient() { }
        public SSLTcpClient(TcpClient pClient, X509Certificate2 pCert) {
            SslStream = new SslStream(
                pClient.GetStream(),
                false,
                new RemoteCertificateValidationCallback(
                    delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
                        return true;
                    }
                ),
                new LocalCertificateSelectionCallback(
                    delegate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) {
                        return new X509Certificate2(pCert);
                    }
                )
            );

            try {
                SslStream.AuthenticateAsServer(pCert, true, SslProtocols.Tls, true);
            } catch (AuthenticationException) {
                pClient.Close();
                return;
            }

            Thread objThread = new Thread(new ThreadStart(RunListener));
            objThread.Start();

            if (connected != null) {
                connected(this);
            }
        }

        /**
         * Connect the TcpClient
         */
        public bool ConnectAsync(IPAddress pIP, int pPort, string pX509CertificatePath, string pX509CertificatePassword) {
            TcpClient objClient = new TcpClient();
            try {
                if(!objClient.ConnectAsync(pIP, pPort).Wait(1000)) {
                    throw new Exception("Connect failed");
                };
            } catch (Exception) {
                return false;
            }
            X509Certificate2 clientCertificate;
            X509Certificate2Collection clientCertificatecollection = new X509Certificate2Collection();
            try {
                clientCertificate = new X509Certificate2(pX509CertificatePath, pX509CertificatePassword);
                clientCertificatecollection.Add(clientCertificate);
             } catch(CryptographicException) {
                objClient.Close();
                return false;
            }

            SslStream = new SslStream(
                objClient.GetStream(), 
                false, 
                new RemoteCertificateValidationCallback(
                    delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { 
                        return true;
                    }
                ), 
                new LocalCertificateSelectionCallback(
                    delegate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) {
                        var cert = new X509Certificate2(pX509CertificatePath, pX509CertificatePassword);
                        return cert;
                    }
                )
            );

            try {
                SslStream.AuthenticateAsClient(pIP.ToString(), clientCertificatecollection, SslProtocols.Tls, false);
            } catch (AuthenticationException) {
                objClient.Close();
                return false;
            }

            Thread objThread = new Thread(new ThreadStart(RunListener));
            objThread.Start();

            if (connected != null) {
                connected(this);
            }
            return true;
        }

        /**
         * Reading
         */
        private async void RunListener() {
            try {
                while (true) {
                    byte[] bytes = new byte[8];
                    await SslStream.ReadAsync(bytes, 0, (int)bytes.Length);

                    int bufLenght = BitConverter.ToInt32(bytes, 0);
                    if (bufLenght > 0) {
                        byte[] buffer = new byte[bufLenght];
                        await SslStream.ReadAsync(buffer, 0, bufLenght);

                        if (dataReceived != null) {
                            dataReceived(this, buffer);
                        }
                    }
                }
            } catch (Exception) {
                Dispose();
            }
        }

        /**
         * Writing 
         */
        public bool Send(byte[] pData) {
            try {
                byte[] lenght = BitConverter.GetBytes(pData.Length);
                Array.Resize(ref lenght, 8);

                SslStream.Write(lenght);
                if (!SslStream.WriteAsync(pData, 0, pData.Length).Wait(1000)) {
                    throw new Exception("Send timed out");
                }
            } catch (Exception) {
                Dispose();
                return false;
            }
            return true;
        }

        public bool Send(string pData) {
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(pData);
            return Send(bytes);
        }

        /**
         * Shutdown
         */
        public void Dispose() {
            SslStream.Close();
            if (disconnected != null) {
                disconnected(this);
            }
        }
    }

person Nico    schedule 13.12.2015    source источник
comment
На первый взгляд, вы вызываете ReadAsync, не глядя на возвращаемый int. Это всегда ошибка кодирования. Прочтите документацию Задача, представляющая асинхронную операцию чтения. Значение параметра TResult содержит общее количество байтов, считанных в буфер. Результирующее значение может быть меньше запрошенного количества байтов, если количество доступных в настоящее время байтов меньше запрошенного числа, или может быть равно 0 (ноль), если достигнут конец потока. .   -  person Scott Chamberlain    schedule 14.12.2015
comment
Я чувствую себя таким глупым сейчас, я закончил поток, мое тестовое приложение, которое я сделал, закрылось после отправки 1 сообщения. По крайней мере, это было простое исправление, спасибо, что указали на ошибку.   -  person Nico    schedule 14.12.2015


Ответы (2)


То, как вы читаете 4 или 8 байтов, неверно. Вам нужно зацикливаться, пока вы на самом деле не получите их. Вы можете получить 1.

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

Вероятно, вам следует использовать BinaryReader, чтобы абстрагироваться от циклов.

Кроме того, вам нужно очистить ресурсы. Почему вы не заворачиваете их в using? Все вызовы Close небезопасны и не нужны.

Также я не понимаю, зачем здесь нужны исключения для потока управления. Проведите рефакторинг.

person usr    schedule 13.12.2015

Всего 2 мысли, которые, надеюсь, помогут улучшить ваш код, но не являются ответом на ваш первоначальный вопрос:

  • Вы отправляете 8 байтов, указывающих следующую длину полезной нагрузки, но используете только 4 из них в следующем вызове BitConverter.ToInt32, поэтому 4 байта будет достаточно.
  • Что произойдет, если передача будет отключена с другой стороны? На мой взгляд, у вас нет возможности определить, что данные, которые вы получили, недействительны. Возможно, создание чего-то вроде небольшого протокола низкого уровня помогло бы, например. 4 bytes raw data length, за которым следует сам raw data, за которым следует some bytes of checksum (что позволит проверить, правильно ли переданы данные, которые вы получили).
person Markus Safar    schedule 13.12.2015
comment
- Вы правы насчет 8 байтов, я изменил 4 байта. - что касается конца передачи, над tcpclient реализована поддержка активности, которая должна обрабатывать это (все еще работает над этим банкоматом), CRC в конце - хорошая идея, все еще думал о том, как я буду проверять данные - person Nico; 14.12.2015