c#开发完整的Socks5代理客户端与服务端——客户端(已完结)
笔记哥 /
05-09 /
38点赞 /
0评论 /
546阅读
> 本文我们介绍下如何在Windows系统上开发一个代理本机流量的客户端,并且对接我们之前开发的Socks5服务端,实现整个代理的一条龙。对于Socks5代理的服务端的开发可以详见之前的文章。
目录
- 本机流量劫持
- 通过系统开启手动代理
- 客户端开启对应TCP服务
- 解析系统的Http请求
- 远程Socks5服务端握手
- 交换流量
- 代理流量显示
- 验证
- 远端开启服务端
- 开启客户端
- 结尾
- 源码地址
#### 本机流量劫持
##### 通过系统开启手动代理
通过c#程序打开Windows的手动代理, 并且设置端口号和IP地址,这样只要客户端监听该端口就可以获取到本机的Http的流量数据。
>
>
> 通过对注册表的修改,来开启本机的手动代理,并且设置端口,Ip设置为本机,因为客户端是本机启动的,端口设置不冲突的即可。
>
>
>
> 黑名单则是设置哪些域名或者IP段不走代理,我们这里先把局域网的排除掉。
>
```csharp
// 引入Windows API
[DllImport("wininet.dll")]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
public const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
public const int INTERNET_OPTION_REFRESH = 37;
// 设置系统代理
public static void SetProxy(string proxyServer, bool enable)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
// 设置代理服务器地址和端口
Microsoft.Win32.Registry.SetValue(keyName, "ProxyServer", proxyServer);
// 启用或禁用代理
Microsoft.Win32.Registry.SetValue(keyName, "ProxyEnable", enable ? 1 : 0);
// 通知系统设置已更改
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
///
/// 黑名单
///
///
public static void SetProxyExceptions(string exceptions)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
Microsoft.Win32.Registry.SetValue(keyName, "ProxyOverride", exceptions);
}
```
```csharp
SystemProxy.SetProxy($"127.0.0.1:{App.SettingsModel.LocalPort}", true);
SystemProxy.SetProxyExceptions("localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*;");
```
##### 客户端开启对应TCP服务
```csharp
_tcpListener = new TcpListener(IPAddress.Any, _httpProxyPort);
_tcpListener.Start();
```
主要就是开启一个监听服务,让操作系统将对应的流量转发到我们的Socks5客户端。
>
>
> 将http报文进行解析,获取到请求的targetHost和Port,**这样才能后面在和Socks5服务端握手的时候才能告知对方需要代理的远端信息**。
>
##### 解析系统的Http请求
```csharp
///
/// 解析http请求报文,提取关键信息
///
/// http请求
/// 请求主机
/// 请求主机端口号
/// 是否解析成功
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("CONNECT"))
{
var parts = request.Split(' ')[1].Split(':');
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("Host:", StringComparison.OrdinalIgnoreCase))
{
var hostParts = line.Substring(5).Trim().Split(':');
host = hostParts[0];
if (hostParts.Length > 1)
port = int.Parse(hostParts[1]);
return true;
}
// 空行表示头结束
if (string.IsNullOrWhiteSpace(line))
break;
}
}
// 3. 旧式HTTP/1.0请求可能没有Host头,从URL解析
if (request.StartsWith("GET ") || request.StartsWith("POST "))
{
var url = request.Split(' ')[1];
if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
host = uri.Host;
port = uri.Port;
return true;
}
}
return false;
}
```
##### 远程Socks5服务端握手
>
>
> 这里我们采用的是带有认证的握手,需要服务端也开启认证配置,**这里对于握手协议和认证还不清楚的可以看我集合的上面一篇文章**
>
>
>
> 将http的远程信息作为握手信息与服务端建立连接,让服务端建立与目标的连接代理。
>
```csharp
///
/// 带有身份验证的登录
///
///
///
///
///
///
///
///
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("SOCKS5服务器不支持任何提供的认证方法");
// === 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("SOCKS5用户名/密码认证失败");
}
// === 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($"SOCKS5连接失败 (状态码: {response[1]})");
}
```
##### 交换流量
>
>
> 所谓的交换流量就是把远程代理的流量和本机的请求流量通过客户端作为中间人来转发
>
```csharp
private async Task ForwardDataAsync(NetworkStream src, NetworkStream dest)
{
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await src.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await dest.WriteAsync(buffer, 0, bytesRead);
}
}
```
```csharp
await Task.WhenAny(ForwardDataAsync(httpStream, socks5Stream),ForwardDataAsync(socks5Stream, httpStream)
```
##### 代理流量显示
>
>
> 客户端也需要显示上传和下载流量的一些显示,我们这里简单点,因为我们之前开发的服务端是有基于用户的流量统计的,所以只需要把数据获取到就行,一般情况下为了性能,也可以做双端统计减少压力。
>
我们这里通过SSE将用户的流量信息基于用户名推送到客户端。
```csharp
public async Task ConnectAsync(string remoteAddress, string userName)
{
_httpClient = new HttpClient(new HttpClientHandler
{
Proxy = new WebProxy($"http://{remoteAddress}:5000"), // 明确指定代理
});
_cts = new CancellationTokenSource();
try
{
using var response = await _httpClient
.GetAsync($"http://{remoteAddress}:5000/account/flow/{userName}", 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($"SSE连接错误: {ex.Message}");
}
}
```
#### 验证
##### 远端开启服务端

##### 开启客户端
添加一些配置
包括:服务端IP,服务端Port,本地代理Port,用户名密码

开启客户端

可以看到代理成功,走的是本机的代理和服务端的代理请求

#### 结尾
因为本身代理采用的修改系统代理设置是一种基础设置,所谓可能存在下面影响:
**仅影响支持系统代理的应用(部分UWP应用、游戏等会绕过**
**无法代理非HTTP/HTTPS流量(如DNS、UDP**
#### 源码地址
https://github.com/BruceQiu1996/Socks5Server
本文来自投稿,不代表本站立场,如若转载,请注明出处:http//www.knowhub.vip/share/2/3194
- 热门的技术博文分享
- 1 . ESP实现Web服务器
- 2 . 从零到一:打造高效的金仓社区 API 集成到 MCP 服务方案
- 3 . 使用C#构建一个同时问多个LLM并总结的小工具
- 4 . .NET 原生驾驭 AI 新基建实战系列Milvus ── 大规模 AI 应用的向量数据库首选
- 5 . 在Avalonia/C#中使用依赖注入过程记录
- 6 . [设计模式/Java] 设计模式之工厂方法模式
- 7 . 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
- 8 . SQL 中的各种连接 JOIN 的区别总结!
- 9 . JavaScript 中防抖和节流的多种实现方式及应用场景
- 10 . SaltStack 远程命令执行中文乱码问题
- 11 . 推荐10个 DeepSeek 神级提示词,建议搜藏起来使用
- 12 . C#基础:枚举、数组、类型、函数等解析
- 13 . VMware平台的Ubuntu部署完全分布式Hadoop环境
- 14 . C# 多项目打包时如何将项目引用转为包依赖
- 15 . Chrome 135 版本开发者工具(DevTools)更新内容
- 16 . 从零创建npm依赖,只需执行一条命令
- 17 . 关于 Newtonsoft.Json 和 System.Text.Json 混用导致的的序列化不识别的问题
- 18 . 大模型微调实战之训练数据集准备的艺术与科学
- 19 . Windows快速安装MongoDB之Mongo实战
- 20 . 探索 C# 14 新功能:实用特性为编程带来便利