若汝棋茗 2021-11-29 17:50 采纳率: 0%
浏览 138
已结题

C# 在Winform和WPF框架下使用IOCP通信模型时发生疑似内存泄漏现象

问题

我是一个致力于C#网络通信的搬砖人,但是今天被一个朋友提问,关于,他的软件在使用我的RRQMSocket框架时,运行24小时,内存从19变成30的问题。起初我没在意,因为即使是30Mb,也比较小,完全可以不用管。但是这使我想起了之前压力测试时发生的一个情况。

之前在压测时,100个客户端,每个客户端每秒钟发送近4000条1kb的数据。在测试期间(一分钟),内存从29变成205Mb,而且还在不断上升。

但是诡异的是,我把该代码放在自己电脑上运行时,却没有任何问题。

最后定性为微软SDK的bug。

但是今天又有了受害者。所以我得站出来,拿出有力证据。不然还以为我在胡说。

img

相关代码

首先,代码分3个类型测试。分别为仿RRQM的IOCP架构常规IOCP架构多线程阻塞架构。对应代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace SocketLib
{
    public class RRQMIOCPMode
    {
        public void Start(int port)
        {
            //用列表存下已连接的客户端,模拟RRQM的SocketClients集合。
            List<SimulationSocketClient> simulations = new List<SimulationSocketClient>();
            Task.Run(() =>
            {
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                socket.Bind(new IPEndPoint(IPAddress.Any, port));
                socket.Listen(10);
                while (true)
                {
                    Socket newSocket = socket.Accept();//同步接收,这个无所谓

                    SimulationSocketClient simulationSocketClient = new SimulationSocketClient() { Socket = newSocket };
                    simulations.Add(simulationSocketClient);
                    simulationSocketClient.BeginReceive();
                }
            });
        }
    }

    class SimulationSocketClient
    {
        public Socket Socket { get; set; }

        public void BeginReceive()
        {
            eventArgs = new SocketAsyncEventArgs();
            eventArgs.Completed += EventArgs_Completed;

            byte[] buffer = new byte[1024 * 64];
            eventArgs.SetBuffer(buffer, 0, buffer.Length);

            if (!Socket.ReceiveAsync(eventArgs))
            {
                ProcessReceived(eventArgs);
            }
        }
        SocketAsyncEventArgs eventArgs;


        private void EventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                ProcessReceived(e);
            }
        }
        private void ProcessReceived(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                //在这里处理数据,此处不做任何处理,直接进行下次接收。
                if (!Socket.ReceiveAsync(e))
                {
                    ProcessReceived(e);
                }
            }
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace SocketLib
{
    public class NormalIOCPMode
    {
        public void Start(int port)
        {
            Task.Run(() =>
            {
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                socket.Bind(new IPEndPoint(IPAddress.Any, port));
                socket.Listen(10);
                while (true)
                {
                    Socket newSocket = socket.Accept();//同步接收,这个无所谓
                    SetSocket(newSocket);
                }
            });
        }

        public void SetSocket(Socket socket)
        {
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();
            eventArgs.Completed += EventArgs_Completed;
            eventArgs.UserToken = socket;

            byte[] buffer = new byte[1024 * 64];
            eventArgs.SetBuffer(buffer, 0, buffer.Length);

            if (!socket.ReceiveAsync(eventArgs))
            {
                ProcessReceived(eventArgs);
            }
        }

        private void EventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                ProcessReceived(e);
            }
        }
        private void ProcessReceived(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                //在这里处理数据,此处不做任何处理,直接进行下次接收。
                if (!((Socket)e.UserToken).ReceiveAsync(e))
                {
                    ProcessReceived(e);
                }
            }
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SocketLib
{
    public class MultithreadingMode
    {
        public void Start(int port)
        {
            Task.Run(() =>
            {
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                socket.Bind(new IPEndPoint(IPAddress.Any, port));
                socket.Listen(10);
                while (true)
                {
                    Socket newSocket = socket.Accept();//同步接收,这个无所谓
                    SetSocket(newSocket);
                }
            });
        }

        public void SetSocket(Socket socket)
        {
            Thread thread = new Thread(()=> 
            {
                byte[] buffer = new byte[1024*64];
                while (true)
                {
                    int r = socket.Receive(buffer);
                    //在这里处理数据,此处不做任何处理,直接进行下次接收。
                }
            });
            thread.Name = socket.RemoteEndPoint.ToString();
            thread.IsBackground = true;
            thread.Start();
        }
    }
}


测试方法

方法有两个,既然指出了Winform的罪过,那就先拿Winform开刀。

建立winform项目,选择net461架构,不然说我欺负你。

界面

img

代码

using SocketLib;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            switch (this.comboBox1.SelectedIndex)
            {
                case 0:
                    {
                        RRQMIOCPMode mode = new RRQMIOCPMode();
                        mode.Start(7790);
                        break;
                    }
                case 1:
                    {
                        NormalIOCPMode mode = new NormalIOCPMode();
                        mode.Start(7790);
                        break;
                    }
                case 2:
                    {
                        MultithreadingMode mode = new MultithreadingMode();
                        mode.Start(7790);
                        break;
                    }
                default:
                    break;
            }

            MessageBox.Show("启动成功");
        }

       
    }

}


第二种,就用控制台

using SocketLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("0.仿RRQM的IOCP架构");
            Console.WriteLine("1.常规IOCP架构");
            Console.WriteLine("2.多线程阻塞架构");
            switch (Console.ReadLine())
            {
                case "0":
                    {
                        RRQMIOCPMode mode = new RRQMIOCPMode();
                        mode.Start(7790);
                        break;
                    }
                case "1":
                    {
                        NormalIOCPMode mode = new NormalIOCPMode();
                        mode.Start(7790);
                        break;
                    }
                case "2":
                    {
                        MultithreadingMode mode = new MultithreadingMode();
                        mode.Start(7790);
                        break;
                    }
                default:
                    break;
            }
            Console.WriteLine("启动成功");
            Console.ReadKey();
        }
    }
}

测试客户端,使用的是RRQMBox的压力测试,这个不用关心,因为无论如何,也与客户端没关系。不过想自己测试的话,可以去https://gitee.com/RRQM_OS/RRQMBox下载。

Winform测试结果

在仿RRQM的IOCP模式下,和正常IOCP模式下,测试1分钟,内存从29升到219Mb。

img

在多线程模式下,测试1分钟,内存从29升到50Mb,就不再升,且没有再升的迹象。

img

控制台测试结果

控制台下,一切正常。

img

####相关资源

测试代码

最后再说一下,如果你在自己电脑测试没有问题的话,那就没有问题,因为这就是很诡异。__

  • 写回答

0条回答 默认 最新

    报告相同问题?

    问题事件

    • 系统已结题 12月7日
    • 创建了问题 11月29日

    悬赏问题

    • ¥15 yolov5目标检测并显示目标出现的时间或视频帧
    • ¥15 电视版的优酷可以设置电影连续播放吗?
    • ¥50 复现论文;matlab代码编写
    • ¥30 echarts 3d地图怎么实现一进来页面散点数据和卡片一起轮播
    • ¥15 数字图像的降噪滤波增强
    • ¥15 心碎了,为啥我的神经网络训练的时候第二个批次反向传播会报错呀,第一个批次都没有问题
    • ¥15 MSR2680-XS路由器频繁卡顿问题
    • ¥15 VB6可以成功读取的文件,用C#读不了
    • ¥15 如何使用micpyhon解析Modbus RTU返回指定站号的湿度值,并确保正确?
    • ¥15 C++ 句柄后台鼠标拖动如何实现