如何解决C#中TcpListener的“Address already in use”异常(端口复用场景)
问题:同一本地端口同时用于TCP客户端连接和监听时出现“Address already in use”异常
我需要用C#实现一套系统,功能如下:
- 通过类方法Method1连接远程主机;
- 在类方法Method2中每隔10秒向远程主机发送请求,维持步骤1建立的连接;
- 在类方法Method3中监听步骤1、2发送请求时使用的同一本地端口。
但执行步骤3时,出现了“Address already in use”异常,以下是相关代码:
public static async Task Method1() { TcpClient _client = new TcpClient(); _client = new TcpClient(); await _client.ConnectAsync("IPAddress", 1234); var localEndpoint = (IPEndPoint)_client.Client.LocalEndPoint; sourcePort = localEndpoint.Port; AppStatics.SourcePort = sourcePort; var _stream = _client.GetStream(); await _stream.WriteAsync("..."); Task.Run(() => Method2(_stream)); Method3(); } public static async Task Method2(NetworkStream stream) { //Start trying to connect to the remote server here Console.WriteLine("Checking the connection with the USSD gateway..."); var _shakeTimer = new System.Timers.Timer(10000); _shakeTimer.Elapsed += async (object source, ElapsedEventArgs e) => await SendNetworkConnectionCheck(stream); _shakeTimer.AutoReset = true; _shakeTimer.Enabled = true; } private static async Task<bool> SendNetworkConnectionCheck(NetworkStream stream) { stream.Write("..."); } public static void Method3() { TcpListener listener = new TcpListener(IPAddress.Any, AppStatics.SourcePort); listener.ExclusiveAddressUse = false; listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); try { listener.Stop(); listener.Start(); while (true) { TcpClient acceptTask = listener.AcceptTcpClient(); TcpClient incomingClient = acceptTask; } } catch (SocketException sex) { if (sex.SocketErrorCode == SocketError.AddressAlreadyInUse) { listener.Stop(); listener = new TcpListener(IPAddress.Any, AppStatics.SourcePort); listener.ExclusiveAddressUse = false; listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); listener.Start(); }else { Console.WriteLine(sex.InnerException?.Message ?? sex.Message); } } catch (Exception ex) { Console.WriteLine("There was error while trying to listen/handle incoming message"); string errMsg = ex.InnerException?.Message ?? ex.Message; Console.WriteLine(errMsg); Console.WriteLine(ex.StackTrace?.ToString() ?? ex.ToString()); } }
解决方案
1. 明确TCP端口复用的核心逻辑
TCP客户端连接建立后,本地端口会与远程IP+端口形成双向绑定,默认无法直接在该端口启动监听。ReuseAddress选项的作用是允许不同套接字绑定到同一端口,但需要客户端和监听端都正确配置该选项,且满足TCP协议的复用规则。
2. 修正客户端套接字配置
在Method1中创建TcpClient时,提前设置端口复用选项,确保后续监听可以复用该端口:
public static async Task Method1() { TcpClient _client = new TcpClient(); // 配置客户端套接字允许端口复用 _client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _client.Client.ExclusiveAddressUse = false; await _client.ConnectAsync("IPAddress", 1234); var localEndpoint = (IPEndPoint)_client.Client.LocalEndPoint; sourcePort = localEndpoint.Port; AppStatics.SourcePort = sourcePort; var _stream = _client.GetStream(); await _stream.WriteAsync(Encoding.UTF8.GetBytes("...")); // 用_ = 避免未捕获任务警告 _ = Task.Run(() => Method2(_stream)); Method3(); }
3. 重构监听逻辑,正确配置套接字
放弃TcpListener的间接配置,直接使用Socket类来构建监听,确保复用选项在绑定前生效:
public static void Method3() { using Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 关键配置:允许端口复用 + 非独占地址 listenerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); listenerSocket.ExclusiveAddressUse = false; try { listenerSocket.Bind(new IPEndPoint(IPAddress.Any, AppStatics.SourcePort)); listenerSocket.Listen(10); // 设置监听队列长度 while (true) { using Socket incomingSocket = listenerSocket.Accept(); // 处理 incomingSocket 的数据交互逻辑 } } catch (SocketException sex) { if (sex.SocketErrorCode != SocketError.AddressAlreadyInUse) { Console.WriteLine($"Socket异常: {sex.Message}"); } } catch (Exception ex) { Console.WriteLine("监听/处理消息时出错"); string errMsg = ex.InnerException?.Message ?? ex.Message; Console.WriteLine($"错误信息: {errMsg}"); Console.WriteLine(ex.StackTrace ?? ex.ToString()); } }
4. 其他优化点
SendNetworkConnectionCheck方法中,stream.Write需传入字节数组,建议补充编码转换(如Encoding.UTF8.GetBytes("...")),同时使用异步写法await stream.WriteAsync保持一致性;Method2中的定时器需注意生命周期,可通过将定时器变量提升为类成员避免被GC回收;- 移除
Method3中重复创建TcpListener的无效重试逻辑,一次配置到位即可。
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

