我后来加大了client的连接/断开的次数(500,1000),Server端的连接被释放了。

这说明:

  1. 此代码是 可以正常工作的。

  2.TcpListener/TcpListener的async的使用上,编译器生成的代码并没有在socket关闭的时候显式调用Disopose(),Dispose()在GC的时候被调用的,个人猜测可能是Pooling,也可能是生成的代码不够漂亮。

--------------------------------------------

最近想写个socket通信的小程序,我相信大凡用过async/await特性的人再也不会去写APM、EAP之类的代码,tcp层的异步通信网上也有很多例子,MSDN上的例子如下: https://msdn.microsoft.com/en-us/magazine/dn605876.aspx , 我参考这个例子写了个Demo,结果发现有内存泄漏,细想应该是某些地方用的不对了,如果有哪位大神看出来,请帮忙指正:

 

Server端代码:

我们可以看到Tcp上使用TcpListener,并且异步的实现实在NetworkStream上。

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 AsyncTcpListenerServer
{
    using System.IO;
    class Program
    {
        static void Log(string msg)
        {
            Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} {msg}");
        }

        static void Main(string[] args)
        {
            ProcessConnectClient();
            Log("Listened success.");
            Console.ReadLine();
        }

        static async void ProcessConnectClient()
        {
            IPAddress ipAddress = IPAddress.Loopback;
            TcpListener listener = new TcpListener(ipAddress, 8099);
            listener.Start();

            Log("Begin start listen...");

            while (true)
            {
                try
                {
                    TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
                    Log($"listened: {client.Client.RemoteEndPoint}");
                    ProcessReceiveData(client);

                }
                catch (Exception ex)
                {
                    Console.WriteLine("error:" + ex.ToString());
                }
            }
        }

        static async void ProcessReceiveData(TcpClient client)
        {
            //client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            IPEndPoint endPoint = (IPEndPoint)client.Client.RemoteEndPoint;
            string info = client.Client.RemoteEndPoint.ToString();
            try
            {
                #region 
                //using (NetworkStream networkStream = client.GetStream())
                //{
                //    byte[] buffer = new byte[client.ReceiveBufferSize];
                //    while (true)
                //    {
                //        int byteRead = await networkStream.ReadAsync(buffer, 0, client.ReceiveBufferSize).ConfigureAwait(false);
                //        if (byteRead <= 0)
                //        {
                //            break;
                //        }
                //        Log($" IP:{endPoint.Address} Port:{endPoint.Port} received {byteRead} bytes");
                //    }
                //} 
                #endregion

                #region MyRegion
                using (NetworkStream networkStream = client.GetStream())
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    byte[] buffer = new byte[client.ReceiveBufferSize];
                    while (true)
                    {
                        int byteRead = await networkStream.ReadAsync(buffer, 0, client.ReceiveBufferSize).ConfigureAwait(false);
                        if (byteRead <= 0)
                        {
                            break;
                        }
                        await memoryStream.WriteAsync(buffer, 0, byteRead).ConfigureAwait(false);
                        byte[] bytes = memoryStream.ToArray();
                        memoryStream.Seek(0, SeekOrigin.Begin);

                        string str = Encoding.ASCII.GetString(bytes);

                        Log($" IP:{endPoint.Address} Port:{endPoint.Port} {str}");
                    }
                }
                #endregion

                #region MyRegion
                //using (NetworkStream networkStream = client.GetStream())
                //using (StreamReader reader = new StreamReader(networkStream))
                //{
                //    while (true)
                //    {
                //        string msg = await reader.ReadLineAsync().ConfigureAwait(false);
                //        if (msg == null)
                //        {
                //            break;
                //        }

                //        Log($" IP:{endPoint.Address} Port:{endPoint.Port} {msg}");
                //    }
                //}
                #endregion

            }
            catch (Exception ex)
            {
                Log(ex.ToString());
            }
            finally
            {
                Console.WriteLine($"client closed!");
                client.Close();
            }
        }
    }
}

 

 

Client端代码:

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 AsyncTcpClientClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Task t = Sending();
            t.Wait();
            Console.WriteLine("OK client");
        }

        static async Task Sending()
        {
            TcpClient client = new TcpClient();
            try
            {
                await client.ConnectAsync(IPAddress.Loopback, 8099);
                using (NetworkStream stream = client.GetStream())
                {
                    int i = 0;
                    while (stream.CanWrite)
                    {
                        i++;
                        if (i>5)
                        {
                            break;
                        }
                        string msg = System.Diagnostics.Process.GetCurrentProcess().ProcessName;
                        byte[] ba = Encoding.Default.GetBytes(msg);
                        await stream.WriteAsync(ba, 0, ba.Length);
                        //System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
                        //writer.AutoFlush = true;
                        //await writer.WriteLineAsync(msg);

                        Console.WriteLine($"Send...{msg}");
                        Thread.Sleep(1000);                       
                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                
            }
            finally
            {
                if (client.Connected)
                {
                    client.Close();
                }
            }
        }
    }
}

 

写法是特别简单,但是。。。

开启Server端后,运行Client3次端,Server端的显示:

abcdefg

 

可以看到通信成功,此时抓取Dump,分析结果如下:

Microsoft (R) Windows Debugger Version 10.0.10240.9 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.


Loading Dump File [C:\Users\StevenChen\AppData\Local\Temp\AsyncTcpListenerServer (8).DMP]
User Mini Dump File with Full Memory: Only application data is available

WARNING: Minidump contains unknown stream type 0x15
WARNING: Minidump contains unknown stream type 0x16
Symbol search path is: *** Invalid ***
****************************************************************************
* Symbol loading may be unreliable without a symbol search path.           *
* Use .symfix to have the debugger choose a symbol path.                   *
* After setting your symbol path, use .reload to refresh symbol locations. *
****************************************************************************
Executable search path is:
Windows 10 Version 10240 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Built by: 10.0.10240.16384 (th1.150709-1700)
Machine Name:
Debug session time: Tue Dec 15 17:29:05.000 2015 (UTC + 8:00)
System Uptime: 1 days 8:03:25.088
Process Uptime: 0 days 0:00:49.000
.....................................
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ntdll.dll -

************* Symbol Loading Error Summary **************
Module name            Error
ntdll                  The system cannot find the file specified

You can troubleshoot most symbol related issues by turning on symbol loading diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for KERNELBASE.dll -
ntdll!ZwReadFile+0xa:
00007ff9`313135da c3              ret
0:000> .symfix d:\symbols
0:000> .loadby sos clr
0:000> !fq
No .natvis files found at C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers.
c0000005 Exception in C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos.fq debugger extension.
      PC: 00007ff8`e01b2085  VA: 00000000`00000000  R/W: 0  Parameter: 00000000`00000000
0:000> !fq
SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 123 finalizable objects (0000001d743340e0->0000001d743344b8)
generation 1 has 0 finalizable objects (0000001d743340e0->0000001d743340e0)
generation 2 has 0 finalizable objects (0000001d743340e0->0000001d743340e0)
Ready for finalization 0 objects (0000001d743344b8->0000001d743344b8)
Statistics for all finalizable objects (including all objects ready for finalization):
              MT    Count    TotalSize Class Name
00007ff91a6f2ec0        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
00007ff91a6f2e30        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00007ff91a6eab30        1           32 Microsoft.Win32.SafeHandles.SafeWaitHandle
00007ff91a6d1028        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle
00007ff91a6c1190        1           32 System.Threading.Gen2GcCallback
00007ff91a685878        1           40 System.Threading.RegisteredWaitHandleSafe
00007ff9134adbe8        3          120 System.Net.Sockets.TcpClient
00007ff91a6f0fe8        4          128 Microsoft.Win32.SafeHandles.SafeFileHandle
00007ff91a6d0fc0        2          128 System.Threading.ReaderWriterLock
00007ff91a6d3ff0        2          176 System.Diagnostics.Tracing.EventSource+OverideEventProvider
00007ff91a6ac5e8        1          184 System.Threading.PinnableBufferCacheEventSource
00007ff91a690ab8        1          184 System.Threading.Tasks.TplEtwProvider
00007ff9134ddac8        6          192 System.Net.SafeNativeOverlapped
00007ff9134d5790        3          192 System.Net.Sockets.NetworkStream
00007ff91a6de1e8        2          208 System.IO.FileStream
00007ff9134dda38        6          240 System.Net.SafeCloseSocket
00007ff9134dd820        8          256 System.Net.SafeCloseSocket+InnerSafeCloseSocket
00007ff91a6eee60        3          288 System.Threading.Thread
00007ff91a6d00f8        9          288 Microsoft.Win32.SafeHandles.SafeRegistryHandle
00007ff9134d6aa8        6          288 System.Net.Sockets.OverlappedCache
00007ff91a6e9a20       14          336 System.WeakReference
00007ff9134b60f0        3          528 System.Net.Sockets.AcceptOverlappedAsyncResult
00007ff91a6d3148       21          672 Microsoft.Win32.SafeHandles.SafeAccessTokenHandle
00007ff9134dc7c0        5          680 System.Net.Sockets.Socket
00007ff9134d7368       18         3312 System.Net.Sockets.OverlappedAsyncResult
Total 123 objects
0:000> !dumpheap -mt 00007ff9134dc7c0
         Address               MT     Size
0000001d00034540 00007ff9134dc7c0      136    
0000001d00040110 00007ff9134dc7c0      136    
0000001d00052de0 00007ff9134dc7c0      136    
0000001d000680d8 00007ff9134dc7c0      136    
0000001d0007a780 00007ff9134dc7c0      136    

Statistics:
              MT    Count    TotalSize Class Name
00007ff9134dc7c0        5          680 System.Net.Sockets.Socket
Total 5 objects
0:000> !gcroot 0000001d00034540
HandleTable:
    0000001d71741350 (strong handle)
    -> 0000001d0003be08 System.Threading._ThreadPoolWaitOrTimerCallback
    -> 0000001d00034540 System.Net.Sockets.Socket

    0000001d71741be0 (async pinned handle)
    -> 0000001d00052860 System.Threading.OverlappedData
    -> 0000001d0007a6d0 System.Net.Sockets.AcceptOverlappedAsyncResult
    -> 0000001d00034540 System.Net.Sockets.Socket

Found 2 unique roots (run '!GCRoot -all' to see all roots).
0:000> !gcroot 0000001d00040110
Finalizer Queue:
    0000001d00040110
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00040328
    -> 0000001d00040328 System.Net.Sockets.TcpClient
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00040c88
    -> 0000001d00040c88 System.Net.Sockets.NetworkStream
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00050f20
    -> 0000001d00050f20 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00054ba0
    -> 0000001d00054ba0 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00054ff8
    -> 0000001d00054ff8 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00055450
    -> 0000001d00055450 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d000558a8
    -> 0000001d000558a8 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

    0000001d00055d00
    -> 0000001d00055d00 System.Net.Sockets.OverlappedAsyncResult
    -> 0000001d00040110 System.Net.Sockets.Socket

Warning: These roots are from finalizable objects that are not yet ready for finalization.
This is to handle the case where objects re-register themselves for finalization.
These roots may be false positives.
Found 9 unique roots (run '!GCRoot -all' to see all roots).
0:000> !dumpheap -mt 00007ff9134d7368  
         Address               MT     Size
0000001d00050f20 00007ff9134d7368      184    
0000001d00054ba0 00007ff9134d7368      184    
0000001d00054ff8 00007ff9134d7368      184    
0000001d00055450 00007ff9134d7368      184    
0000001d000558a8 00007ff9134d7368      184    
0000001d00055d00 00007ff9134d7368      184    
0000001d00067c60 00007ff9134d7368      184    
0000001d00068a00 00007ff9134d7368      184    
0000001d00068e58 00007ff9134d7368      184    
0000001d000692b0 00007ff9134d7368      184    
0000001d00069708 00007ff9134d7368      184    
0000001d00069b60 00007ff9134d7368      184    
0000001d0007a308 00007ff9134d7368      184    
0000001d0007aea0 00007ff9134d7368      184    
0000001d0007b2f8 00007ff9134d7368      184    
0000001d0007b750 00007ff9134d7368      184    
0000001d0007bba8 00007ff9134d7368      184    
0000001d0007c000 00007ff9134d7368      184    

Statistics:
              MT    Count    TotalSize Class Name
00007ff9134d7368       18         3312 System.Net.Sockets.OverlappedAsyncResult
Total 18 objects
0:000> !gcroot 0000001d00054ff8
Finalizer Queue:
    0000001d00054ff8
    -> 0000001d00054ff8 System.Net.Sockets.OverlappedAsyncResult

Warning: These roots are from finalizable objects that are not yet ready for finalization.
This is to handle the case where objects re-register themselves for finalization.
These roots may be false positives.
Found 1 unique roots (run '!GCRoot -all' to see all roots).

 

我们能够比较明显的看出有些对象没有释放,这些对象的root都指向System.Net.Sockets.OverlappedAsyncResult,但是很奇怪System.Net.Sockets.OverlappedAsyncResult本身也是一个根。