问题
I'm trying to create a TCP Server & Client that will have a persistent connection so that the server and client at any point in time can notify each other of certain 'events'(so push instead of poll).
I almost have everything working, clients can connect, connection is kept open and client and server can both write & read from the tcp stream.
The problem is with the read, I've defined the message boundary by first sending 8 bytes that contain the length of the message. Once I've received it, the messages of lenght x is read and an event is raised.
This all works fine but once the message has been read I want the "await stream.ReadAsync" to wait for new incoming data, but it keeps looping(and returns 0 data) instead of waiting causing 100% cpu usage.
Is there a way to say 'Reset' to the stream so that it begins to wait again like it did originally.
This is the code for my tcpclient(used for both sending and receiving), you can skip to the RunListener method, I don't think the rest matters.
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);
}
}
}
回答1:
The way you read 4 or 8 bytes is wrong. You need to loop until you actually got them. You might get 1.
You are assuming here and in other places that you will read the amount that you wanted. You will read at least one byte, or zero if the connection was shut down by the remote side.
Probably, you should use BinaryReader
to abstract away the looping.
Also, you need to clean up resources. Why aren't you wrapping them in using
? All the Close
calls are unsafe and not needed.
Also I don't see why exceptions for control flow would be necessary here. Refactor that away.
回答2:
Just 2 thoughts that hopefully help improving your code but are no answer to your initial question:
- You are sending 8 bytes indicating the following payload length but use only 4 of them in the following
BitConverter.ToInt32
call, so 4 bytes would be enough. - What would happen if the transmission is cut of from the other side? In my opinion you have no way to determine that the data you have receied is not valid. Maybe building something like a small low level protocol would help out e.g.
4 bytes raw data length
, followed by theraw data
itselft, followed bysome bytes of checksum
(which would allow to verify if the data you have received has been correctly transmitted or not).
来源:https://stackoverflow.com/questions/34256680/c-sharp-tcpclient-reading-multiple-messages-over-persistent-connection