C#TcpClient通过持久连接读取多条消息

时间:2015-12-13 21:38:08

标签: c# tcpclient persistent

我正在尝试创建一个TCP服务器&将具有持久连接的客户端,以便服务器和客户端可以在任何时间点相互通知某些“事件”(因此推送而不是轮询)。

我几乎拥有一切正常,客户端可以连接,连接保持打开,客户端和服务器都可以写入&从tcp流中读取。

问题在于读取,我通过首先发送包含消息长度的8个字节来定义消息边界。 一旦我收到它,就会读取长度为x的消息并引发一个事件。

这一切都运行良好但是一旦读取了消息,我希望“await stream.ReadAsync”等待新的传入数据,但它保持循环(并返回0数据)而不是等待导致100%的cpu使用。 / p>

有没有办法对流说'重置',以便它像原来一样再次开始等待。

这是我的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);
            }
        }
    }

2 个答案:

答案 0 :(得分:1)

您读取4或8个字节的方式是错误的。你需要循环,直到你真正得到它们。你可能得到1。

您在这里和其他地方假设您将阅读您想要的金额。您将至少读取一个字节,如果远程端关闭了连接,则为零。

可能你应该使用BinaryReader来抽象出循环。

此外,您需要清理资源。你为什么不把它们包裹在using中?所有Close次电话都不安全,不需要。

此外,我不明白为什么这里需要控制流的例外。重构那个。

答案 1 :(得分:0)

只有2个想法可能有助于改进您的代码,但无法解答您的初始问题:

  • 您发送的8个字节表示以下有效负载长度,但在以下BitConverter.ToInt32调用中仅使用4个字节,因此4个字节就足够了。
  • 如果从另一侧切断传输会怎样?在我看来,您无法确定您已收到的数据无效。也许构建像小型低级协议之类的东西会有助于4 bytes raw data length,然后是raw data itselft,然后是some bytes of checksum(这样可以验证您收到的数据是否已正确传输)。