Frpc 内网穿透客户端配置教程
github: https://github.com/fatedier/frp/releases
1 下载操作系统对应版本的Frpc.exe 客户端程序
2 配置对应的frpc.ini文件
3 切换到Frpc.exe目录,cmd执行:Frpc.exe -c frpc.ini

例如:frpc.ini 如下

例如 frps服务地址:10.10.10.10 端口10000
本地服务端口5000, frps 域名219801a6865241m00017testappe.frp.ws.com,端口8000
本地应用唯一实例:[219801a6865241m00017testapp]
假设本地Api为:http://192.169.137.10/5000/edit;postId=18337057#postBody
内网穿透代理为:http://219801a6865241m00017testappe.frp.ws.com/8000/edit;postId=18337057#postBody
注意:frpc.ini 配置通过请求后台下发
/// <summary>
/// 内网穿透代理客户端配置
/// </summary>
public class FrpcIni
{
public CommonSection Common { get; set; }
public AppSection App { get; set; }
}
/// <summary>
/// 公共配置节点
/// </summary>
public class CommonSection
{
public string ServerAddress { get; set; }
public string ServerPort { get; set; }
public string AuthenticationMethod { get; set; }
public string Token { get; set; }
public string DialServerTimeout { get; set; }
public string LoginFailExit { get; set; }
}
/// <summary>
/// 应用配置
/// </summary>
public class AppSection
{
public string Type { get; set; }
public string RemotePort { get; set; }
public string LocalIp { get; set; }
public string LocalPort { get; set; }
public string UseCompression { get; set; }
public string CustomDomains { get; set; }
}
/// <summary>
/// 代理客户端Ini配置常量
/// </summary>
internal class FrpcIniConst
{
//与服务端连接段和Key
public const string CommonSection = "common";
public const string ServerAddrKey = "server_addr";
public const string ServerPortKey = "server_port";
public const string AuthenticationMethodKey = "authentication_method";
public const string TokenKey = "token";
public const string DialServerTimeout = "dial_server_timeout";
public const string LoginFailExit = "login_fail_exit";
//单应用段和Key
public const string AppSection = "app_name";
public const string TypeKey = "type";
public const string LocalIpKey = "local_ip";
public const string LocalPortKey = "local_port";
public const string UseCompressionKey = "use_compression";
public const string CustomDomainsKey = "custom_domains";
public const string RemotePortKey = "remote_port";
public const string FrpcName = "frpc";
}
/// <summary>
/// 代理配置操作
/// </summary>
internal class FrpcConfigurationOperation
{
/// <summary>
/// 配置操作
/// </summary>
/// <param name="responseRegister">注册响应的包</param>
/// <param name="localServicePort">本地服务端口号</param>
/// <param name="appSection">应用节点</param>
public FrpcConfigurationOperation(ResponseRegister responseRegister, int localServicePort, string appSection)
{
_responseRegister = responseRegister;
_localServicePort = localServicePort;
_appSection = appSection;
}
/// <summary>
/// 更新配置
/// </summary>
/// <param name="frpcIniPath"></param>
public void UpDateFrpcIni( string frpcIniPath)
{
var writeFrpcIni = ResponseToFrpcIni(_responseRegister, _localServicePort, _appSection);
var readFrpcIni = ReadFrpcIniFromFile(frpcIniPath, _appSection);
WriteFrpcIniToFile(frpcIniPath, writeFrpcIni, readFrpcIni, _appSection);
}
/// <summary>
/// 响应包转换配置
/// </summary>
/// <returns></returns>
public FrpcIni ResponseToFrpcIni()
{
return ResponseToFrpcIni(_responseRegister, _localServicePort, _appSection);
}
/// <summary>
/// 响应包转换配置
/// </summary>
/// <param name="responseRegister"></param>
/// <param name="localServicePort"></param>
/// <param name="appSection"></param>
/// <returns></returns>
private FrpcIni ResponseToFrpcIni(ResponseRegister responseRegister, int localServicePort, string appSection)
{
if (responseRegister.Server == null) throw new InvalidOperationException("无法获取服务信息");
if (!responseRegister.SubDomainList.Any()) throw new InvalidOperationException("无法获取分配的子域");
var app = responseRegister.SubDomainList.First(s => s.Subdomain.StartsWith(appSection, StringComparison.CurrentCultureIgnoreCase));
if (app == null) throw new InvalidOperationException("无法获取分配的子域");
var frpcIni = new FrpcIni
{
Common = new CommonSection
{
ServerAddress = responseRegister.Server.ServerAddress,
ServerPort = responseRegister.Server.ServerPort,
AuthenticationMethod = responseRegister.Server.AuthenticationMethod,
Token = responseRegister.Server.Token,
DialServerTimeout = responseRegister.Server.DialServerTimeout,
LoginFailExit = responseRegister.Server.LoginFailExit,
},
App = new AppSection()
};
frpcIni.App.Type = app.Type;
frpcIni.App.RemotePort = app.RemotePort;
frpcIni.App.UseCompression = app.UseCompression;
//必须小写,否则路由无法找到:frp已知问题
frpcIni.App.CustomDomains = app.CustomDomain;
frpcIni.App.LocalIp = LocalIp;
frpcIni.App.LocalPort = localServicePort.ToString();
return frpcIni;
}
/// <summary>
/// 从文件读取代理客户端配置
/// </summary>
/// <param name="filePath"></param>
/// <param name="appSection"></param>
/// <returns></returns>
private FrpcIni ReadFrpcIniFromFile(string filePath, string appSection)
{
if (!File.Exists(filePath)) return null;
DeleteErrorSection(filePath, appSection);
return new FrpcIni
{
Common = new CommonSection
{
ServerAddress = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerAddrKey),
ServerPort = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerPortKey),
AuthenticationMethod = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.AuthenticationMethodKey),
Token = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.TokenKey),
DialServerTimeout = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.DialServerTimeout),
LoginFailExit = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.LoginFailExit)
},
App = new AppSection
{
Type = IniHelper.Read(filePath, appSection, FrpcIniConst.TypeKey),
LocalIp = IniHelper.Read(filePath, appSection, FrpcIniConst.LocalIpKey),
LocalPort = IniHelper.Read(filePath, appSection, FrpcIniConst.LocalPortKey),
UseCompression = IniHelper.Read(filePath, appSection, FrpcIniConst.UseCompressionKey),
CustomDomains = IniHelper.Read(filePath, appSection, FrpcIniConst.CustomDomainsKey),
RemotePort = IniHelper.Read(filePath, appSection, FrpcIniConst.RemotePortKey)
}
};
}
/// <summary>
/// 向文件写入代理客户端配置
/// </summary>
/// <param name="filePath"></param>
/// <param name="writeFrpcIni"></param>
/// <param name="readFrpcIni"></param>
/// <param name="appSection"></param>
private void WriteFrpcIniToFile(string filePath, FrpcIni writeFrpcIni, FrpcIni readFrpcIni, string appSection)
{
if (IsNeedWrite(writeFrpcIni.Common.ServerAddress, readFrpcIni.Common.ServerAddress))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerAddrKey, writeFrpcIni.Common.ServerAddress);
if (IsNeedWrite(writeFrpcIni.Common.ServerPort, readFrpcIni.Common.ServerPort))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerPortKey, writeFrpcIni.Common.ServerPort);
if (IsNeedWrite(writeFrpcIni.Common.AuthenticationMethod, readFrpcIni.Common.AuthenticationMethod))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.AuthenticationMethodKey, writeFrpcIni.Common.AuthenticationMethod);
if (IsNeedWrite(writeFrpcIni.Common.Token, readFrpcIni.Common.Token))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.TokenKey, writeFrpcIni.Common.Token);
if (IsNeedWrite(writeFrpcIni.Common.DialServerTimeout, readFrpcIni.Common.DialServerTimeout))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.DialServerTimeout, writeFrpcIni.Common.DialServerTimeout);
if (IsNeedWrite(writeFrpcIni.Common.LoginFailExit, readFrpcIni.Common.LoginFailExit))
IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.LoginFailExit, writeFrpcIni.Common.LoginFailExit);
if (IsNeedWrite(writeFrpcIni.App.Type, readFrpcIni.App.Type))
IniHelper.Write(filePath, appSection, FrpcIniConst.TypeKey, writeFrpcIni.App.Type);
if (IsNeedWrite(writeFrpcIni.App.LocalIp, readFrpcIni.App.LocalIp))
IniHelper.Write(filePath, appSection, FrpcIniConst.LocalIpKey, writeFrpcIni.App.LocalIp);
if (IsNeedWrite(writeFrpcIni.App.LocalPort, readFrpcIni.App.LocalPort))
IniHelper.Write(filePath, appSection, FrpcIniConst.LocalPortKey, writeFrpcIni.App.LocalPort);
if (IsNeedWrite(writeFrpcIni.App.UseCompression, readFrpcIni.App.UseCompression))
IniHelper.Write(filePath, appSection, FrpcIniConst.UseCompressionKey, writeFrpcIni.App.UseCompression);
if (IsNeedWrite(writeFrpcIni.App.CustomDomains, readFrpcIni.App.CustomDomains, false))
IniHelper.Write(filePath, appSection, FrpcIniConst.CustomDomainsKey, writeFrpcIni.App.CustomDomains);
if (IsNeedWrite(writeFrpcIni.App.RemotePort, readFrpcIni.App.RemotePort, false))
IniHelper.Write(filePath, appSection, FrpcIniConst.RemotePortKey, writeFrpcIni.App.RemotePort);
}
/// <summary>
/// 需要写入
/// </summary>
/// <param name="writeValue"></param>
/// <param name="readValue"></param>
/// <param name="ignoreCase"></param>
/// <returns></returns>
private bool IsNeedWrite(string writeValue, string readValue, bool ignoreCase = true)
{
if (string.IsNullOrWhiteSpace(writeValue)) return true;
if (ignoreCase)
return !writeValue.Equals(readValue, StringComparison.CurrentCultureIgnoreCase);
return !writeValue.Equals(readValue);
}
/// <summary>
/// 删除错误节点
/// </summary>
/// <param name="filePath"></param>
/// <param name="appSection"></param>
private void DeleteErrorSection(string filePath, string appSection)
{
var sections = IniHelper.ReadSections(filePath);
if (sections == null || sections.Count <= 0) return;
var ignoreSection = FrpcIniConst.CommonSection;
foreach (var section in sections)
{
if (section.Equals(ignoreSection) || section.Equals(appSection))
continue;
IniHelper.DeleteSection(filePath, section);
}
}
private const string LocalIp = "localhost";
private readonly ResponseRegister _responseRegister;
private readonly int _localServicePort;
private readonly string _appSection;
}
/// <summary>
/// 代理状态
/// </summary>
public enum FrpcAgentStatus
{
[Description("未知状态")]
UnKnow = -1,
[Description("代理开启成功")]
StartUpSuccess =0,
[Description("代理开启失败")]
StartUpFailed = 1,
[Description("代理关闭")]
Closed = 2,
[Description("代理路由冲突")]
Conflict = 3,
[Description("代理被拒绝连接")]
Refused =4,
}
/// <summary>
/// 代理内网穿透客户端
/// </summary>
public class FrpcAgent
{
/// <summary>
/// 连接改变
/// </summary>
public event EventHandler<bool> ConnectedChanged;
/// <summary>
/// 代理接收信息
/// </summary>
public event EventHandler<string> OutputReceived;
/// <summary>
/// 代理收错误信息
/// </summary>
public event EventHandler<string> ErrorReceived;
/// <summary>
/// 代理状态
/// </summary>
public event EventHandler<FrpcAgentStatus> AgentStatus;
public FrpcAgent()
{
Application.Current.Dispatcher?.Invoke(() =>
{
Application.Current.Exit += Current_Exit;
});
}
/// <summary>
/// 退出代理
/// </summary>
public void Exit()
{
Application.Current.Dispatcher?.Invoke(() =>
{
Application.Current.Exit -= Current_Exit;
});
Current_Exit(null, null);
}
/// <summary>
/// 开启代理
/// </summary>
/// <returns></returns>
public async Task Start(string intranetPenetrationPath)
{
await Task.Run(() =>
{
_frpcAgentFullName = Path.Combine(intranetPenetrationPath, $"{FrpcIniConst.FrpcName}.exe");
KillLocalMainModule(_frpcAgentFullName);
StartProcess(_frpcAgentFullName, AgentIniParameter(), intranetPenetrationPath);
});
}
#region 私有方法
private void StartProcess(string projectUrl, string arguments, string intranetPenetrationPath)
{
ExitedHandle();
_frpcProcess = new Process
{
StartInfo =
{
UseShellExecute = false,
WorkingDirectory=intranetPenetrationPath,
FileName = projectUrl,
Arguments = arguments,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
},
EnableRaisingEvents = true
};
//守护重启
_frpcProcess.Exited += FrpcProcess_Exited;
_frpcProcess.StartInfo.RedirectStandardInput = true;
_frpcProcess.StartInfo.RedirectStandardOutput = true;
_frpcProcess.StartInfo.RedirectStandardError = true;
_frpcProcess.StartInfo.StandardErrorEncoding = Encoding.UTF8;
_frpcProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8;
_frpcProcess.OutputDataReceived += FrpcProcess_OutputDataReceived;
_frpcProcess.ErrorDataReceived += FrpcProcess_ErrorDataReceived;
_frpcProcess.Start();
_frpcProcess.BeginOutputReadLine();
_frpcProcess.BeginErrorReadLine();
}
private void FrpcProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
ErrorReceived?.Invoke(sender, $"{FrpcIniConst.FrpcName} -ErrorDataReceived: {e.Data}");
}
private void FrpcProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
var strOutput = e.Data;
if (string.IsNullOrWhiteSpace(strOutput)) return;
var agentStatus = GetAgentStatus(strOutput);
switch (agentStatus)
{
case FrpcAgentStatus.StartUpSuccess:
ConnectedChanged?.Invoke(this, true);
break;
case FrpcAgentStatus.StartUpFailed:
ConnectedChanged?.Invoke(this, false);
break;
case FrpcAgentStatus.Closed:
ConnectedChanged?.Invoke(this, false);
break;
case FrpcAgentStatus.Conflict:
ConnectedChanged?.Invoke(this, false);
KillLocalMainModule(_frpcAgentFullName);
AgentStatus?.Invoke(sender, FrpcAgentStatus.Conflict);
break;
case FrpcAgentStatus.Refused:
ConnectedChanged?.Invoke(this, false);
Exit();
AgentStatus?.Invoke(sender, FrpcAgentStatus.Refused);
break;
case FrpcAgentStatus.UnKnow:
break;
default:
throw new ArgumentOutOfRangeException();
}
OutputReceived?.Invoke(sender, $"{FrpcIniConst.FrpcName} -OutputDataReceived: {strOutput}");
}
private void FrpcProcess_Exited(object sender, EventArgs e)
{
ExitedHandle();
}
private void ExitedHandle()
{
if (_frpcProcess == null) return;
_frpcProcess.Exited -= FrpcProcess_Exited;
ConnectedChanged?.Invoke(this, false);
OutputReceived?.Invoke(this, $"{FrpcIniConst.FrpcName} -Exited:");
_frpcProcess.Dispose();
_frpcProcess = null;
}
/// <summary>
/// 返回代理状态
/// </summary>
/// <param name="strOutput"></param>
/// <returns></returns>
private FrpcAgentStatus GetAgentStatus(string strOutput)
{
var success = strOutput.ToLower().Contains(Proxy) && strOutput.ToLower().Contains(ProxySuccess);
if (success) return FrpcAgentStatus.StartUpSuccess;
var error = strOutput.ToLower().Contains(Proxy) && strOutput.ToLower().Contains(ProxyError);
if (error) return FrpcAgentStatus.StartUpFailed;
var close = strOutput.ToLower().Contains(Closing) || strOutput.ToLower().Contains(Closed) || strOutput.ToLower().Contains(Failed);
if (close) return FrpcAgentStatus.Closed;
var conflict = strOutput.ToLower().Contains(Conflict);
if (conflict) return FrpcAgentStatus.Conflict;
var refused = strOutput.ToLower().Contains(Refused);
if (refused) return FrpcAgentStatus.Refused;
return FrpcAgentStatus.UnKnow;
}
/// <summary>
/// 杀掉当前应用域下的代理客户端
/// </summary>
private void KillLocalMainModule(string agentFullName)
{
Process[] services = null;
try
{
services = Process.GetProcessesByName(FrpcIniConst.FrpcName);
if (services.Length <= 0) return;
foreach (var service in services)
{
if (service.MainModule != null &&
service.MainModule.FileName.StartsWith(agentFullName,
StringComparison.CurrentCultureIgnoreCase))
service.Kill();
}
}
catch (Exception e)
{
ErrorReceived?.Invoke(null, e.Message);
}
finally
{
if (services != null)
{
foreach (var process in services)
{
process.Dispose();
}
}
}
}
private void Current_Exit(object sender, ExitEventArgs e)
{
try
{
if (_frpcProcess != null)
{
_frpcProcess.Exited -= FrpcProcess_Exited;
_frpcProcess.OutputDataReceived -= FrpcProcess_OutputDataReceived;
_frpcProcess.ErrorDataReceived -= FrpcProcess_ErrorDataReceived;
}
_frpcProcess?.Kill();
_frpcProcess?.Close();
_frpcProcess?.Dispose();
_frpcProcess = null;
}
catch (Exception ex)
{
ErrorReceived?.Invoke(null, ex.Message);
}
}
/// <summary>
/// 代理配置
/// </summary>
private string AgentIniParameter() => $"-c {FrpcIniConst.FrpcName}.ini";
#endregion
#region 私有字段
//代理开启成功
private const string Proxy = "proxy";
private const string ProxySuccess = "success";
//代理开启失败
private const string ProxyError = "error";
private const string Failed = "failed";
//断开
private const string Closing = "closing";
private const string Closed = "closed";
//冲突
private const string Conflict = "conflict";
private const string Refused = "refused";
//代理客户端完整路径
private string _frpcAgentFullName;
private Process _frpcProcess;
#endregion
}
浙公网安备 33010602011771号