C# 侦听器线程上CPU使用率高,睡眠未命中断开连接
下面是我的连接处理程序。这更多是为了个人实验,而不是生产代码 如果我不添加线程。在while循环中的任意位置睡眠,它就会开始消耗CPU。。相反,如果我真的睡觉来缓解无休止的垃圾邮件,我就会错过断开连接的机会。。CPU的增长与运行的客户机/线程的数量成正比,因此导致高使用率的不是侦听器本身,而是下面发布的实际客户机线程。。有人知道如何解决这个问题吗 我避免使用基于await的解决方案,因为我对async/await不够熟悉,而且线程方法对于这个相当小的项目来说工作得很好 我只是简单地四处搜索,寻找解决方案,没有注意到这个特定的问题,也没有注意到除了指导人们阅读async/Wait文章之外提供了其他解决方案,如果我错过了一个适用的答案,那么很抱歉C# 侦听器线程上CPU使用率高,睡眠未命中断开连接,c#,multithreading,winforms,tcplistener,tcpserver,C#,Multithreading,Winforms,Tcplistener,Tcpserver,下面是我的连接处理程序。这更多是为了个人实验,而不是生产代码 如果我不添加线程。在while循环中的任意位置睡眠,它就会开始消耗CPU。。相反,如果我真的睡觉来缓解无休止的垃圾邮件,我就会错过断开连接的机会。。CPU的增长与运行的客户机/线程的数量成正比,因此导致高使用率的不是侦听器本身,而是下面发布的实际客户机线程。。有人知道如何解决这个问题吗 我避免使用基于await的解决方案,因为我对async/await不够熟悉,而且线程方法对于这个相当小的项目来说工作得很好 我只是简单地四处搜索,寻找
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);
}
}
通过套接字进行通信的正确方式是: 不停地读。这些读取将被阻止,直到数据进入或套接字正常断开连接,通过读取0字节完成检测。 定期写。 正确的线程方法要求每个连接有两个线程。我不认为它比异步方法简单
另外,如果你的代码使用了Connected,那么它就有一个bug。正确的解决方案永远不需要使用连接。通过套接字进行通信的正确方式是: 不停地读。这些读取将被阻止,直到数据进入或套接字正常断开连接,通过读取0字节完成检测。 定期写。 正确的线程方法要求每个连接有两个线程。我不认为它比异步方法简单
另外,如果你的代码使用了Connected,那么它就有一个bug。正确的解决方案永远不需要使用连接。我遇到了与您相同的问题,但我发现解决此问题的最佳方法是: 不使用休眠和线程阻塞套接字 升级:如果您在服务器中使用线程和休眠,则通过每个连接接收和应答每条消息的性能将很低 如果你想要一个高性能的应用程序,你不能为你接受的每个连接使用休眠或创建线程。最好的方法是使用NetworkStream提供的异步方法,使用BeginRead和EndRead,例如:
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.");
}
升级我使用EndRead来确保请求已经到达
通过另一种方式,您可以使用BeginWrite和EndWrite来了解套接字何时完成写入以关闭连接
通过这种方式,您将以持续的方式尽快连接。在我的例子中,我将CPU使用率从30%降至0%,每小时的请求量为15K。我遇到了与您相同的问题,但我发现解决此问题的最佳方法是: 不使用休眠和线程阻塞套接字 升级:如果您在服务器中使用线程和休眠,则通过每个连接接收和应答每条消息的性能将很低 如果你想要一个高性能的应用程序,你不能为你接受的每个连接使用休眠或创建线程。最好的方法是使用NetworkStream提供的异步方法,使用BeginRead和EndRead,例如:
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.");
}
升级我使用EndRead来确保请求已经到达
通过另一种方式,您可以使用BeginWrite和EndWrite来了解套接字何时完成写入以关闭连接
通过这种方式,您将以持续的方式尽快连接。在我的情况下,我将CPU使用率从30%降低到0%,每小时安装15K的请求。你应该考虑使用异步/等待变量。你的意思是你错过了断开连接?你是说客户端断开连接后仍然连接,而你卡在了第一个while循环中?@TyCobb是的-我所说的没有断开连接的意思是远程连接■DSC和drop,然后ParseMsgr看到并优雅地关闭连接。。但是如果上面的while线程处于睡眠状态,它就永远不会得到消息。此外,\u client.Connected即使在远程客户端断开后也不会切换为false。我花了一天/晚上的大部分时间试图找到一个等待/连续的异步服务器,它不是典型的连接、接收、回显示例,断开连接,因此仍然存在添加缺少传入数据的while循环+sleep的问题。当我把它清理干净,它值得在这里保存时,我会回答我自己的问题。感谢大家抽出时间来回应人们,非常感谢反馈。你应该考虑使用异步/等待变量。你的意思是你错过了断开连接?您是说客户端断开连接后仍处于连接状态吗
cts和你被困在第一个while循环中?@TyCobb是的-我所说的错过断开连接的意思是远程连接■DSC和drop,然后ParseMsgr看到并优雅地关闭连接。。但是如果上面的while线程处于睡眠状态,它就永远不会得到消息。此外,\u client.Connected即使在远程客户端断开后也不会切换为false。我花了一天/晚上的大部分时间试图找到一个等待/连续的异步服务器,它不是典型的连接、接收、回显示例,断开连接,因此仍然存在添加缺少传入数据的while循环+sleep的问题。当我把它清理干净,它值得在这里保存时,我会回答我自己的问题。感谢各位花时间回复,非常感谢您的反馈。感谢您花时间回复,甚至简短地浏览您的文章,你的回答更多的是你错了,这里没有一个例子或资源来说明通过线程来做事情的正确方法。@MisterNad:没有使用线程方法的例子或资源,因为它本质上是不可伸缩的。正如我在博客系列文章中所提到的,最好的整体资源是Stevens的TCP/IP插图。感谢您花时间回复,甚至简单地浏览您的文章,你的回答更多的是你错了,这里没有一个例子或资源来说明通过线程来做事情的正确方法。@MisterNad:没有使用线程方法的例子或资源,因为它本质上是不可伸缩的。正如我在博客系列文章中提到的,最好的总体资源是Stevens的TCP/IP插图。在本示例中,我不使用EndRead,因为我不需要-如何知道读取了多少字节或是否存在异常?@acelent my protocol definition将每个请求和响应的大小设置为612字节。当您使用套接字并且您是协议的所有者时,最好使用具有要接收的数据量的头来构建协议,或者在本例中,设置要发送和接收的唯一数据量,我不是该协议的所有者。如果您的头具有数据的大小,则可以调用两倍于起始数据量的头。首先获取标头:startReceiveAsync它正在调用nst.BeginReadrequestBuffer,0,标头大小,this.headerReceived,nst;,其次,在方法headerReceived中获取数据量并请求剩余字节nst.BeginReadrequestBuffer,0,DataSize,this.requestReceived,nst;其中datasizes是您从标题中获得的数据量。我不是在批评您的协议。如果没有EndRead,您就无法知道读取是否成功,如果成功,则无法知道它是否实际读取了请求的字节数。这是一厢情愿的想法,相当于同步try{nst.ReadrequestBuffer,0,BUFFER_SIZE;}catch{}。@acelent我同意你使用EndRead,而不是我推荐的。在我的情况下,在关闭连接之前,我会收到一个请求并发送一个响应。我做了很多测试来决定是否允许或删除EndRead,根据我在实验室和生产中的结果,我决定删除EndRead,因为它足以接收请求的字节数。但正如您所评论的,对于这个答案,最好让EndRead来说明如何使用它。在这个示例中,我不使用EndRead,因为我不需要-您如何知道读取了多少字节或是否存在异常?@acelent My protocol definition将每个请求和响应的大小设置为612字节。当您使用套接字并且您是协议的所有者时,最好使用具有要接收的数据量的头来构建协议,或者在本例中,设置要发送和接收的唯一数据量,我不是该协议的所有者。如果您的头具有数据的大小,则可以调用两倍于起始数据量的头。首先获取标头:startReceiveAsync它正在调用nst.BeginReadrequestBuffer,0,标头大小,this.headerReceived,nst;,其次,在方法headerReceived中获取数据量并请求剩余字节nst.BeginReadrequestBuffer,0,DataSize,this.requestReceived,nst;其中datasizes是您从标题中获得的数据量。我不是在批评您的协议。如果没有EndRead,您就无法知道读取是否成功,如果成功,则无法知道它是否实际读取了请求的字节数。这是一厢情愿的想法,相当于同步try{nst.ReadrequestBuffer,0,BUFFER_SIZE;}catch{}。@acelent我同意你使用EndRead,而不是我推荐的。在我的情况下,在关闭连接之前,我会收到一个请求并发送一个响应。我做了很多测试来决定是否允许或删除EndRead,根据我在实验室和生产中的结果,我决定删除EndRead,因为它足以接收请求的字节数。但正如您所评论的,对于这个答案,最好让EndRead来展示如何使用它。