调用EndReceive时抛出ObjectDisposedException的异步Socket客户端问题
嘿,我仔细看了你的代码,发现问题出在异步接收的循环没有正确终止上。当你调用theClient.Shutdown(SocketShutdown.Both);和theClient.Close();时,Socket上还挂着一个未完成的BeginReceive操作,这时候关闭Socket会直接中断这个异步接收,从而触发你看到的EndReceive行的异常。
为啥会出现这个异常?
你的ReceiveCallback里不管有没有收到完整响应,都会再次调用BeginReceive,相当于让Socket一直处于“等数据”的状态。哪怕你已经拿到了模块的响应,这个循环还在继续发起新的接收请求。当你关闭Socket时,这个还在等待的异步操作就会被强制打断,抛出SocketException(通常错误码是10054或者10004)。
另外你的接收逻辑还有个小隐患:receiveDone.Set()的时机太随意了,不管有没有收到完整数据都会触发,虽然你说功能正常,但如果模块响应分多次发送,可能会导致你提前结束等待,拿到不完整的响应。
具体怎么修?
我给你整理了几个关键修复点:
给接收加“结束判断”
你得知道什么时候算收到了完整的响应——比如RN 171的响应是不是以特定字符(比如\r\n)结尾?或者有没有固定长度?找到这个结束标志后,在ReceiveCallback里判断,一旦收到完整响应,就不要再继续调用BeginReceive了。标记停止接收的状态
给StateObject加个布尔标志,用来告诉回调函数“不用再接收了”,避免在关闭Socket后还有新的接收请求发起。捕获并处理正常关闭的异常
就算做了前面的处理,偶尔还是可能出现异常,比如Socket关闭时刚好有挂起的接收操作,这时候我们可以捕获特定的Socket异常,判断是正常关闭导致的,就直接忽略。
修改后的代码示例
首先给StateObject加个停止接收的标志:
public class StateObject { public Socket workSocket = null; public const int BufferSize = 256; public byte[] buffer = new byte[BufferSize]; public StringBuilder sb = new StringBuilder(); public bool StopReceiving = false; // 新增:标记是否停止接收 }
然后修改ReceiveCallback方法,加上结束判断和异常处理:
private static void ReceiveCallback(IAsyncResult ar) { try { StateObject state = (StateObject)ar.AsyncState; Socket clientReceiveCB = state.workSocket; // 如果已经标记停止接收,直接退出 if (state.StopReceiving) { return; } int bytesRead = clientReceiveCB.EndReceive(ar); if (bytesRead > 0) { string testResponse = Encoding.ASCII.GetString(state.buffer, 0, bytesRead); state.sb.Append(testResponse); // 这里替换成RN 171的响应结束判断逻辑 // 示例:假设响应以"\r\n"结尾 if (state.sb.ToString().EndsWith("\r\n")) { response = state.sb.ToString(); receiveDone.Set(); state.StopReceiving = true; // 标记停止,不再发起新接收 return; } // 没收到完整响应,继续接收 clientReceiveCB.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); } else { // 对方主动关闭连接,处理剩余数据 response = state.sb.ToString(); receiveDone.Set(); state.StopReceiving = true; } } catch (SocketException ex) { // 正常关闭Socket导致的异常,直接忽略 if (ex.ErrorCode == 10054 || ex.ErrorCode == 10004) { Console.WriteLine("Socket正常关闭,忽略此异常"); } else { Console.WriteLine("接收异常:" + ex.ToString()); } } catch (Exception e) { Console.WriteLine(e.ToString()); } }
额外的优化建议
你的代码里response、connectDone这些都是静态变量,要是RabbitMQ同时触发多次Start调用,肯定会出现线程安全问题。建议把这些改成实例变量,每个Electricity实例拥有自己的事件和响应变量:
public class Electricity { private const int port = 2000; // 改成实例成员,避免多线程冲突 private ManualResetEvent connectDone = new ManualResetEvent(false); private ManualResetEvent sendDone = new ManualResetEvent(false); private ManualResetEvent receiveDone = new ManualResetEvent(false); private string response = string.Empty; // ... 其他代码不变,只是把静态的事件和response改成实例的 }
这样多个请求之间就不会互相干扰了,稳定性会好很多。
内容的提问来源于stack exchange,提问作者Raphaël Dev

