C#多线程TCP客户端使用同一端口收发数据连接异常咨询
问题结论
客户端收发数据共用同一个本地端口的要求完全可以实现,你当前的认知有误,报错是代码逻辑错误导致的。
报错根本原因
你在发送线程和接收线程分别创建了独立的Socket实例,且两个实例都尝试绑定同一个本地端口、连接同一个远端服务器的同一个端口。不管是否开启端口复用,同一台设备上同一个TCP四元组(源IP、源端口、目的IP、目的端口)只能对应一个Socket连接,两个Socket实例争抢同一组资源必然会抛出占用异常。
解决方案
Socket本身是全双工通信组件,同一个Socket实例天生支持同时执行发送和接收操作,你只需要全局仅初始化一次Socket实例,分别给两个线程调用Send和Receive方法即可,具体修改逻辑如下:
- 全局共享唯一的Socket实例,将Socket初始化、绑定、连接操作合并为单次执行逻辑,不要分散在收发线程中重复执行。参考初始化代码:
// 建议将Socket实例放到GlobalVar中全局共享 public void InitCommSocket() { while (!GlobalVar.is_conn) { } try { GlobalVar.CommSocket = new Socket(AddressFamily.InterNetwork, GlobalVar._isTcp ? SocketType.Stream : SocketType.Dgram, GlobalVar._isTcp ? ProtocolType.Tcp : ProtocolType.Udp); IPEndPoint serverEndPoint = new IPEndPoint(Mul_ip, Mul_port); if (GlobalVar._isTcp) { GlobalVar.CommSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true); GlobalVar.CommSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); GlobalVar.CommSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); GlobalVar.CommSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, true); } else { GlobalVar.CommSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(Mul_ip, IPAddress.Any)); } // 仅绑定一次本地端口 GlobalVar.CommSocket.Bind(new IPEndPoint(IPAddress.Any, Mul_port)); // 仅连接一次服务器 GlobalVar.CommSocket.Connect(serverEndPoint); GlobalVar.socketWasConfiged = true; } catch(Exception e) { MessageBox.Show("Socket初始化失败:" + e.Message, "错误"); } }
- 简化发送线程逻辑,仅处理队列消息发送:
public void Send_msg() { // 等待Socket初始化完成 while (!GlobalVar.socketWasConfiged) { } try { while (!_shouldStop) { while (!Send_msg_queue.IsEmpty) { Send_msg_queue.TryDequeue(out CM_msg send_msg); byte[] send_buff = Encoder(send_msg); try { // 直接调用全局Socket的Send方法 GlobalVar.CommSocket.Send(send_buff, SocketFlags.None); GlobalVar.Is_Wd_Rec = false; } catch (Exception e) { MessageBox.Show("发送消息出错:" + e.Message, "错误"); } } } } catch(Exception e) { MessageBox.Show("发送线程异常:" + e.Message, "错误"); } }
- 简化接收线程逻辑,仅处理数据接收:
public void Receive_msg() { while (!GlobalVar.socketWasConfiged) { } try { byte[] rec_buff = new byte[size]; while (!_shouldStop) { // 直接调用全局Socket的Receive方法 size_rec = GlobalVar.CommSocket.Receive(rec_buff); GlobalVar.Counter_auto += 1; Receive_msg_queue.Enqueue(Decoder(rec_buff)); GlobalVar.Is_Wd_Rec = true; } } catch (Exception e) { MessageBox.Show("接收消息出错:" + e.Message); } }
注意事项
- 收发操作本身是线程安全的,Send和Receive调用互不影响,不需要额外加锁。
- 遇到连接断开等异常时,统一销毁旧Socket实例、重新执行初始化逻辑即可,不要在收发线程中单独处理重连,避免资源冲突。
- 不需要要求服务器修改回写端口,当前客户需求完全可以实现。
内容的提问来源于stack exchange,提问作者amir sarusi
相关产品推荐
相关产品推荐

