C# WinForm TCP服务端出现消息重复发送问题的原因咨询
TCP服务端消息重复发送的问题排查
现存代码的核心问题点
- 全局接收缓冲区的并发冲突
你代码中用于接收数据的buffer、BufferSize属于类级别的全局共享变量,所有客户端连接共用同一块内存区域接收网络数据。当多个客户端并发发送消息时,后触发的接收回调会直接覆盖缓冲区中已暂存的未处理内容,既会导致转发的消息内容错乱,也会因为并发读写同一块内存,出现同一份数据被多个接收回调重复读取、重复转发的问题。 - 缺少连接状态与返回值校验
当前OnDataReceived方法在执行EndReceive获取返回值received后,没有做分支校验:当received == 0时,代表客户端已经正常断开连接,此时仍会执行消息转发、继续为已失效的Socket挂载新的接收回调。在异常处理分支中,也没有清理已处于Pending状态的接收请求、关闭失效Socket,遇到网络闪断、半开连接的场景时,同一个连接会被多次注册OnDataReceived回调,每次数据到达就会触发多轮转发,直接造成消息重复。 - 客户端集合的非线程安全遍历
SentToOthers方法中直接遍历clientsList集合执行发送,但这个集合会在新客户端接入、客户端断开时被其他线程修改。在接收回调的线程池线程中无锁遍历该集合时,既可能触发集合修改的运行时异常,也可能因为迭代器状态异常,出现同一个客户端被多次遍历、重复调用Send发送同一份消息的情况。 - 同步发送阻塞引发的回调重入
SentToOthers方法中使用同步的Socket.Send执行消息转发,该操作会直接阻塞当前接收回调的IO线程。当网络拥塞、Socket内部发送缓冲打满时,阻塞时间会被拉长,极易导致底层IOCP机制多次触发同一个Socket的接收回调,每次重入都会执行一轮完整的转发逻辑,进一步放大重复发送的概率。
对应修正方向
- 为每个
Client实例绑定独立的接收缓冲区,彻底替换全局共享的buffer,从根源上避免多客户端并发时的内存读写冲突。 - 在
EndReceive执行完成后首先判断received == 0的场景,直接执行对应客户端的连接清理逻辑,不再继续转发消息、挂载新的接收回调;在异常分支中确保关闭失效Socket、释放对应的回调注册,避免残留的Pending请求重复触发回调。 - 对
clientsList的遍历、新增、删除操作加锁保护,或替换为线程安全的集合类型,避免迭代过程中集合被修改导致的重复遍历、集合报错问题。 - 将转发逻辑的同步发送改为异步发送,避免阻塞接收回调线程,降低回调重入的发生概率。
内容的提问来源于stack exchange,提问作者m00s
相关产品推荐
相关产品推荐

