c#开发完整的Socks5代理客户端与服务端——客户端(已完结)

“);

##### 客户端开启对应TCP服务
```csharp
 _tcpListener = new TcpListener(IPAddress.Any, _httpProxyPort);
 _tcpListener.Start();

主要就是开启一个监听服务,让操作系统将对应的流量转发到我们的Socks5客户端。 > > > 将http报文进行解析,获取到请求的targetHost和Port,这样才能后面在和Socks5服务端握手的时候才能告知对方需要代理的远端信息。 >

解析系统的Http请求
/// <summary>
    /// 解析http请求报文,提取关键信息
    /// </summary>
    /// <param name="request"/>http请求
    /// <param name="host"/>请求主机
    /// <param name="port"/>请求主机端口号
    /// <returns>是否解析成功</returns>
    private bool TryParseHttpRequest(string request, out string host, out int port)
    {
        host = null;
        port = 0;
// 解析 CONNECT 请求(如 CONNECT example.com:443 HTTP/1.1)
        if (request.StartsWith(&#34;CONNECT&#34;))
        {
            var parts = request.Split(&#39; &#39;)[1].Split(&#39;:&#39;);
            host = parts[0];
            port = int.Parse(parts[1]);
            return true;
        }
// 2. 处理 GET/POST 请求(HTTP)
        using (var reader = new StringReader(request))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                // 从 Host 头提取目标
                if (line.StartsWith(&#34;Host:&#34;, StringComparison.OrdinalIgnoreCase))
                {
                    var hostParts = line.Substring(5).Trim().Split(&#39;:&#39;);
                    host = hostParts[0];
                    if (hostParts.Length &gt; 1)
                        port = int.Parse(hostParts[1]);
                    return true;
                }
// 空行表示头结束
                if (string.IsNullOrWhiteSpace(line))
                    break;
            }
        }
// 3. 旧式HTTP/1.0请求可能没有Host头,从URL解析
        if (request.StartsWith(&#34;GET &#34;) || request.StartsWith(&#34;POST &#34;))
        {
            var url = request.Split(&#39; &#39;)[1];
            if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
            {
                host = uri.Host;
                port = uri.Port;
                return true;
            }
        }
return false;
    }
远程Socks5服务端握手

&gt; &gt; &gt; 这里我们采用的是带有认证的握手,需要服务端也开启认证配置,这里对于握手协议和认证还不清楚的可以看我集合的上面一篇文章 &gt; &gt; &gt; &gt; 将http的远程信息作为握手信息与服务端建立连接,让服务端建立与目标的连接代理。 &gt;

    /// <summary>
    /// 带有身份验证的登录
    /// </summary>
    /// <param name="socks5Stream"/>
    /// <param name="targetHost"/>
    /// <param name="targetPort"/>
    /// <param name="username"/>
    /// <param name="password"/>
    /// 
    /// 
    private async Task PerformSocks5Handshake(NetworkStream socks5Stream,
                                              string targetHost,
                                              int targetPort,
                                              string username,
                                              string password)
    {
        // === 1. 协商认证方法 ===
        // 发送支持的认证方法:无认证(0x00) 和 用户名/密码(0x02)
        var authMethods = new byte[] { 0x05, 0x02, 0x00, 0x02 };
        await socks5Stream.WriteAsync(authMethods, 0, authMethods.Length);
// 读取服务器选择的认证方法
        var authResponse = new byte[2];
        await socks5Stream.ReadAsync(authResponse, 0, 2);
if (authResponse[1] == 0xFF)
            throw new Exception(&#34;SOCKS5服务器不支持任何提供的认证方法&#34;);
// === 2. 用户名/密码认证 ===
        if (authResponse[1] == 0x02)
        {
            // 构建认证请求包
            var authRequest = new byte[3 + username.Length + password.Length];
            authRequest[0] = 0x01; // 认证子协商版本
            authRequest[1] = (byte)username.Length;
            Encoding.ASCII.GetBytes(username).CopyTo(authRequest, 2);
            authRequest[2 + username.Length] = (byte)password.Length;
            Encoding.ASCII.GetBytes(password).CopyTo(authRequest, 3 + username.Length);
await socks5Stream.WriteAsync(authRequest, 0, authRequest.Length);
// 读取认证响应
            var authResult = new byte[2];
            await socks5Stream.ReadAsync(authResult, 0, 2);
            if (authResult[1] != 0x00)
                throw new Exception(&#34;SOCKS5用户名/密码认证失败&#34;);
        }
// === 3. 发送连接请求 ===
        var request = new byte[7 + targetHost.Length];
        request[0] = 0x05; // VER
        request[1] = 0x01; // CMD=CONNECT
        request[2] = 0x00; // RSV
        request[3] = 0x03; // ATYP=域名
        request[4] = (byte)targetHost.Length;
        Encoding.ASCII.GetBytes(targetHost).CopyTo(request, 5);
        BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)targetPort)).CopyTo(request, 5 + targetHost.Length);
await socks5Stream.WriteAsync(request, 0, request.Length);
// === 4. 读取连接响应 ===
        var response = new byte[10];
        await socks5Stream.ReadAsync(response, 0, 10);
        if (response[1] != 0x00)
            throw new Exception($&#34;SOCKS5连接失败 (状态码: {response[1]})&#34;);
    }
交换流量

&gt; &gt; &gt; 所谓的交换流量就是把远程代理的流量和本机的请求流量通过客户端作为中间人来转发 &gt;

    private async Task ForwardDataAsync(NetworkStream src, NetworkStream dest)
    {
        var buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = await src.ReadAsync(buffer, 0, buffer.Length)) &gt; 0)
        {
            await dest.WriteAsync(buffer, 0, bytesRead);
        }
    }
await Task.WhenAny(ForwardDataAsync(httpStream, socks5Stream),ForwardDataAsync(socks5Stream, httpStream)
代理流量显示

&gt; &gt; &gt; 客户端也需要显示上传和下载流量的一些显示,我们这里简单点,因为我们之前开发的服务端是有基于用户的流量统计的,所以只需要把数据获取到就行,一般情况下为了性能,也可以做双端统计减少压力。 &gt; 我们这里通过SSE将用户的流量信息基于用户名推送到客户端。

public async Task ConnectAsync(string remoteAddress, string userName)
{
    _httpClient = new HttpClient(new HttpClientHandler
    {
        Proxy = new WebProxy($&#34;http://{remoteAddress}:5000&#34;), // 明确指定代理
    });
    _cts = new CancellationTokenSource();
try
    {
        using var response = await _httpClient
            .GetAsync($&#34;http://{remoteAddress}:5000/account/flow/{userName}&#34;, HttpCompletionOption.ResponseHeadersRead,_cts.Token);
        if (response.IsSuccessStatusCode)
        {
            using var stream = await response.Content.ReadAsStreamAsync();
            using var reader = new StreamReader(stream);
            while (!_cts.Token.IsCancellationRequested)
            {
                var line = await reader.ReadLineAsync();
                if (!string.IsNullOrEmpty(line))
                {
                    MessageReceived?.Invoke(line);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($&#34;SSE连接错误: {ex.Message}&#34;);
    }
}

验证

远端开启服务端

image

开启客户端

添加一些配置 包括:服务端IP,服务端Port,本地代理Port,用户名密码 image 开启客户端 image 可以看到代理成功,走的是本机的代理和服务端的代理请求 image

结尾

因为本身代理采用的修改系统代理设置是一种基础设置,所谓可能存在下面影响: 仅影响支持系统代理的应用(部分UWP应用、游戏等会绕过 无法代理非HTTP/HTTPS流量(如DNS、UDP

源码地址

https://github.com/BruceQiu1996/Socks5Server