积极答复者
关于Socket高并发时出现的异常

问题
-
使用.Net Socket 的 SocketAsyncEventArgs 方式做服务端进行侦听接受客户端连接,当并发线程数小于50,且连接的客户端(工业设备)较为稳定不会时常断线时,客户端数量达到3万以上均无问题,且CPU及内存使用量都非常小。但是发现以下情况时服务端会出现崩溃:
1、 并发线程数超过 50 时,会出现服务端无法接受新的客户端连接。
经过反复实验发现,再次调用 SocketServer.AcceptAsync(args) 方法可以恢复接受连接,所以估计是 .Net Socket 内部处理问题,导致 args.Completed 事件没有产生。
2、 连接到到服务端的设备大批量同时掉线时会出现以下异常,该异常无法捕获:
System.Transactions Critical: 0 : <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>未处理的异常</Description><AppDomain>amschannelwin.vshost.exe</AppDomain><Exception><ExceptionType>System.NullReferenceException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>未将对象引用设置到对象的实例。</Message><StackTrace> 在 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</StackTrace><ExceptionString>System.NullReferenceException: 未将对象引用设置到对象的实例。
在 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</ExceptionString></Exception></TraceRecord>
3、 某些工业设备可能未严格按照 TCP 协议进行握手、数据传输、断开连接等底层通讯,导致使用 .Net Socket 编写的服务端出现更为严重的错误,该错误也无法进行异常捕获:
检测到 FatalExecutionEngineError
Message: 运行时遇到了错误。此错误的地址为 0x793dfbf0,在线程 0x744 上。错误代码为 0xc0000005。此错误可能是 CLR 中的 bug,或者是用户代码的不安全部分或不可验证部分中的 bug。此 bug 的常见来源包括用户对 COM-interop 或 PInvoke 的封送处理错误,这些错误可能会损坏堆栈。以上是使用 C# 语言 .Net 4.0 开发 Socket 服务器遇到的问题,但是同样的工业设备连接到 C++ 开发的服务器上没有问题,希望能与 C# 开发遇到同样问题的朋友进行交流。
答案
-
补充:
问题1 是使用C++编写的模拟软件进行测试,设置并发线程为50,在连接数达到2000 - 5000时均会出现无法接受连接的情况,而此时服务器程序的内存占用仅为50M。但是设置并发线程小于50后,可以稳定的达到3万连接,并且内存仅为60M,CPU负荷也很低,简化后的代码如下:
this.SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.SocketServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
this.SocketServer.SendBufferSize = 255;
this.SocketServer.ReceiveBufferSize = 255;
this.SocketServer.Bind(new IPEndPoint(System.Net.IPAddress.Any, 4002));
this.SocketServer.Listen(500);
this.Accept();private void Accept()
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventArgs_Completed);try
{
if (this.SocketServer.AcceptAsync(args) == false)
{
this.AcceptCallback(args);
}
}
catch (Exception)
{
this.Accept();
}
}
以上代码未使用栈来存放空闲SocketAsyncEventArgs以达到可复用的目的,发现入栈出栈更加消耗资源消耗时间问题2和问题3 均是在现场环境中出现的,使用模拟软件未出现过此异常,且现场环境中接入的客户端数量仅为2000不到,并且通讯量并不大,服务端压力也很小。怀疑还是 .Net 底层 Socket 处理的问题。
- 已建议为答案 CrazyGhost_Von 2012年5月30日 5:58
- 已标记为答案 Mike FengModerator 2012年6月11日 5:54
-
第一点当在很多连接接入的时候,如果处理不级时是会存在连接不能接入的情况,1时候可以通过listen指定队列数量,2AcceptAsync尽可以快速的处理进入下一下AcceptAsync,如果过程中有逻辑处理可以把accept的socket扔到队列中,然后马上开始2AcceptAsync.既然可以通过AcceptAsync恢复显然是你的逻辑代码处理问题导致没有执行AcceptAsync
第二点触发这个错误是PerformIOCompletionCallback,这个方法是IO回调用的,应用是EndReceive或args.Completed 涉及的地方,估计是你程序问题可以看一exception的详细信息来排查.
第三点也许真的是.net问题
有一点更搞不懂既然是使用异步模型,为何需要用到这么多经程处理,即使你有3W个连接每秒每个连接都要进行发或接收处理,基本一个接收线和和一个发送线程就完全足够应付.
- 已建议为答案 CrazyGhost_Von 2012年6月6日 11:55
- 已标记为答案 Mike FengModerator 2012年6月11日 5:54
全部回复
-
补充:
问题1 是使用C++编写的模拟软件进行测试,设置并发线程为50,在连接数达到2000 - 5000时均会出现无法接受连接的情况,而此时服务器程序的内存占用仅为50M。但是设置并发线程小于50后,可以稳定的达到3万连接,并且内存仅为60M,CPU负荷也很低,简化后的代码如下:
this.SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.SocketServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
this.SocketServer.SendBufferSize = 255;
this.SocketServer.ReceiveBufferSize = 255;
this.SocketServer.Bind(new IPEndPoint(System.Net.IPAddress.Any, 4002));
this.SocketServer.Listen(500);
this.Accept();private void Accept()
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventArgs_Completed);try
{
if (this.SocketServer.AcceptAsync(args) == false)
{
this.AcceptCallback(args);
}
}
catch (Exception)
{
this.Accept();
}
}
以上代码未使用栈来存放空闲SocketAsyncEventArgs以达到可复用的目的,发现入栈出栈更加消耗资源消耗时间问题2和问题3 均是在现场环境中出现的,使用模拟软件未出现过此异常,且现场环境中接入的客户端数量仅为2000不到,并且通讯量并不大,服务端压力也很小。怀疑还是 .Net 底层 Socket 处理的问题。
- 已建议为答案 CrazyGhost_Von 2012年5月30日 5:58
- 已标记为答案 Mike FengModerator 2012年6月11日 5:54
-
兄弟给你个注意,直接向微软的开发人员问。有答案了别忘了给大伙说说
Mariya Atanasova是 System.Net 团队测试部的一名软件设计工程师。您可以通过她的博客 blogs.msdn.com/mariya 与她取得联系。
Larry Cleeton是 System.Net 团队的一名软件设计工程师。您可以通过他的博客 blogs.msdn.com/lcleeton 与他取得联系。
Mike Flasko是 System.Net、Winsock 和 Winsock Kernel 团队的项目经理。您可以通过他的博客 blogs.msdn.com/mflasko 与他取得联系。
Amit Paka是 System.Net 团队的一名软件设计工程师。您可以通过他的博客 blogs.msdn.com/amitpaka 与他取得联系。 -
可能是socket listen 的时候设置的队列太小
_socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5555)); _socket.Listen(1000);
话说如果是做对象池 4.0 用 concurrent bag 代替 locked stack 效率会高很多
listen的其实用不到 对象池 用了也浪费 只有 send receive 需要 而且租期与连接的生命周期要一致 一次receive 就要拿一次资源 那肯定效率低下
入了ipad,最近用ipad上论坛
-
第一点当在很多连接接入的时候,如果处理不级时是会存在连接不能接入的情况,1时候可以通过listen指定队列数量,2AcceptAsync尽可以快速的处理进入下一下AcceptAsync,如果过程中有逻辑处理可以把accept的socket扔到队列中,然后马上开始2AcceptAsync.既然可以通过AcceptAsync恢复显然是你的逻辑代码处理问题导致没有执行AcceptAsync
第二点触发这个错误是PerformIOCompletionCallback,这个方法是IO回调用的,应用是EndReceive或args.Completed 涉及的地方,估计是你程序问题可以看一exception的详细信息来排查.
第三点也许真的是.net问题
有一点更搞不懂既然是使用异步模型,为何需要用到这么多经程处理,即使你有3W个连接每秒每个连接都要进行发或接收处理,基本一个接收线和和一个发送线程就完全足够应付.
- 已建议为答案 CrazyGhost_Von 2012年6月6日 11:55
- 已标记为答案 Mike FengModerator 2012年6月11日 5:54