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

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

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

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

这一切都工作正常,但一旦消息被读取,我希望“await stream.ReadAsync”等待新的传入数据,但它保持循环(并返回0数据)而不是等待导致100%的CPU使用率。

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

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

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

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

也许你应该使用BinaryReader抽象出循环。

此外,您需要清理资源。 你为什么不用它们包装? 所有Close调用都是不安全的,不需要。

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

只有2个想法,希望有助于改进您的代码,但没有回答您的初始问题:

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