问题
My connection handler is below (this is more for personal experimentation than production code)
If I don't add a Thread.Sleep anywhere in the while loop, it starts sucking down CPU.. Conversely, if I do Sleep to alleviate the endless while-spam, I miss the disconnection.. The CPU goes up in direct proportion to the number of clients/threads running, so it's not the listener itself that's causing the high usage, it's the actual client thread posted below.. Anyone have any ideas on how to solve this?
(I'm avoiding await-based solutions as I'm not familiar enough with async/await and the threaded method is working fine for this rather small project)
I only briefly searched around SO looking for a solution and didn't notice any that were this specific problem or that provided a solution other than directing folks to async/await articles, so sorry if I did miss an applicable answer.
private void HandleConnection(CancellationToken ct) {
int recv = 0;
byte[] buf = new byte[4096];
Trace.WriteLine($"{_name} Connected");
if (_ns.CanWrite && _client.Connected) {
_ns.Write(Encoding.BigEndianUnicode.GetBytes("■WEL"), 0, Encoding.BigEndianUnicode.GetBytes("■WEL").Length);
try {
while (_client.Connected && !ct.IsCancellationRequested) {
while (!_ns.DataAvailable) { //first attempted solution
Thread.Sleep(100); // miss discon if i sleep here
}
if (ct.IsCancellationRequested) {
Trace.WriteLine($"{(string)this} thread aborting");
break;
}
buf = new byte[4096];
if (_client.Connected && _ns.DataAvailable) {
recv = _ns.Read(buf, 0, buf.Length);
} else {
recv = 0;
}
if (recv > 0) {
string r = Encoding.BigEndianUnicode.GetString(buf);
r = r.TrimEnd('\0');
if (String.IsNullOrEmpty(r) || String.IsNullOrWhiteSpace(r))
r = null; //need the !not version
else
if (ParseMsg(r))
break;
}
//Thread.Sleep(100); // also miss discon here too
}
} catch (IOException ioe) { }
Trace.WriteLine($"{_name} Disconnected");
if (OnDisconnected != null)
OnDisconnected(this);
}
}
回答1:
I had the same problema than you but I found that the best way to solve this problem is:
Not Blocking the Socket with sleeps and thread.
UPGRADE: If you use threads and sleeps into your server, it will suffer a low performance to receive and answer each message by each connection.
If you want an High Performance App you must not use sleeps or create thread for each connection that you accept. The best way is using the Asyncronous methods that NetworkStream provides, using BeginRead
and EndRead
, for example:
public void run()
{
server = new TcpListener(IPAddress.Any, port);
server.Start();
log.Info("Starting SocketServer on Port [" + port + "]");
while (keepRunning)
{
try
{
TcpClient socket = server.AcceptTcpClient();
if (keepRunning)
RequestManager.createRequestForEvalue(socket, idLayout);
}
catch (Exception ex)
{
log.Error(ex.Message);
log.Error(ex.StackTrace);
}
}
log.Info("Server Stoped.");
}
public static bool createRequestForEvalue(TcpClient socket, int idLayout)
{
Request req = null;
req = new Request(socket,idLayout);
registerRequest(req.ID,req); //Registra el Request, para su posterior uso.
// DO NOT CREATE THREADS FOR ATTEND A NEW CONNECTION!!!
//Task.Factory.StartNew(req.RunForIVR);
//ThreadPool.QueueUserWorkItem(req.RunForIVR);
req.startReceiveAsync(); //Recive data in asyncronus way.
return true;
}
public void startReceiveAsync()
{
try
{
log.Info("[" + id + "] Starting to read the Request.");
requestBuffer = new byte[BUFFER_SIZE];
NetworkStream nst = socket.GetStream();
nst.BeginRead(requestBuffer, 0,BUFFER_SIZE, this.requestReceived, nst);
}catch(Exception ex)
{
log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
RequestManager.removeRequest(id);
closeSocket();
}
}
public void requestReceived(IAsyncResult ar)
{
try
{
NetworkStream nst = socket.GetStream();
int bread = nst.EndRead(ar); //Block the socket until all the buffer has been available.
message = Encoding.UTF8.GetString(requestBuffer, 0, BUFFER_SIZE);
log.Info("[" + id + "] Request recived: [" + message +"]");
RunForIVR();
}
catch (Exception ex)
{
log.Error("[" + id + "] There was a problem to read the Request: " + ex.Message);
RequestManager.removeRequest(id);
closeSocket();
}
}
public void SendResponse(String Response)
{
StringBuilder sb = new StringBuilder();
sb.Append(Response);
sb.Append('\0', BUFFER_SIZE - Response.Length);
string message = sb.ToString();
log.Info("[" + id + "] ivrTrans CMD: [" + idCMD + "] RESPONSE: [" + Response + "]");
NetworkStream nst = socket.GetStream();
byte[] buffer = new byte[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; i++)
buffer[i] = (byte)message.ElementAt(i);
nst.BeginWrite(buffer, 0, BUFFER_SIZE, this.closeSocket, nst);
}
public void closeSocket(IAsyncResult ar = null)
{
try
{
if (ar != null) //Since 4.24
{
NetworkStream nst = socket.GetStream();
nst.EndWrite(ar);
}
socket.Close();
socket = null;
}catch(Exception ex)
{
log.Warn("[" + id + "] There was a problem to close the socket. Error: " + ex.Message + Environment.NewLine + ex.StackTrace);
}
log.Info("[" + id + "] Socket closed.");
}
Upgrade I use the
EndRead
to be sure that the request has been arrived at all.
By Other way, you can use BeginWrite
and EndWrite
to know when the socket has been finished of write to close the connection
In this way you are attended the connection in a continues way and as soon as possible. In my case i reduce the CPU usage from 30% to 0%, for an a mount of 15K request per hour.
回答2:
The proper way to communicate over a socket is:
- Continuously read. These reads will block until data comes in or until the socket is gracefully disconnected (detectable by a read completing with 0 bytes read).
- Periodically write. These writes are required to ensure the connection is still viable.
A proper threading approach requires two threads per connection. I'm not convinced that it's simpler than an asynchronous approach.
P.S. If your code uses Connected
, then it has a bug. Proper solutions never need to use Connected
.
来源:https://stackoverflow.com/questions/43327534/c-sharp-high-cpu-usage-on-listener-thread-sleeping-misses-disconnect