我正在尝试创建一个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);
}
}
}
答案 0 :(得分:1)
您读取4或8个字节的方式是错误的。你需要循环,直到你真正得到它们。你可能得到1。
您在这里和其他地方假设您将阅读您想要的金额。您将至少读取一个字节,如果远程端关闭了连接,则为零。
可能你应该使用BinaryReader
来抽象出循环。
此外,您需要清理资源。你为什么不把它们包裹在using
中?所有Close
次电话都不安全,不需要。
此外,我不明白为什么这里需要控制流的例外。重构那个。
答案 1 :(得分:0)
只有2个想法可能有助于改进您的代码,但无法解答您的初始问题:
BitConverter.ToInt32
调用中仅使用4个字节,因此4个字节就足够了。4 bytes raw data length
,然后是raw data
itselft,然后是some bytes of checksum
(这样可以验证您收到的数据是否已正确传输)。