You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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.

Problem Recap & Solution Overview

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

  1. Create a Worker Service project (use Visual Studio's template or run dotnet new worker in your terminal)
  2. Update Worker.cs with 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;
    }
}
  1. Register the service as a singleton in Program.cs so 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

  1. Deploy the Proxy Service: Publish it as a Windows Service (use Visual Studio's publish wizard or sc create command) so it runs automatically in the background.
  2. 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.
  3. Monitor Ports: Use netstat -ano on 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:23:31