做CS的开发一直都是这样的方式:

server端用 C++编写,采用IOCP机制处理大量客户端连接、数据接收发送的问题

client端用 C++ 或C# 写,没什么特殊要求。

最近工作时间上比较宽裕,决定采用新的方式来处理服务端的工作: C# + SOCKET异步机制(.net里没有IOCP的直接支持)

目前正可行性分析阶段,第一步的工作:接收3W个SOCKET连接, 结果还是不错的,很快就建立起来了,速度也可以。

但是第二步测试,接收、发送数据时,就发生了点问题:

运行的SERVER程序在较短的时间内就占用了大量的内存!

我的测试环境:i3 +2G内存 + Win732位

客户端创建5000个连接,每间隔1秒种对所有的连接发送、接收一次数据。每次发送20bytes到server。

服务端与客户端不在同一台机器上

一般情况下,程序的启动内存占用为4.5M ,运行5分钟后,SERVER程序内存占用超过 100M,并且还在不停的快速增长

在一台服务器上测试(2W个连接),4个小时内,把8G内存全部用光(从任务管理器上看,使用了7.9G内存)

先看SERVER端的完整代码:(大家可以COPY到自己的IDE里直接编译)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Net.Sockets;
  6. namespace TestAsyncSendMem
  7. {
  8. class Program
  9. {
  10. static TcpListener m_lisnter;
  11. static AsyncCallback m_acb = new AsyncCallback(DoAcceptSocketCallback);
  12. static void Main(string[] args)
  13. {
  14. m_lisnter = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, 8001);
  15. m_lisnter.Start(5 * 1000);
  16. try
  17. {
  18. m_lisnter.BeginAcceptSocket(m_acb, null);
  19. }
  20. catch (Exception ex)
  21. {
  22. m_lisnter.Stop();
  23. m_lisnter = null;
  24. System.Diagnostics.Debug.WriteLine("BeginAcceptSocket err.Start fail!" + ex);
  25. return;
  26. }
  27. Console.WriteLine("Begin receiving connection... Press any key to quit.");
  28. Console.ReadKey();
  29. m_lisnter.Stop();
  30. }
  31. static void DoAcceptSocketCallback(IAsyncResult ar)
  32. {
  33. System.Net.Sockets.Socket s = null;
  34. try
  35. {
  36. s = m_lisnter.EndAcceptSocket(ar);
  37. }
  38. catch (Exception ex)
  39. {
  40. System.Diagnostics.Debug.WriteLine("End Accept socket err" + ex);
  41. s = null;
  42. }
  43. try
  44. {
  45. m_lisnter.BeginAcceptSocket(m_acb, null);
  46. }
  47. catch (Exception ex)
  48. {
  49. System.Diagnostics.Debug.WriteLine("after accept client socket,Re beginAcceptSocket fail." + ex);
  50. }
  51. if (s != null)
  52. {
  53. #region...
  54. CTcpClientSync c = new CTcpClientSync(s);
  55. Console.WriteLine(string.Format("accept client.{0}", c.Socket.RemoteEndPoint));
  56. if (c.BeginRcv() == true)
  57. {
  58. c.OnDisconnected += (CTcpClientSync client) =>
  59. {
  60. System.Diagnostics.Debug.WriteLine(string.Format("client {0} disconected", client.RemoteIP));
  61. };
  62. }
  63. else
  64. {
  65. c.Stop();
  66. System.Diagnostics.Debug.WriteLine(string.Format("accepted client {0} removed.cannot begin rcv", c.RemoteIP));
  67. }
  68. #endregion
  69. }
  70. }
  71. }
  72. public class CTcpClientSync
  73. {
  74. #region delegate
  75. public delegate void dlgtDisconnected(CTcpClientSync c);
  76. public event dlgtDisconnected OnDisconnected;
  77. #endregion
  78. #region prop
  79. Socket m_skt = null;
  80. public Socket Socket { get { return m_skt; } }
  81. string m_strRemoteIP;
  82. public string RemoteIP { get { return m_strRemoteIP; } }
  83. byte[] m_arybytBuf = new byte[1024];
  84. AsyncCallback m_acb = null;
  85. #endregion
  86. public CTcpClientSync(Socket skt)
  87. {
  88. m_acb = new AsyncCallback(DoBeginRcvData);
  89. m_skt = skt;
  90. try
  91. {
  92. m_strRemoteIP = skt.RemoteEndPoint.ToString();
  93. }
  94. catch (Exception ex)
  95. {
  96. System.Diagnostics.Debug.WriteLine("get remote end point exception."+ ex);
  97. }
  98. }
  99. public void Stop()
  100. {
  101. m_skt.Close();
  102. }
  103. #region Raise event
  104. void RaiseDisconnectedEvent()
  105. {
  106. dlgtDisconnected handler = OnDisconnected;
  107. if (handler != null)
  108. {
  109. try
  110. {
  111. handler(this);
  112. }
  113. catch (Exception ex)
  114. {
  115. System.Diagnostics.Debug.WriteLine("Raise disconn event exception." + ex.Message);
  116. }
  117. }
  118. }
  119. #endregion
  120. public bool BeginRcv()
  121. {
  122. try
  123. {
  124. m_skt.BeginReceive(m_arybytBuf, 0, m_arybytBuf.Length, SocketFlags.None, m_acb, null);
  125. }
  126. catch (Exception ex)
  127. {
  128. System.Diagnostics.Debug.WriteLine("BeginRcv exception." + ex);
  129. return false;
  130. }
  131. return true;
  132. }
  133. void DoBeginRcvData(IAsyncResult ar)
  134. {
  135. int iReaded = 0;
  136. try
  137. {
  138. iReaded = m_skt.EndReceive(ar);
  139. }
  140. catch (Exception ex)
  141. {
  142. System.Diagnostics.Debug.WriteLine("BeginRcv exception." + ex);
  143. Stop();
  144. RaiseDisconnectedEvent();
  145. return;
  146. }
  147. if (iReaded > 0)
  148. {
  149. //收到后发送回一个数据包
  150. SendAsync(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 });
  151. if (BeginRcv() == false)
  152. {
  153. Stop();
  154. RaiseDisconnectedEvent();
  155. }
  156. }
  157. else
  158. {
  159. Stop();
  160. RaiseDisconnectedEvent();
  161. }
  162. }
  163. public bool SendAsync(byte[] bytsCmd)
  164. {
  165. SocketAsyncEventArgs e = new SocketAsyncEventArgs();
  166. try
  167. {
  168. e.SetBuffer(bytsCmd, 0, bytsCmd.Length);
  169. }
  170. catch (Exception ex)
  171. {
  172. System.Diagnostics.Debug.WriteLine("SetBuffer exception." + ex);
  173. return false;
  174. }
  175. try
  176. {
  177. if (m_skt.SendAsync(e))
  178. {//Returns true if the I/O operation is pending.
  179. return true;
  180. }
  181. }
  182. catch (Exception ex)
  183. {
  184. System.Diagnostics.Debug.WriteLine("SendAsync exception." + ex);
  185. return false;
  186. }
  187. //Returns false if the I/O operation completed synchronously.
  188. //In this case, The SocketAsyncEventArgs.Completed event on the e parameter will not be raised and
  189. //the e object passed as a parameter may be examined immediately after the method call returns to retrieve the result of the operation.
  190. return true;
  191. }
  192. }
  193. }

.net中的内存是由系统自行回收的,一旦一个对象(内存块)发现没有被其它任何人使用(引用)则会被回收。

当满足以下条件之一时将发生垃圾回收:

  • The system has low physical memory.

  • The memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This means that a threshold of acceptable memory usage has been exceeded on the managed heap.This threshold is continuously adjusted as the process runs.

  • The GC.Collect method is called. In almost all cases, you do not have to call this method, because the garbage collector runs continuously. This method is primarily used for unique situations and testing.

具体请看这里(垃圾回收

条件1:当物理内存极低时会调用

如上所说,我在一个服务器上测试此程序,8G内存,2W个连接,每5秒种给所有的连接发送一次。在大概4个小时就把所有的内存完了。从任务管理器上看,内存占用了7.9个G。并且,此时SERVER程序已经无法接受发送来自客户端的数据了。所以,按这个情况,内存回收肯定应该工作了!但没有!

条件2:已经在托管heap上分配的对象所占用的内存超过一个阀值时会调用。这个阀值会动态变更。

如上一个测试,物理内存都已经用光了,并导致程序不能正常运行了。这个阀值还没有超过?!!这个阀值是怎么定的呢?(需要找一下文档,网友了解的提供一下 :))

假定是因为某种原因,GC没有执行。那我们手动的执行一下,添加一个全局变量 s_iRcvTimes  ,每接收5000次就执行一下回收

  1. public bool SendAsync(byte[] bytsCmd)
  2. {
  3. if (s_iRcvTimes > 5000)
  4. {
  5. s_iRcvTimes = 0;
  6. GC.Collect(2);
  7. }
  8. s_iRcvTimes += 1;
  1. ...//原来的代码省略

测试结果如下:(程序启动后,每过一段时间记录一下SERVER程序的内存占用情况)

程序的启动内存占用为:4.5M

序号 时间 时间间隔 内存占用 内存增长
1 16:07:00 1分钟 22,023K --
2 16:08:00 1分钟 22,900K 677K
3 16:10:00 2分钟 26,132K 3,232K
4 16:12:00 2分钟 30,172K 4,040K
5 16:17:00 5分钟 116,032K 85,860K
6 16:22:00 5分钟 200,146K 84,114K
7 16:27:00 5分钟 274,120K 73,974K

内存占用:对应时刻Server程序所占用的内存(从windows任务管理器看到的数据)

从测试结果来看,应该没有起到作用!

我感觉,还是程序有问题!理论上来说,一旦内存不够,则系统自动进行回收,但是,为什么这里的情况不进行回收呢?!!MSDN里有这样一句话:

When a garbage collection is triggered, the garbage collector reclaims the memory that is occupied by dead objects.

所以,有可能有些对象根本都没有成为 dead objects,从而使GC没办法将其回收。

OK ,那先找到内存爆涨的地方,再来分析为什么这些对象没办法成为 dead object !

内存出问题,那肯定是NEW出来的没有被回收!

程序中,NEW的地方有几个:

1.收到数据后,回送的地方:SendAsync(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 });

2.接收数据里的异步操作支持:SocketAsyncEventArgs e = new SocketAsyncEventArgs();

下意识的觉得第二个地方比较可疑。所以,修改此处并测试。 内存果然有明显的提升:

两个测试:

1. 5K个连接,每次给每个连接发送20个bytes,发完后停止1秒再继续。

2. 1K个连接,每次给每个连接发送20个bytes,发完后停止1秒再继续。

测试的结果,可以坚持10分钟以上没有任何问题,这期间内存一直在30M以下,启动后当所有的连接连上来之后(开始发送数据之前)程序大概占用20M

其实MSDN里也有对这个的例子,例子中对这个 SocketAsyncEventArgs也是给缓存起来,而不是直接每次都NEW一个对象。它的例子详细请看这里

修改后的代码如下:添加一个变量

  1. public static List<SocketAsyncEventArgs> s_lst = new List<SocketAsyncEventArgs>();

然后修改SendAsync 函数如下:

  1. public bool SendAsync(byte[] bytsCmd)
  2. {
  3. SocketAsyncEventArgs e = null;//new SocketAsyncEventArgs();
  4. lock (Program.s_lst)
  5. {
  6. if (Program.s_lst.Count > 0)
  7. {
  8. e = Program.s_lst[Program.s_lst.Count - 1];
  9. Program.s_lst.RemoveAt(Program.s_lst.Count - 1);
  10. }
  11. }
  12. if (e == null)
  13. {
  14. e = new SocketAsyncEventArgs();
  15. e.Completed += (object sender, SocketAsyncEventArgs _e) =>
  16. {
  17. lock (Program.s_lst)
  18. Program.s_lst.Add(e);
  19. };
  20. }
  21. try
  22. {
  23. e.SetBuffer(bytsCmd, 0, bytsCmd.Length);
  24. }
  25. catch (Exception ex)
  26. {
  27. lock (Program.s_lst)
  28. Program.s_lst.Add(e);
  29. System.Diagnostics.Debug.WriteLine("SetBuffer exception." + ex);
  30. return false;
  31. }
  32. try
  33. {
  34. if (m_skt.SendAsync(e))
  35. {//Returns true if the I/O operation is pending.
  36. return true;
  37. }
  38. }
  39. catch (Exception ex)
  40. {
  41. System.Diagnostics.Debug.WriteLine("SendAsync exception." + ex);
  42. return false;
  43. }
  44. //Returns false if the I/O operation completed synchronously.
  45. //In this case, The SocketAsyncEventArgs.Completed event on the e parameter will not be raised and
  46. //the e object passed as a parameter may be examined immediately after the method call returns to retrieve the result of the operation.
  47. lock (Program.s_lst)
  48. Program.s_lst.Add(e);
  49. return true;
  50. }

方法应该比较简单:不是每次都创建新的对象,而是用完后保存起来给下次调用时使用。

现在的问题比较明确了:

为什么这里的 new SocketAsyncEventArgs() 会无法被回收呢? 也就是说:一直被某个对象引用着,无法成为 dead object.

明天继续... :)

程序中的代码也可以这里 下载

转 http://blog.csdn.net/ani/article/details/7182035

之前笔者也遇到这个问题,后来根据此文得以解决,特转载下来。

.net 中异步SOCKET发送数据时碰到的内存问题的更多相关文章

  1. 对于socket发送数据时是否要加锁及write read的阻塞非阻塞

    偶尔讨论到了socket发送数据时是否应该加锁的问题,就在网上查了一下,下面是大神陈硕的答案 对于 UDP,多线程读写同一个 socket 不用加锁,不过更好的做法是每个线程有自己的 socket,避 ...

  2. c++ socket发送数据时,sendData = char * string 导致的乱码问题

    解决方法:将string 通过copy函数复制到某个char[] 1. string res =“xxx”; char arr[100]; int len = res.copy(arr, 100); ...

  3. HTTP 请求方式: GET和POST的比较当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。

    什么是HTTP? 超文本传输协议(HyperText Transfer Protocol -- HTTP)是一个设计来使客户端和服务器顺利进行通讯的协议. HTTP在客户端和服务器之间以request ...

  4. C#中在WebClient中使用post发送数据实现方法

    很多时候,我们需要使用C#中的WebClient 来收发数据,WebClient 类提供向 URI 标识的任何本地.Intranet 或 Internet 资源发送数据以及从这些资源接收数据的公共方法 ...

  5. 云计算之路-阿里云上:原来“黑色0.1秒”发生在socket读取数据时

    在昨天的博文(云计算之路-阿里云上:读取缓存时的“黑色0.1秒”)中我们犯了一个很低级的错误——把13ms算成了130ms(感谢陈硕发现这个错误!),从而对问题的原因作出了错误的推断,望大家谅解! 从 ...

  6. STM32的USART发送数据时如何使用TXE和TC标志

    在USART的发送端有2个寄存器,一个是程序可以看到的USART_DR寄存器,另一个是程序看不到的移位寄存器,对应USART数据发送有两个标志,一个是TXE=发送数据寄存器空,另一个是TC=发送结束. ...

  7. C#程序中从数据库取数据时需注意数据类型之间的对应,int16\int32\int64

    private void btn2_Click(object sender, RoutedEventArgs e)         {             using (SqlConnection ...

  8. ionic中将service中异步返回的数据赋值给controller的$scope

    1.service中异步获取数据实例 angular.module('starter.services', []) .factory('Chats', function($http,$q) {//定义 ...

  9. asp.net mvc视图中使用entitySet类型数据时提示出错

    asp.net mvc5视图中使用entitySet类型数据时提示以下错误 检查了一下引用,发现已经引用了System.Data.Linq了,可是还是一直提示出错, 后来发现还需要在Views文件夹下 ...

随机推荐

  1. vue Watcher分类 computed watch

    1.Watcher构造函数源码部分代码 if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = ...

  2. position sticky 定位

    1.兼容性 https://caniuse.com/#search=sticky chrome.ios和firefox兼容性良好. 2.使用场景 sticky:粘性.粘性布局. 在屏幕范围内时,元素不 ...

  3. 【VMware】宿主机连接wifi,虚拟机中的Linux系统配置连接wifi

    环境描述 宿主机:Windows 10 64bit 虚拟机:Centos 第一步:虚拟机设置 选择连接方式为NAT 第二步:设置宿主机的wifi 控制面板>>网络和Internet> ...

  4. Linux系统中用stat命令查看文件的三个时间属性

    在Linux中,没有文件创建时间的概念.只有文件的访问时间.修改时间.状态改变时间.也就是说无法知道文件的创建时间. [root@rhel7 yum.repos.d]# stat cdrom.repo ...

  5. Linux安装Nginx1.7.4、php5.5.15和配置

    Nginx是一个轻量级的高性能Webserver.反向代理server.邮件(IMAP/POP3/SMTP)server,是Igor Sysoev为俄罗斯訪问量第二的Rambler.ru网站开发,第一 ...

  6. 【laravel5.4】git上clone项目到本地,配置和运行 项目报错:../vendor/aotuload.php不存在

    1.一般我们直接使用git clone 将git的项目克隆下来,在本地git库和云上git库建立关联关系 2.vendor[扩展]文件夹是不会上传的,那么下载下来直接运行项目,会报错: D:phpSt ...

  7. 架构-LAMP特级学习(网站服务器监控)

    1.服务监控(SNMP配合CACTI监控) Apache Web服务监控 MySQL数据库监控 磁盘空间监控 2.流量监控(SNMP配合MRTG监控) 网站流量监控 3.使用SNMP可以获取被监控服务 ...

  8. js setAttribute removeAttribute

    <input type="button" value="生效" style="font-size:111px"/> <sc ...

  9. [转]网易云音乐Android版使用的开源组件

    原文链接 网易云音乐Android版从第一版使用到现在,全新的 Material Design 界面,更加清新.简洁.同样也是音乐播放器开发者,我们确实需要思考,相同的功能,会如何选择.感谢开源,让我 ...

  10. 【LeetCode】130. Surrounded Regions (2 solutions)

    Surrounded Regions Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A ...