C# 侦听器线程上CPU使用率高,睡眠未命中断开连接

C# 侦听器线程上CPU使用率高,睡眠未命中断开连接,c#,multithreading,winforms,tcplistener,tcpserver,C#,Multithreading,Winforms,Tcplistener,Tcpserver,下面是我的连接处理程序。这更多是为了个人实验,而不是生产代码 如果我不添加线程。在while循环中的任意位置睡眠,它就会开始消耗CPU。。相反,如果我真的睡觉来缓解无休止的垃圾邮件,我就会错过断开连接的机会。。CPU的增长与运行的客户机/线程的数量成正比,因此导致高使用率的不是侦听器本身,而是下面发布的实际客户机线程。。有人知道如何解决这个问题吗 我避免使用基于await的解决方案,因为我对async/await不够熟悉,而且线程方法对于这个相当小的项目来说工作得很好 我只是简单地四处搜索,寻找

下面是我的连接处理程序。这更多是为了个人实验,而不是生产代码

如果我不添加线程。在while循环中的任意位置睡眠,它就会开始消耗CPU。。相反,如果我真的睡觉来缓解无休止的垃圾邮件,我就会错过断开连接的机会。。CPU的增长与运行的客户机/线程的数量成正比,因此导致高使用率的不是侦听器本身,而是下面发布的实际客户机线程。。有人知道如何解决这个问题吗

我避免使用基于await的解决方案,因为我对async/await不够熟悉,而且线程方法对于这个相当小的项目来说工作得很好

我只是简单地四处搜索,寻找解决方案,没有注意到这个特定的问题,也没有注意到除了指导人们阅读async/Wait文章之外提供了其他解决方案,如果我错过了一个适用的答案,那么很抱歉

        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来展示如何使用它。