网上的开源socket服务一般都是go实现的,几乎没有见到用dotnet写的,为了压榨GPT的免费额度,无聊时,就给它提需求,让它用dotnetcore写一个TCP的,很简单能跑在linux终端上就行。后面有需求再改为socket协议。

服务端 ProxyServerApp

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace ProxyServerApp
{

    public class Program
    {
       public static async Task Main(string[] args)
        {
            using var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
            });

            var logger = loggerFactory.CreateLogger<ProxySocketServer>();
            int port = 8080; // 默认监听端口

            if (args.Length > 0 && int.TryParse(args[0], out int customPort))
            {
                port = customPort;
            }

            var proxyServer = new ProxySocketServer(port, logger);
            await proxyServer.StartListeningAsync();
        }
    }
    public class ProxySocketServer
    {
        private readonly int _listenPort;
        private readonly ILogger _logger;

        public ProxySocketServer(int listenPort, ILogger<ProxySocketServer> logger)
        {
            _listenPort = listenPort;
            _logger = logger;
        }

        public async Task StartListeningAsync()
        {
            var listener = new TcpListener(IPAddress.Any, _listenPort);
            listener.Start();
            _logger.LogInformation($"Proxy server started, listening on port {_listenPort}...");

            while (true)
            {
                var client = await listener.AcceptTcpClientAsync();
                _logger.LogInformation("Client connected.");
                _ = HandleClientAsync(client);
            }
        }
        private async Task HandleClientAsync(TcpClient client)
        {
            try
            {
                using (var clientStream = client.GetStream())
                {
                    var buffer = new byte[4096];
                    string request = string.Empty;
                    bool isConnectRequest = false;

                    // 循环读取,直到收到 "CONNECT" 请求
                    for (int readAttempt = 0; readAttempt < 3; readAttempt++)
                    {
                        int bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length);
                        request = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                        _logger.LogInformation($"Received request attempt {readAttempt + 1}: {request}");

                        if (request.StartsWith("CONNECT"))
                        {
                            isConnectRequest = true;
                            break;
                        }
                        else
                        {
                            _logger.LogWarning("Invalid request. Retrying to read the 'CONNECT' request...");
                            await Task.Delay(500); // 增加延迟避免快速重试
                        }
                    }

                    if (!isConnectRequest)
                    {
                        var errorResponse = "HTTP/1.1 400 Bad Request\r\n\r\n";
                        await clientStream.WriteAsync(Encoding.UTF8.GetBytes(errorResponse));
                        _logger.LogError("Failed to receive a valid CONNECT request.");
                        return;
                    }

                    // 解析 CONNECT 请求
                    var connectLine = request.Split(' ')[1];
                    var hostAndPort = connectLine.Split(':');
                    var destinationHost = hostAndPort[0];
                    var destinationPort = int.Parse(hostAndPort[1]);

                    bool connectionEstablished = false;

                    // 尝试连接到目标服务器,最多3次
                    for (int attempt = 0; attempt < 3; attempt++)
                    {
                        try
                        {
                            using (var destinationClient = new TcpClient())
                            {
                                await destinationClient.ConnectAsync(destinationHost, destinationPort);
                                _logger.LogInformation($"Connected to destination: {destinationHost}:{destinationPort}");

                                // 发送成功响应给客户端
                                var response = "HTTP/1.1 200 Connection Established\r\n\r\n";
                                await clientStream.WriteAsync(Encoding.UTF8.GetBytes(response));
                                _logger.LogInformation("Sent 200 Connection Established to client.");
                                connectionEstablished = true;

                                // 双向传输:代理客户端和目标服务器的数据
                                await Task.WhenAny(
                                    TransferData(clientStream, destinationClient.GetStream()),
                                    TransferData(destinationClient.GetStream(), clientStream)
                                );
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogWarning($"Attempt {attempt + 1} to connect to {destinationHost}:{destinationPort} failed: {ex.Message}");
                            if (attempt == 2) // 最后一次尝试
                            {
                                var errorResponse = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
                                await clientStream.WriteAsync(Encoding.UTF8.GetBytes(errorResponse));
                                _logger.LogError($"Failed to establish connection to {destinationHost}:{destinationPort}");
                            }
                        }

                        if (connectionEstablished) break; // 成功则跳出重试
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error: {ex.Message}");
            }
            finally
            {
                client.Close();
                _logger.LogInformation("Client disconnected.");
            }
        }


        private async Task TransferData(NetworkStream source, NetworkStream destination)
        {
            var buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await destination.WriteAsync(buffer, 0, bytesRead);
                await destination.FlushAsync();
            }
        }
    }


}

客户端测试用例

using System;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.NetworkInformation;

namespace ProxyClientApp
{
    class Program
    {
        private static readonly string[] proxyServers = { "yourserverip1:port", "yourserverip2:port" }; // 设置多个
        private const string targetUrl = "https://v2ex.com/index.xml"; // 目标 URL
        private const int maxRetries = 3; // 最大重试次数

        static async Task Main(string[] args)
        {
            try
            {
                //for (int i = 0; i < 100; i++)
                {
                    await RetryOnExceptionAsync(async () => await ConnectThroughProxyAsync(), maxRetries, 3000); 
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine($"Operation failed after {maxRetries} retries: {ex.Message}");
            }
        }

        private static async Task ConnectThroughProxyAsync()
        {
            var uri = new Uri(targetUrl);
            string targetHost = uri.Host;
            int targetPort = uri.Scheme == "https" ? 443 : 80;

            foreach (var proxy in proxyServers)
            {
                var parts = proxy.Split(':');
                var proxyHost = parts[0];
                var proxyPort = int.Parse(parts[1]);

                if (!NetworkInterface.GetIsNetworkAvailable())
                {
                    Console.WriteLine("Network unavailable. Retrying in 5 seconds...");
                    await Task.Delay(5000);
                    continue;
                }

                using (var client = new TcpClient())
                {
                    try
                    {
                        Console.WriteLine($"Attempting to connect to proxy {proxyHost}:{proxyPort}");
                        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
                        client.Client.ReceiveTimeout = 5000;
                        client.Client.SendTimeout = 5000;

                        // 连接到代理服务器
                        await client.ConnectAsync(proxyHost, proxyPort);
                        Console.WriteLine($"Connected to proxy: {proxyHost}:{proxyPort}");

                        using (var networkStream = client.GetStream())  clientStream.ReadAsync(buffer, 0, buffer.Length);
                        using (var writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = true })
                        using (var reader = new StreamReader(networkStream, Encoding.UTF8))
                        {
                            // 发送 CONNECT 请求
                            string connectRequest = $"CONNECT {targetHost}:{targetPort} HTTP/1.1\r\nHost: {targetHost}\r\n\r\n";
                            await writer.WriteAsync(connectRequest);
                            Console.WriteLine("Sent CONNECT request to proxy.");
 
                            var connectResponse = await reader.ReadLineAsync();
                            Console.WriteLine("Proxy server response: " + connectResponse);

                            if (!connectResponse.Contains("200"))
                            {
                                Console.WriteLine("Failed to establish connection through proxy.");
                                continue;
                            }

                            // 创建 SSL/TLS 流
                            var sslStream = new SslStream(networkStream, false);
                            using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) // 设置超时
                            {
                                await sslStream.AuthenticateAsClientAsync(targetHost, null, System.Security.Authentication.SslProtocols.Tls12, false);
                                Console.WriteLine("SSL/TLS handshake successful.");

                                // 发送 GET 请求到目标服务器
                                string getRequest = $"GET {uri.PathAndQuery} HTTP/1.1\r\nHost: {targetHost}\r\nConnection: close\r\n\r\n";
                                await sslStream.WriteAsync(Encoding.UTF8.GetBytes(getRequest));
                                Console.WriteLine("Sent GET request to target server.");

                                StringBuilder httpContent = new StringBuilder();
                                // 读取响应数据
                                using (var responseReader = new StreamReader(sslStream, Encoding.UTF8))
                                {
                                    string responseLine;
                                    Console.WriteLine("Response from target server:");
                                    while ((responseLine = await responseReader.ReadLineAsync()) != null)
                                    {
                                        Console.WriteLine(responseLine);
                                        httpContent.Append(responseLine);
                                    }
                                }
                                System.Diagnostics.Debug.WriteLine(httpContent.ToString());
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($" proxy {proxyHost}:{proxyPort} , Error: {ex.Message}");
                    }
                }
            }
        }

        private static async Task RetryOnExceptionAsync(Func<Task> operation, int maxRetries = 3, int delayMilliseconds = 1000)
        {
            for (int retry = 0; retry < maxRetries; retry++)
            {
                try
                {
                    await operation();
                    return; // 操作成功,直接返回
                }
                catch (Exception ex) when (retry < maxRetries - 1)
                {
                    Console.WriteLine($"Retry {retry + 1}/{maxRetries} failed: {ex.Message}");
                    await Task.Delay(delayMilliseconds * (int)Math.Pow(2, retry)); // 指数退避
                }
            }
            throw new Exception("Operation failed after retries.");
        } 
}

通过local网络客户端,访问https://v2ex.com/index.xml。测试结果:
服务端
image-1729910663580
客户端
image-1729908867038

分析

没有网络知识,又是应用级API,只能看个大概,大致就是S与C先连上,然后S 等待reader some data,C 开始write some data 并等待. S拿到C的some data,处理结果write result data, 这时C的wite方法返回了S 的 result data,关闭连接,结束。所C要与S保待节奏一致,GPT给的都是理想情况一次完成,出错几次后,再把调试信息给它,它会加上retries的次数。这也证明了GPT 的确根据理解生成的代码,不是搜索的代码。