C# WebSocket Fleck 内存泄漏

最近在维护公司旧项目,内存泄漏严重,找了行业内大佬帮忙分析Dump文件(windbg我不擅长),大佬指出问题在于Fleck,这里记录一下。

整理一下问题:

1. 大佬指出 System.Threading.Tasks.ContinuationTaskFromTask 和 System.ObjectDisposedException 有71完个对象。

2. System.ObjectDisposedException 是由调用 System.Net.Sockets.NetworkStream.EndWrite(System.IAsyncResult) 方法时无法访问已释放的对象造成的。

3. System.Threading.Tasks.ContinuationTaskFromTask 也就是 Task.ContinueWith(t => { }) 过多造成的,这种写法比较传统,导致托管堆大量的 task,引发碎片化。

4. 个人观察每次释放时 都会频繁调用 Fleck.IWebSocketConnection.OnClose 事件,触发事件的 OnClose 的参数 IWebSocketConnectionInfo.Id 完全相同。

问题解决:

1. 修改了Fleck的源码,修改了Fleck.SocketWrapper 类中使用了TaskFactory.FromAsync(begin, end).ContinueWith(t => { })的内容,但感觉问题没定位到。

修改了Accept(Action<ISocket>, Action<Exception>) 方法、Receive(byte[], Action<int>, Action<Exception>, int)方法 和 Send(byte[], Action, Action<Exception>) 方法,代码如下:

public void Accept(Action<ISocket> callback, Action<Exception> error)
{
    try
    {
        Action<IAsyncResult> end = (ar) =>
        {
            try
            {
                Socket client = _socket.EndAccept(ar);
                var sw = new SocketWrapper(client);
                callback(sw);
            }
            catch (Exception e)
            {
                error(e);
            }
        };
        _socket.BeginAccept(new AsyncCallback(end), null);
    }
    catch (Exception e)
    {
        error(e);
    }
}

public void Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset)
{
    try
    {
        Action<IAsyncResult> end = (ar) =>
        {
            try
            {
                var num = _stream.EndRead(ar);
                callback(num);
            }
            catch (Exception ex)
            {
                error(ex);
            }
        };
        _stream.BeginRead(buffer, offset, buffer.Length, new AsyncCallback(end), null);
    }
    catch (Exception e)
    {
        error(e);
    }
}
public async Task Send(byte[] buffer, Action callback, Action<Exception> error)
{
    if (_tokenSource.IsCancellationRequested)
        return;

    try
    {
        if (!_stream.CanWrite)
        {
            return;
        }
        await _stream.WriteAsync(buffer, 0, buffer.Length, _tokenSource.Token).ConfigureAwait(false);
        await _stream.FlushAsync(_tokenSource.Token).ConfigureAwait(false); // 这个其实没用,源码里面是空的
        callback();
    }
    catch (Exception e)
    {
        error(e);
    }
}

2. 研究了 WebSocket客户端代码,发现客户端多次执行了 window.ws = new window.WebSocket('ws://localhost:8181/'); 每次都赋予 window.ws 值前都没有关闭,入截图所示

修改为:创建window.WebSocket("") 和 关闭页面前都执行 window.ws.close();

3.  Fleck.IWebSocketConnection.OnClose 事件 触发时 循环所有 IWebSocketConnection 客户端集合,移除 IsAvailable==false的数据;服务器向客户端推送数据前判断 IsAvailable==true;

 

 

上面的修改后 两周过去了,但问题还是没有解决,中间还是各种尝试。这里做下总结

问题是:TCP协议需要客户端确认接收,WebSocket频繁发送消息到客户端时,可能某些原因客户端卡顿了不能及时确认回复,导致Task一直停留在EndWrite()的回调中直到连接关闭。

 期间进行了各种尝试:

1. 打印 当前客户端连接数量,确实监控到一下额外的客户端。

2. 修改 Fleck.SocketWrapper.cs 类,构造方法中增加 socket.ReceiveTimeout = 1000 和 socket.SendTimeout = 1000 的设置。也就是1秒超时。

3. 当内存增大时,手动关闭客户端连接。过段时间内存也确实降了下来。

4. 也看了 System.Net.Sockets.Socket 和 System.Net.Sockets.NetworkStream 的源码,但没有太大的入手的。

 

最后通过修改客户端代码解决的,修改了一些数组只Push 不 Shift 的问题。要怪只能怪写客户端的人太不责任了。

 

posted @ 2024-09-11 16:46  Karl_Albright  阅读(82)  评论(0编辑  收藏  举报