基于C#/.NET的移动端TCP长连接与Rest API架构优化问询
Hey there! As someone who's worked through similar TCP port exhaustion issues in .NET, let's walk through a practical solution that fits your setup—since you're an Android dev new to .NET, I'll keep things straightforward with actionable code and clear explanations.
Your current issue is that your ASP.NET REST API creates a new TCP socket for every request to the Linux server, leading to port exhaustion. The fix is to split off a dedicated background service that maintains persistent TCP connections to the Linux server. Your REST API will then communicate locally with this service instead of directly connecting to Linux, eliminating repeated socket creation/destruction.
Here's the revised flow:
Android App → ASP.NET REST API → Local Inter-Process Communication → .NET Background Service (Persistent TCP) → Linux Server
Step 1: Create a .NET Worker Service (Persistent TCP Manager)
.NET Worker Services are built for long-running background tasks—perfect for managing persistent TCP connections. They auto-start with Windows and include built-in lifecycle management.
Core Features
- Auto-connect to the Linux server on startup
- Auto-reconnect if the connection drops
- Forward requests from the REST API to Linux
- Relay responses back to the correct API request
Simplified Code Example
- Create a Worker Service project (use Visual Studio's template or run
dotnet new workerin your terminal) - Update
Worker.cswith this logic:
using System.Net.Sockets; using System.Text; using System.IO.Pipes; namespace LinuxTcpProxyService; public class Worker : BackgroundService { private TcpClient? _tcpClient; private NetworkStream? _stream; private readonly ILogger<Worker> _logger; private readonly Dictionary<string, TaskCompletionSource<string>> _pendingRequests = new(); private readonly object _lockObj = new(); public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // Establish initial connection to Linux await ConnectToLinux(stoppingToken); // Start listening for responses from Linux _ = ListenForLinuxResponses(stoppingToken); // Start listening for requests from the REST API via named pipes _ = ListenForApiRequests(stoppingToken); while (!stoppingToken.IsCancellationRequested) { // Reconnect if connection is lost if (_tcpClient?.Connected != true) { _logger.LogWarning("Lost connection to Linux server, reconnecting..."); await ConnectToLinux(stoppingToken); } await Task.Delay(5000, stoppingToken); } } private async Task ConnectToLinux(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { _tcpClient = new TcpClient(); await _tcpClient.ConnectAsync("PRIVATE_LINUX_IP", YOUR_TCP_PORT, stoppingToken); _stream = _tcpClient.GetStream(); _logger.LogInformation("Connected to Linux server successfully"); // Send your required login handshake here await SendLoginHandshake(); break; } catch (Exception ex) { _logger.LogError(ex, "Failed to connect to Linux. Retrying in 10s..."); await Task.Delay(10000, stoppingToken); } } } private async Task SendLoginHandshake() { var loginMsg = Encoding.UTF8.GetBytes("LOGIN:your_auth_credentials"); await _stream!.WriteAsync(loginMsg); await _stream.FlushAsync(); // Add logic to verify login response if needed } private async Task ListenForLinuxResponses(CancellationToken stoppingToken) { var buffer = new byte[4096]; while (!stoppingToken.IsCancellationRequested && _stream != null) { try { int bytesRead = await _stream.ReadAsync(buffer, stoppingToken); if (bytesRead == 0) { _logger.LogWarning("Linux server closed the connection"); _tcpClient?.Close(); break; } var response = Encoding.UTF8.GetString(buffer, 0, bytesRead); // Assume response format: "REQUEST_ID:response_content" var parts = response.Split(':', 2); if (parts.Length == 2 && _pendingRequests.TryGetValue(parts[0], out var tcs)) { lock (_lockObj) { _pendingRequests.Remove(parts[0]); } tcs.SetResult(parts[1]); } } catch (Exception ex) { _logger.LogError(ex, "Error reading from Linux server"); break; } } } private async Task ListenForApiRequests(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { // Use a named pipe for local communication with the REST API using var pipeServer = new NamedPipeServerStream( "LinuxTcpProxyPipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Message); _logger.LogInformation("Waiting for REST API connection..."); await pipeServer.WaitForConnectionAsync(stoppingToken); _logger.LogInformation("REST API connected to proxy service"); var reader = new StreamReader(pipeServer); var writer = new StreamWriter(pipeServer); writer.AutoFlush = true; while (!stoppingToken.IsCancellationRequested && pipeServer.IsConnected) { var apiRequest = await reader.ReadLineAsync(); if (apiRequest == null) break; var requestParts = apiRequest.Split(':', 2); if (requestParts.Length != 2) { await writer.WriteLineAsync("ERROR:Invalid request format"); continue; } try { var linuxResponse = await SendRequestToLinux(requestParts[0], requestParts[1]); await writer.WriteLineAsync(linuxResponse); } catch (Exception ex) { await writer.WriteLineAsync($"ERROR:{ex.Message}"); } } pipeServer.Disconnect(); } catch (Exception ex) { _logger.LogError(ex, "Error handling API request via pipe"); await Task.Delay(1000, stoppingToken); } } } public async Task<string> SendRequestToLinux(string requestId, string requestContent) { if (_stream == null || !_tcpClient!.Connected) { throw new InvalidOperationException("Not connected to Linux server"); } var tcs = new TaskCompletionSource<string>(); lock (_lockObj) { _pendingRequests.Add(requestId, tcs); } // Send request in format: "REQUEST_ID:request_content" var requestBytes = Encoding.UTF8.GetBytes($"{requestId}:{requestContent}"); await _stream.WriteAsync(requestBytes); await _stream.FlushAsync(); // Add timeout for unresponsive requests var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30)); var completedTask = await Task.WhenAny(tcs.Task, timeoutTask); if (completedTask == timeoutTask) { lock (_lockObj) { _pendingRequests.Remove(requestId); } throw new TimeoutException("Request to Linux timed out"); } return await tcs.Task; } }
- Register the service as a singleton in
Program.csso the REST API can access it:
using LinuxTcpProxyService; var builder = Host.CreateDefaultBuilder(args); builder.ConfigureServices(services => { services.AddHostedService<Worker>(); services.AddSingleton<Worker>(); }); var host = builder.Build(); await host.RunAsync();
Step 2: Update Your ASP.NET REST API to Use the Proxy Service
Since the REST API and proxy service run on the same Windows server, named pipes are the simplest, most efficient way to communicate locally.
Add Pipe Client Logic to Your API Controller
using System.IO.Pipes; [ApiController] [Route("api/[controller]")] public class DataController : ControllerBase { [HttpPost] public async Task<IActionResult> FetchData([FromBody] string requestContent) { var requestId = Guid.NewGuid().ToString(); // Unique ID to match responses var pipeName = "LinuxTcpProxyPipe"; using var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); try { await pipeClient.ConnectAsync(TimeSpan.FromSeconds(5)); var reader = new StreamReader(pipeClient); var writer = new StreamWriter(pipeClient); writer.AutoFlush = true; // Send request to proxy service await writer.WriteLineAsync($"{requestId}:{requestContent}"); // Receive response var response = await reader.ReadLineAsync(); if (response?.StartsWith("ERROR:") == true) { return StatusCode(500, response.Substring(6)); } return Ok(response); } catch (TimeoutException) { return StatusCode(503, "Proxy service unavailable"); } catch (Exception ex) { return StatusCode(500, ex.Message); } } }
Step 3: Deploy & Test
- Deploy the Proxy Service: Publish it as a Windows Service (use Visual Studio's publish wizard or
sc createcommand) so it runs automatically in the background. - Verify Connections: Check the proxy service logs to confirm it's connected to Linux. Then test your REST API with Postman or your Android app to ensure data flows correctly.
- Monitor Ports: Use
netstat -anoon Windows to confirm you no longer have hundreds of TIME_WAIT TCP connections—only the persistent connection(s) from the proxy service to Linux will exist.
Optional Optimizations
- Connection Pool: If a single TCP connection can't handle 8k concurrent requests, add a pool of persistent connections in the proxy service (confirm with Linux server owners first).
- Protobuf Serialization: Replace JSON with Protobuf for faster, smaller message payloads (compatible with Android too via
com.google.protobuf). - TLS Encryption: Add TLS to the TCP connection between the proxy service and Linux for secure data transfer.
- Logging: Use a library like Serilog to add detailed logs for debugging connection issues or slow requests.
内容的提问来源于stack exchange,提问作者Yousaf Bungash

